diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d589614476d6..8b1e812e67ea3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -655,24 +655,26 @@ jobs: # List loaded images docker images - - name: Run federation integration tests with pre-built image + - name: Start federation services working-directory: ./ee/packages/federation-matrix env: ROCKETCHAT_IMAGE: ghcr.io/${{ needs.release-versions.outputs.lowercase-repo }}/rocket.chat:${{ needs.release-versions.outputs.gh-docker-tag }}-amd64 ENTERPRISE_LICENSE_RC1: ZAikY+LLaal7mT6RNYxpyWEmMQyucrl50/7pYBXqHczc90j+RLwF+T0xuCT2pIpKMC5DxcZ1TtkV6MYJk5whrwmap+mQ0FV+VpILJlL0i4T21K4vMfzZXTWm/pzcAy2fMTUNH+mUA9HTBD6lYYh40KnbGXPAd80VbZk0MO/WbWBm2dOT0YCwfvlRyurRqkDAQrftLaffzCNUsMKk0fh+MKs73UDHZQDp1yvs7WoGpPu5ZVi5mTBOt3ZKVz5KjGfClLwJptFPmW1w6nKelAiJBDPpjcX1ylfjxpnBoixko7uN52zlyaeoAYwfRcdDLnZ8k0Ou6tui/vTQUXjGIjHw2AhMaKwonn4E9LYpuA1KEXt08qJL5J3ZtjSCV1T+A9Z3zFhhLgp5dxP/PPUbxDn/P8XKp7nXM9duIfcCMlnea7V8ixEyCHwwvKQaXVVidcsUGtB8CwS0GlsAEBLOzqMehuQUK2rdQ4WgEz3AYveikeVvSzgBHvyXsxssWAThc0Mht0eEJqdDhUB2QeZ2WmPsaSSD639Z4WgjSUoR0zh8bfqepH+2XRcUryXe2yN+iU+3POzi9wfg0k65MxXT8pBg3PD5RHnR8oflEP0tpZts33JiBhYRxX3MKplAFm4dMuphTsDJTh+e534pT7IPuZF79QSVaLEWZfVVVb7nGFtmMwA= QASE_TESTOPS_JEST_API_TOKEN: ${{ secrets.QASE_TESTOPS_JEST_API_TOKEN }} PR_NUMBER: ${{ github.event.number }} - run: yarn test:integration --image "${ROCKETCHAT_IMAGE}" + run: yarn test:integration --start-containers-only --image "${ROCKETCHAT_IMAGE}" - - name: Show rc server logs if tests failed - if: failure() + - name: Run federation integration tests working-directory: ./ee/packages/federation-matrix - run: docker compose -f docker-compose.test.yml logs rc1-prebuilt + env: + QASE_TESTOPS_JEST_API_TOKEN: ${{ secrets.QASE_TESTOPS_JEST_API_TOKEN }} + PR_NUMBER: ${{ github.event.number }} + run: yarn test:integration --ci - - name: Show hs server logs if tests failed + - name: Show federation integration tests logs if: failure() working-directory: ./ee/packages/federation-matrix - run: docker compose -f docker-compose.test.yml logs hs1 + run: yarn test:integration --logs - name: Show mongo logs if tests failed if: failure() diff --git a/apps/meteor/package.json b/apps/meteor/package.json index d33faf9c0e72b..f3119ef09c0b7 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -99,7 +99,7 @@ "@rocket.chat/emitter": "~0.31.25", "@rocket.chat/favicon": "workspace:^", "@rocket.chat/federation-matrix": "workspace:^", - "@rocket.chat/federation-sdk": "0.3.8", + "@rocket.chat/federation-sdk": "0.3.9", "@rocket.chat/fuselage": "^0.70.0", "@rocket.chat/fuselage-forms": "~0.1.1", "@rocket.chat/fuselage-hooks": "~0.38.1", diff --git a/ee/packages/federation-matrix/jest.config.federation.ts b/ee/packages/federation-matrix/jest.config.federation.ts index 85e34daae709c..aa685c2daf12d 100644 --- a/ee/packages/federation-matrix/jest.config.federation.ts +++ b/ee/packages/federation-matrix/jest.config.federation.ts @@ -55,7 +55,6 @@ export default { complete: true, }, }, - debug: true, }, ] as [string, { [x: string]: unknown }], ] diff --git a/ee/packages/federation-matrix/package.json b/ee/packages/federation-matrix/package.json index 529f74173da43..7e205339a76df 100644 --- a/ee/packages/federation-matrix/package.json +++ b/ee/packages/federation-matrix/package.json @@ -22,7 +22,7 @@ "@rocket.chat/core-services": "workspace:^", "@rocket.chat/core-typings": "workspace:^", "@rocket.chat/emitter": "^0.31.25", - "@rocket.chat/federation-sdk": "0.3.8", + "@rocket.chat/federation-sdk": "0.3.9", "@rocket.chat/http-router": "workspace:^", "@rocket.chat/license": "workspace:^", "@rocket.chat/models": "workspace:^", diff --git a/ee/packages/federation-matrix/tests/helper/synapse-client.ts b/ee/packages/federation-matrix/tests/helper/synapse-client.ts index 17b61af16cc69..8a23b43c3f3a9 100644 --- a/ee/packages/federation-matrix/tests/helper/synapse-client.ts +++ b/ee/packages/federation-matrix/tests/helper/synapse-client.ts @@ -8,6 +8,12 @@ import * as fs from 'fs'; import * as path from 'path'; import { createClient, type MatrixClient, KnownMembership, type Room, type RoomMember, Visibility } from 'matrix-js-sdk'; +import { logger } from 'matrix-js-sdk/lib/logger'; + +logger.debug = () => void 0; +logger.info = () => void 0; +logger.warn = () => void 0; +logger.error = () => void 0; /** * Creates a promise that resolves after the specified delay. diff --git a/ee/packages/federation-matrix/tests/scripts/run-integration-tests.sh b/ee/packages/federation-matrix/tests/scripts/run-integration-tests.sh index b2b5e8bd7b293..64fe8b066e6a5 100755 --- a/ee/packages/federation-matrix/tests/scripts/run-integration-tests.sh +++ b/ee/packages/federation-matrix/tests/scripts/run-integration-tests.sh @@ -20,7 +20,6 @@ PACKAGE_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")" DOCKER_COMPOSE_FILE="$PACKAGE_ROOT/docker-compose.test.yml" MAX_WAIT_TIME=240 # 4 minutes CHECK_INTERVAL=5 # Check every 5 seconds -RC1_CONTAINER="rc1" # Build configuration # Use a temporary directory outside the repo to avoid symlink traversal issues during Meteor build @@ -34,9 +33,30 @@ USE_PREBUILT_IMAGE=false PREBUILT_IMAGE="" INTERRUPTED=false NO_TEST=false +CI=false +LOGS=false +START_CONTAINERS=true + while [[ $# -gt 0 ]]; do case $1 in + --start-containers-only) + NO_TEST=true + KEEP_RUNNING=true + shift + ;; + --ci) + CI=true + KEEP_RUNNING=true + START_CONTAINERS=false + shift + ;; + --logs) + LOGS=true + NO_TEST=true + START_CONTAINERS=false + shift + ;; --keep-running) KEEP_RUNNING=true shift @@ -63,11 +83,14 @@ while [[ $# -gt 0 ]]; do --help|-h) echo "Usage: $0 [OPTIONS]" echo "Options:" - echo " --keep-running Keep Docker containers running after tests complete" - echo " --element Include Element web client in the test environment" - echo " --no-test Start containers and skip running tests" - echo " --image [IMAGE] Use a pre-built Docker image instead of building locally" - echo " --help, -h Show this help message" + echo " --start-containers-only Start containers and skip running tests" + echo " --ci Run tests in CI mode (keep containers running, no cleanup)" + echo " --logs Show logs of rc1 and hs1 containers" + echo " --keep-running Keep Docker containers running after tests complete" + echo " --element Include Element web client in the test environment" + echo " --no-test Start containers and skip running tests" + echo " --image [IMAGE] Use a pre-built Docker image instead of building locally" + echo " --help, -h Show this help message" echo "" echo "By default, builds Rocket.Chat locally and runs the 'test' profile" echo "Use --image to test against a pre-built image (e.g., --image rocketchat/rocket.chat:latest)" @@ -101,8 +124,30 @@ log_error() { echo -e "${RED}❌ [$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}" } +docker_logs() { + echo "" + echo "ROCKET.CHAT (rc1) LOGS:" + echo "----------------------------------------" + docker compose -f "$DOCKER_COMPOSE_FILE" --profile "$COMPOSE_PROFILE" logs rc1 + + echo "" + echo "SYNAPSE (hs1) LOGS:" + echo "----------------------------------------" + docker compose -f "$DOCKER_COMPOSE_FILE" --profile "$COMPOSE_PROFILE" logs hs1 + + echo "" + echo "==========================================" +} + # Cleanup function cleanup() { + if [ "$CI" = true ]; then + # Exit with the test result code + if [ -n "${TEST_EXIT_CODE:-}" ]; then + exit $TEST_EXIT_CODE + fi + fi + # Show container logs if tests failed if [ -n "${TEST_EXIT_CODE:-}" ] && [ "$TEST_EXIT_CODE" -ne 0 ]; then echo "" @@ -110,26 +155,7 @@ cleanup() { echo "CONTAINER LOGS (Test Failed)" echo "==========================================" - echo "" - echo "ROCKET.CHAT (rc1) LOGS:" - echo "----------------------------------------" - if docker ps -q -f name=rc1 | grep -q .; then - docker logs rc1 2>&1 | sed 's/^/ /' - else - echo " Rocket.Chat container not found or no logs" - fi - - echo "" - echo "SYNAPSE (hs1) LOGS:" - echo "----------------------------------------" - if docker ps -q -f name=hs1 | grep -q .; then - docker logs hs1 2>&1 | sed 's/^/ /' - else - echo " Synapse container not found or no logs" - fi - - echo "" - echo "==========================================" + docker_logs fi if [ "$KEEP_RUNNING" = true ]; then @@ -141,20 +167,12 @@ cleanup() { if [ "$INCLUDE_ELEMENT" = true ]; then log_info " - Element: https://element" fi - if [ "$INCLUDE_ELEMENT" = true ]; then - log_info "To stop containers manually, run: docker compose -f $DOCKER_COMPOSE_FILE --profile element down -v" - else - log_info "To stop containers manually, run: docker compose -f $DOCKER_COMPOSE_FILE --profile test down -v" - fi + log_info "To stop containers manually, run: docker compose -f \"$DOCKER_COMPOSE_FILE\" --profile \"$COMPOSE_PROFILE\" down -v" else log_info "Cleaning up services..." - if [ -f "$DOCKER_COMPOSE_FILE" ]; then - if [ "$INCLUDE_ELEMENT" = true ]; then - docker compose -f "$DOCKER_COMPOSE_FILE" --profile "element" down -v 2>/dev/null || true - else - docker compose -f "$DOCKER_COMPOSE_FILE" --profile "test" down -v 2>/dev/null || true - fi - fi + + docker compose -f "$DOCKER_COMPOSE_FILE" --profile "$COMPOSE_PROFILE" down -v 2>/dev/null || true + log_success "Cleanup completed" fi @@ -181,145 +199,153 @@ if [ ! -f "$DOCKER_COMPOSE_FILE" ]; then exit 1 fi -# Build Rocket.Chat locally if not using pre-built image -if [ "$USE_PREBUILT_IMAGE" = false ]; then - log_info "🚀 Building Rocket.Chat locally..." - log_info "=====================================" - - # Clean up any existing build - log_info "Cleaning up previous build..." - rm -rf "$BUILD_DIR" - - # Build the project - log_info "Building packages from project root..." - cd "$ROCKETCHAT_ROOT" - yarn build - - # Build the Meteor bundle (must be run from the meteor directory) - log_info "Building Meteor bundle..." - cd "$ROCKETCHAT_ROOT/apps/meteor" - METEOR_DISABLE_OPTIMISTIC_CACHING=1 meteor build --server-only --directory "$BUILD_DIR" - - log_success "Build completed!" +if [ "$INCLUDE_ELEMENT" = true ]; then + COMPOSE_PROFILE="element" else - log_info "🚀 Using pre-built image: $PREBUILT_IMAGE" - log_info "=====================================" + COMPOSE_PROFILE="test" fi -log_info "🚀 Starting Federation Integration Tests" -log_info "=====================================" +if [ "$START_CONTAINERS" = true ]; then + # Build Rocket.Chat locally if not using pre-built image + if [ "$USE_PREBUILT_IMAGE" = false ]; then + log_info "🚀 Building Rocket.Chat locally..." + log_info "=====================================" -BUILD_PARAM="" + # Clean up any existing build + log_info "Cleaning up previous build..." + rm -rf "$BUILD_DIR" -# Set environment variables for Docker Compose -if [ "$USE_PREBUILT_IMAGE" = true ]; then - export ROCKETCHAT_IMAGE="$PREBUILT_IMAGE" - log_info "Using pre-built image: $PREBUILT_IMAGE" -else - export ROCKETCHAT_BUILD_CONTEXT="$BUILD_DIR" - export ROCKETCHAT_DOCKERFILE="$ROCKETCHAT_ROOT/apps/meteor/.docker/Dockerfile.alpine" - BUILD_PARAM="--build" - log_info "Building from local context: $BUILD_DIR" -fi + # Build the project + log_info "Building packages from project root..." + cd "$ROCKETCHAT_ROOT" + yarn build -# Start services -if [ "$INCLUDE_ELEMENT" = true ]; then - log_info "Starting all federation services including Element web client..." - docker compose -f "$DOCKER_COMPOSE_FILE" --profile "element" up -d $BUILD_PARAM -else - log_info "Starting federation services (test profile only)..." - docker compose -f "$DOCKER_COMPOSE_FILE" --profile "test" up -d $BUILD_PARAM -fi + # Build the Meteor bundle (must be run from the meteor directory) + log_info "Building Meteor bundle..." + cd "$ROCKETCHAT_ROOT/apps/meteor" + METEOR_DISABLE_OPTIMISTIC_CACHING=1 meteor build --server-only --directory "$BUILD_DIR" -# Wait for rc1 container to be running -log_info "Waiting for rc1 container to start..." -timeout=60 -while [ $timeout -gt 0 ] && [ "$INTERRUPTED" = false ]; do - if docker ps --filter "name=$RC1_CONTAINER" --filter "status=running" | grep -q "$RC1_CONTAINER"; then - log_success "rc1 container is running" - break + log_success "Build completed!" + else + log_info "🚀 Using pre-built image: $PREBUILT_IMAGE" + log_info "=====================================" fi - sleep 2 - timeout=$((timeout - 2)) -done -if [ "$INTERRUPTED" = true ]; then - log_info "Container startup interrupted by user" - exit 130 -fi + log_info "🚀 Starting Federation Integration Tests" + log_info "=====================================" -if [ $timeout -le 0 ]; then - log_error "rc1 container failed to start within 60 seconds" - exit 1 -fi + BUILD_PARAM="" -# Wait for both Rocket.Chat and Synapse to be ready -log_info "Waiting for Rocket.Chat and Synapse servers to be ready..." - -# Function to wait for a service to be ready -wait_for_service() { - local url=$1 - local name=$2 - local host=$3 - local elapsed=0 - local ca_cert="${CA_CERT:-$PACKAGE_ROOT/docker-compose/traefik/certs/ca/rootCA.crt}" - - # Derive host/port from URL when not explicitly provided - local host_with_port="${url#*://}" - host_with_port="${host_with_port%%/*}" - if [ -z "$host" ]; then - host="${host_with_port%%:*}" - fi - local port - if [[ "$host_with_port" == *:* ]]; then - port="${host_with_port##*:}" + # Set environment variables for Docker Compose + if [ "$USE_PREBUILT_IMAGE" = true ]; then + export ROCKETCHAT_IMAGE="$PREBUILT_IMAGE" + log_info "Using pre-built image: $PREBUILT_IMAGE" else - if [[ "$url" == https://* ]]; then - port=443 - else - port=80 - fi + export ROCKETCHAT_BUILD_CONTEXT="$BUILD_DIR" + export ROCKETCHAT_DOCKERFILE="$ROCKETCHAT_ROOT/apps/meteor/.docker/Dockerfile.alpine" + BUILD_PARAM="--build" + log_info "Building from local context: $BUILD_DIR" fi - log_info "Checking $name at $url (host $host -> 127.0.0.1:$port)..." + # Start services + if [ "$INCLUDE_ELEMENT" = true ]; then + log_info "Starting all federation services including Element web client..." + else + log_info "Starting federation services (test profile only)..." + fi - while [ $elapsed -lt $MAX_WAIT_TIME ] && [ "$INTERRUPTED" = false ]; do - # Capture curl output and error for debugging - curl_output=$(curl -fsS --cacert "$ca_cert" --resolve "${host}:${port}:127.0.0.1" "$url" 2>&1) - curl_exit_code=$? + docker compose -f "$DOCKER_COMPOSE_FILE" --profile "$COMPOSE_PROFILE" up -d $BUILD_PARAM - if [ $curl_exit_code -eq 0 ]; then - log_success "$name is ready!" - return 0 + # Wait for rc1 container to be running + log_info "Waiting for rc1 container to start..." + timeout=60 + while [ $timeout -gt 0 ] && [ "$INTERRUPTED" = false ]; do + if docker compose -f "$DOCKER_COMPOSE_FILE" ps rc1 --filter "status=running" | grep -q "rc1"; then + log_success "rc1 container is running" + break fi - - log_info "$name not ready yet, waiting... (${elapsed}s/${MAX_WAIT_TIME}s)" - log_info "Curl error: $curl_output" - sleep $CHECK_INTERVAL - elapsed=$((elapsed + CHECK_INTERVAL)) + sleep 2 + timeout=$((timeout - 2)) done if [ "$INTERRUPTED" = true ]; then - log_info "Service check interrupted by user" - return 1 + log_info "Container startup interrupted by user" + exit 130 fi - log_error "$name failed to become ready within ${MAX_WAIT_TIME} seconds" - return 1 -} + if [ $timeout -le 0 ]; then + log_error "rc1 container failed to start within 60 seconds" + exit 1 + fi -# Wait for Rocket.Chat -if ! wait_for_service "https://rc1/api/info" "Rocket.Chat" "rc1"; then - log_error "Last 50 lines of rc1 logs:" - docker logs --tail 50 "$RC1_CONTAINER" 2>&1 | sed 's/^/ /' - exit 1 -fi + # Wait for both Rocket.Chat and Synapse to be ready + log_info "Waiting for Rocket.Chat and Synapse servers to be ready..." + + # Function to wait for a service to be ready + wait_for_service() { + local url=$1 + local name=$2 + local host=$3 + local elapsed=0 + local ca_cert="${CA_CERT:-$PACKAGE_ROOT/docker-compose/traefik/certs/ca/rootCA.crt}" + + # Derive host/port from URL when not explicitly provided + local host_with_port="${url#*://}" + host_with_port="${host_with_port%%/*}" + if [ -z "$host" ]; then + host="${host_with_port%%:*}" + fi + local port + if [[ "$host_with_port" == *:* ]]; then + port="${host_with_port##*:}" + else + if [[ "$url" == https://* ]]; then + port=443 + else + port=80 + fi + fi -# Wait for Synapse -if ! wait_for_service "https://hs1/_matrix/client/versions" "Synapse" "hs1"; then - log_error "Last 50 lines of hs1 logs:" - docker logs --tail 50 "hs1" 2>&1 | sed 's/^/ /' - exit 1 + log_info "Checking $name at $url (host $host -> 127.0.0.1:$port)..." + + while [ $elapsed -lt $MAX_WAIT_TIME ] && [ "$INTERRUPTED" = false ]; do + # Capture curl output and error for debugging + curl_output=$(curl -fsS --cacert "$ca_cert" --resolve "${host}:${port}:127.0.0.1" "$url" 2>&1) + curl_exit_code=$? + + if [ $curl_exit_code -eq 0 ]; then + log_success "$name is ready!" + return 0 + fi + + log_info "$name not ready yet, waiting... (${elapsed}s/${MAX_WAIT_TIME}s)" + log_info "Curl error: $curl_output" + sleep $CHECK_INTERVAL + elapsed=$((elapsed + CHECK_INTERVAL)) + done + + if [ "$INTERRUPTED" = true ]; then + log_info "Service check interrupted by user" + return 1 + fi + + log_error "$name failed to become ready within ${MAX_WAIT_TIME} seconds" + return 1 + } + + # Wait for Rocket.Chat + if ! wait_for_service "https://rc1/api/info" "Rocket.Chat" "rc1"; then + log_error "Last 50 lines of rc1 logs:" + docker compose -f "$DOCKER_COMPOSE_FILE" logs --tail 50 rc1 + exit 1 + fi + + # Wait for Synapse + if ! wait_for_service "https://hs1/_matrix/client/versions" "Synapse" "hs1"; then + log_error "Last 50 lines of hs1 logs:" + docker compose -f "$DOCKER_COMPOSE_FILE" logs --tail 50 hs1 + exit 1 + fi fi # Run the end-to-end tests @@ -327,8 +353,13 @@ if [ "$NO_TEST" = false ]; then log_info "Running end-to-end tests..." cd "$PACKAGE_ROOT" + set +e IS_EE=true NODE_EXTRA_CA_CERTS=$(pwd)/docker-compose/traefik/certs/ca/rootCA.crt yarn test:federation TEST_EXIT_CODE=$? + set -e +elif [ "$LOGS" = true ]; then + docker_logs + exit 0 else log_info "No-test mode: skipping test execution" log_info "Services are ready and running. You can now:" diff --git a/packages/core-services/package.json b/packages/core-services/package.json index 2f64d2f435a3c..333ad1da9e9f6 100644 --- a/packages/core-services/package.json +++ b/packages/core-services/package.json @@ -18,7 +18,7 @@ }, "dependencies": { "@rocket.chat/core-typings": "workspace:^", - "@rocket.chat/federation-sdk": "0.3.8", + "@rocket.chat/federation-sdk": "0.3.9", "@rocket.chat/http-router": "workspace:^", "@rocket.chat/icons": "~0.46.0", "@rocket.chat/media-signaling": "workspace:^", diff --git a/yarn.lock b/yarn.lock index d5299688aead2..1651c307f759f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8266,7 +8266,7 @@ __metadata: "@rocket.chat/apps-engine": "workspace:^" "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/eslint-config": "workspace:^" - "@rocket.chat/federation-sdk": "npm:0.3.8" + "@rocket.chat/federation-sdk": "npm:0.3.9" "@rocket.chat/http-router": "workspace:^" "@rocket.chat/icons": "npm:~0.46.0" "@rocket.chat/jest-presets": "workspace:~" @@ -8472,7 +8472,7 @@ __metadata: "@rocket.chat/ddp-client": "workspace:^" "@rocket.chat/emitter": "npm:^0.31.25" "@rocket.chat/eslint-config": "workspace:^" - "@rocket.chat/federation-sdk": "npm:0.3.8" + "@rocket.chat/federation-sdk": "npm:0.3.9" "@rocket.chat/http-router": "workspace:^" "@rocket.chat/license": "workspace:^" "@rocket.chat/models": "workspace:^" @@ -8498,9 +8498,9 @@ __metadata: languageName: unknown linkType: soft -"@rocket.chat/federation-sdk@npm:0.3.8": - version: 0.3.8 - resolution: "@rocket.chat/federation-sdk@npm:0.3.8" +"@rocket.chat/federation-sdk@npm:0.3.9": + version: 0.3.9 + resolution: "@rocket.chat/federation-sdk@npm:0.3.9" dependencies: "@datastructures-js/priority-queue": "npm:^6.3.5" "@noble/ed25519": "npm:^3.0.0" @@ -8513,7 +8513,7 @@ __metadata: zod: "npm:^3.24.1" peerDependencies: typescript: ~5.9.2 - checksum: 10/c8a3e8d7bdf68798d20d1d42c7bafd354c934e9d80b94796ef09f073585b1de501ba181b059a3c4b40b92660fd8ee04db6535610932bc64c1c2d147f3a24286c + checksum: 10/8bf215c37fa3c181d12731a2b2f5068656b64736c05560a070ad8dde6177e48cd262a2a26d8cc56e8cee7850f25a3bc5dd069537c4a1ee3f638b0d94cb11519c languageName: node linkType: hard @@ -9140,7 +9140,7 @@ __metadata: "@rocket.chat/eslint-config": "workspace:^" "@rocket.chat/favicon": "workspace:^" "@rocket.chat/federation-matrix": "workspace:^" - "@rocket.chat/federation-sdk": "npm:0.3.8" + "@rocket.chat/federation-sdk": "npm:0.3.9" "@rocket.chat/fuselage": "npm:^0.70.0" "@rocket.chat/fuselage-forms": "npm:~0.1.1" "@rocket.chat/fuselage-hooks": "npm:~0.38.1"