From 6ff87a30e0e31d694182c7773bcf61400598071d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Tue, 13 Oct 2026 15:34:53 +0200 Subject: [PATCH 01/21] ci: temp e2e workflow --- .github/workflows/e2e.yml | 150 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 .github/workflows/e2e.yml diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 000000000..fe278b18d --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,150 @@ +name: e2e + +# Module Federation v2 e2e on a real Android emulator, mirroring the local flow: +# start both dev servers, build the app, run the tests on a booted device. +# +# `pull_request_target` runs workflow code from the base revision, so a +# contributor cannot change what executes; the PR head is checked out +# explicitly below. The job reads no secrets (every test is deterministic) and +# the token is pinned to `contents: read`. +on: + pull_request_target: + types: [opened, reopened, synchronize, labeled, unlabeled] + push: + branches: [main] + workflow_dispatch: + +# An emulator run is too expensive to let superseded commits queue up behind it. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + android: + name: e2e (Android) + # Same-repo PRs run unconditionally; a fork PR runs while the `e2e` label + # is on it (the label does not exist in repo settings yet). No workflow-level + # `paths:` filter — a path-filtered required check is never created and the + # merge hangs forever. + if: >- + github.event_name != 'pull_request_target' || + github.event.pull_request.head.repo.full_name == github.repository || + contains(github.event.pull_request.labels.*.name, 'e2e') + runs-on: ubuntu-latest + timeout-minutes: 45 + # Measurement phase: flip to false and mark the check required in branch + # protection in the same change — a required check on a continue-on-error + # job is permanently green, which is worse than no check. + continue-on-error: true + + steps: + # pull_request_target checks out the base by default, which would test + # the wrong code; every other trigger has no PR context and falls + # through to github.sha. + - name: Checkout pull request head + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # tag=v4.2.2 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Setup workspace + uses: ./.github/actions/setup-pnpm-node + with: + # e2e requires Node ^22.22.3 || >=24.8.0; exact pin so the floor + # cannot drift. + node-version: '24.18.0' + + - name: Setup Java + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # tag=v6.0.1 + with: + distribution: temurin + java-version: '17' + + # Required by android-emulator-runner on GitHub-hosted runners. + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Cache Gradle + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # tag=v6.1.0 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('apps/tester-federation-v2/android/gradle/wrapper/gradle-wrapper.properties', 'apps/tester-federation-v2/android/**/*.gradle', 'apps/tester-federation-v2/android/gradle.properties', 'pnpm-lock.yaml') }} + + # The emulator runner action has no caching of its own. + - name: Cache Android emulator + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # tag=v6.1.0 + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ runner.os }}-api-35 + + # The dev servers are the thing under test: this is how the PR's Repack + # change reaches them (workspace dist/ is gitignored). + - name: Build workspace packages + run: pnpm --filter tester-federation-v2... build + + # Servers before Gradle: the cold compile overlaps the build and the + # emulator boot. nohup/disown because the action runs each script line + # in its own shell and these must outlive this step. The /status wait is + # not ceremony: react-native-test-app picks its bundle source once at app + # start and never retries, so an app launched while 8081 is still booting + # shows an alert no later tap dismisses. + - name: Start dev servers + working-directory: apps/tester-federation-v2 + run: | + nohup pnpm start:hostapp > /tmp/hostapp.log 2>&1 & + disown + nohup pnpm start:miniapp > /tmp/miniapp.log 2>&1 & + disown + timeout 180 sh -c 'until [ "$(curl -sf localhost:8081/status)" = "packager-status:running" ] && [ "$(curl -sf localhost:8082/status)" = "packager-status:running" ]; do sleep 2; done' + + - name: Build Android app + working-directory: apps/tester-federation-v2/android + run: ./gradlew :app:assembleDebug -PreactNativeArchitectures=x86_64 + + # The reverses are load-bearing for 8082: the MiniApp remote URL is baked + # into the bundle as http://localhost:8082/..., and the device's localhost + # is the device itself (8081 needs no reverse — the app reaches it via + # 10.0.2.2). Proven locally: with reverses wiped, exactly the Gallery + # test fails. google_apis, never google_apis_playstore — the Play image + # could not launch the app in earlier testing (unconfirmed mechanism; do + # not switch without verifying). + - name: Run e2e on an emulator + uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # tag=v2.38.0 + with: + api-level: 35 + target: google_apis + arch: x86_64 + profile: pixel_7 + working-directory: apps/tester-federation-v2 + script: | + adb reverse tcp:8081 tcp:8081 + adb reverse tcp:8082 tcp:8082 + adb install -r -g android/app/build/outputs/apk/debug/app-debug.apk + pnpm exec e2e run --target android --reporter list,junit + + # !cancelled() keeps evidence from a test that failed then passed on retry. + - name: Upload e2e artifacts + if: ${{ !cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # tag=v7.0.1 + with: + name: e2e-android + path: apps/tester-federation-v2/.e2e + if-no-files-found: warn + retention-days: 7 + + # The nohup'd server logs are otherwise invisible on a red run. + - name: Show dev server logs on failure + if: ${{ failure() }} + run: tail -n 60 /tmp/hostapp.log /tmp/miniapp.log + + # iOS is Phase 1b: needs an xcodebuild lane, a pinned simulator runtime and a + # ~/.agent-device cache, none measured yet. From 4feac606338e49df15973f7ca1982690474f3679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Tue, 13 Oct 2026 15:11:52 +0200 Subject: [PATCH 02/21] feat: repack e2e tests # Conflicts: # apps/tester-federation-v2/package.json # pnpm-lock.yaml --- .github/workflows/e2e.yml | 164 +++-- apps/tester-federation-v2/.gitignore | 3 + .../android/gradle.properties | 2 +- .../configs/rspack.host-app.mts | 1 + .../configs/webpack.host-app.mts | 1 + apps/tester-federation-v2/e2e.config.ts | 18 + apps/tester-federation-v2/e2e/smoke.e2e.ts | 40 ++ apps/tester-federation-v2/package.json | 5 +- apps/tester-federation-v2/src/host/index.js | 6 +- .../src/host/screens/DetailScreen.tsx | 2 +- .../src/host/screens/HomeScreen.tsx | 2 + .../src/host/screens/MiniAppScreen.tsx | 2 +- .../src/mini/screens/GalleryScreen.tsx | 1 + pnpm-lock.yaml | 624 ++++++++++++++++++ 14 files changed, 803 insertions(+), 68 deletions(-) create mode 100644 apps/tester-federation-v2/e2e.config.ts create mode 100644 apps/tester-federation-v2/e2e/smoke.e2e.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index fe278b18d..6d7fdbcb8 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,12 +1,5 @@ name: e2e -# Module Federation v2 e2e on a real Android emulator, mirroring the local flow: -# start both dev servers, build the app, run the tests on a booted device. -# -# `pull_request_target` runs workflow code from the base revision, so a -# contributor cannot change what executes; the PR head is checked out -# explicitly below. The job reads no secrets (every test is deterministic) and -# the token is pinned to `contents: read`. on: pull_request_target: types: [opened, reopened, synchronize, labeled, unlabeled] @@ -14,36 +7,27 @@ on: branches: [main] workflow_dispatch: -# An emulator run is too expensive to let superseded commits queue up behind it. +# one run per PR; a new push cancels the older one concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true +# keep it read-only permissions: contents: read jobs: android: name: e2e (Android) - # Same-repo PRs run unconditionally; a fork PR runs while the `e2e` label - # is on it (the label does not exist in repo settings yet). No workflow-level - # `paths:` filter — a path-filtered required check is never created and the - # merge hangs forever. + # same-repo PRs - always run; forks - run only with the `e2e` label if: >- github.event_name != 'pull_request_target' || github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'e2e') runs-on: ubuntu-latest timeout-minutes: 45 - # Measurement phase: flip to false and mark the check required in branch - # protection in the same change — a required check on a continue-on-error - # job is permanently green, which is worse than no check. - continue-on-error: true steps: - # pull_request_target checks out the base by default, which would test - # the wrong code; every other trigger has no PR context and falls - # through to github.sha. - name: Checkout pull request head uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # tag=v4.2.2 with: @@ -52,8 +36,6 @@ jobs: - name: Setup workspace uses: ./.github/actions/setup-pnpm-node with: - # e2e requires Node ^22.22.3 || >=24.8.0; exact pin so the floor - # cannot drift. node-version: '24.18.0' - name: Setup Java @@ -62,43 +44,20 @@ jobs: distribution: temurin java-version: '17' - # Required by android-emulator-runner on GitHub-hosted runners. - name: Enable KVM run: | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - - name: Cache Gradle - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # tag=v6.1.0 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: gradle-${{ runner.os }}-${{ hashFiles('apps/tester-federation-v2/android/gradle/wrapper/gradle-wrapper.properties', 'apps/tester-federation-v2/android/**/*.gradle', 'apps/tester-federation-v2/android/gradle.properties', 'pnpm-lock.yaml') }} - - # The emulator runner action has no caching of its own. - - name: Cache Android emulator - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # tag=v6.1.0 - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ runner.os }}-api-35 - - # The dev servers are the thing under test: this is how the PR's Repack - # change reaches them (workspace dist/ is gitignored). - name: Build workspace packages run: pnpm --filter tester-federation-v2... build - # Servers before Gradle: the cold compile overlaps the build and the - # emulator boot. nohup/disown because the action runs each script line - # in its own shell and these must outlive this step. The /status wait is - # not ceremony: react-native-test-app picks its bundle source once at app - # start and never retries, so an app launched while 8081 is still booting - # shows an alert no later tap dismisses. + # nohup/disown: the runner kills step processes at the end. /status wait: RNTA picks its bundle source once, never retries - name: Start dev servers working-directory: apps/tester-federation-v2 + env: + E2E: 'true' run: | nohup pnpm start:hostapp > /tmp/hostapp.log 2>&1 & disown @@ -106,17 +65,19 @@ jobs: disown timeout 180 sh -c 'until [ "$(curl -sf localhost:8081/status)" = "packager-status:running" ] && [ "$(curl -sf localhost:8082/status)" = "packager-status:running" ]; do sleep 2; done' + # ask for the bundles now so the servers compile them — they only build on first request + - name: Warm both compilers + working-directory: apps/tester-federation-v2 + run: | + curl -sf --max-time 300 -o /dev/null 'http://localhost:8081/android/index.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.tester.federationV2&modulesOnly=false&runModule=true' + curl -sf --max-time 300 -o /dev/null 'http://localhost:8082/android/index.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.tester.federationV2&modulesOnly=false&runModule=true' + echo 'Both bundles served; compilers are warm.' + - name: Build Android app working-directory: apps/tester-federation-v2/android run: ./gradlew :app:assembleDebug -PreactNativeArchitectures=x86_64 - # The reverses are load-bearing for 8082: the MiniApp remote URL is baked - # into the bundle as http://localhost:8082/..., and the device's localhost - # is the device itself (8081 needs no reverse — the app reaches it via - # 10.0.2.2). Proven locally: with reverses wiped, exactly the Gallery - # test fails. google_apis, never google_apis_playstore — the Play image - # could not launch the app in earlier testing (unconfirmed mechanism; do - # not switch without verifying). + # expose dev servers to the emulator, install the APK, run the tests - name: Run e2e on an emulator uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # tag=v2.38.0 with: @@ -126,25 +87,102 @@ jobs: profile: pixel_7 working-directory: apps/tester-federation-v2 script: | + adb shell settings put global hide_error_dialogs 1 + for i in 1 2 3; do adb shell "uiautomator dump /sdcard/d.xml >/dev/null && grep -q aerr_ /sdcard/d.xml" && { echo 'ANR dialog on screen, clearing it'; adb shell am force-stop com.google.android.apps.nexuslauncher; }; sleep 2; done adb reverse tcp:8081 tcp:8081 adb reverse tcp:8082 tcp:8082 + adb reverse --list + adb reverse --list | grep -q 'tcp:8081' || { echo '::error::adb reverse missing for tcp:8081'; exit 1; } + adb reverse --list | grep -q 'tcp:8082' || { echo '::error::adb reverse missing for tcp:8082'; exit 1; } adb install -r -g android/app/build/outputs/apk/debug/app-debug.apk - pnpm exec e2e run --target android --reporter list,junit + pnpm exec e2e run --target android --reporter list,junit; ec=$?; adb logcat -d -v time > .e2e/logcat.txt 2>&1; exit $ec - # !cancelled() keeps evidence from a test that failed then passed on retry. - name: Upload e2e artifacts if: ${{ !cancelled() }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # tag=v7.0.1 with: name: e2e-android path: apps/tester-federation-v2/.e2e - if-no-files-found: warn + include-hidden-files: true + if-no-files-found: error retention-days: 7 - # The nohup'd server logs are otherwise invisible on a red run. - - name: Show dev server logs on failure - if: ${{ failure() }} - run: tail -n 60 /tmp/hostapp.log /tmp/miniapp.log + - name: Show dev server logs + if: ${{ !cancelled() }} + run: tail -n 80 /tmp/hostapp.log /tmp/miniapp.log + + # dispatch + main only for now — no PR triggers until this lane has greens + ios: + name: e2e (iOS) + if: github.event_name != 'pull_request_target' + runs-on: macos-15 + timeout-minutes: 45 - # iOS is Phase 1b: needs an xcodebuild lane, a pinned simulator runtime and a - # ~/.agent-device cache, none measured yet. + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # tag=v4.2.2 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Setup workspace + uses: ./.github/actions/setup-pnpm-node + with: + node-version: '24.18.0' + + - name: Build workspace packages + run: pnpm --filter tester-federation-v2... build + + # start both dev servers in the background, wait until both answer /status + - name: Start dev servers + working-directory: apps/tester-federation-v2 + env: + E2E: 'true' + run: | + nohup pnpm start:hostapp > /tmp/hostapp.log 2>&1 & + disown + nohup pnpm start:miniapp > /tmp/miniapp.log 2>&1 & + disown + for p in 8081 8082; do curl -sf --retry 90 --retry-delay 2 --retry-all-errors --retry-connrefused -o /dev/null "http://localhost:$p/status"; done + + # ask for the iOS bundles now so the servers compile them — they only build on first request + - name: Warm both compilers + working-directory: apps/tester-federation-v2 + run: | + curl -sf --max-time 300 -o /dev/null 'http://localhost:8081/index.bundle?platform=ios&dev=true&lazy=true&minify=false&modulesOnly=false&runModule=true&app=com.tester.federationV2' + curl -sf --max-time 300 -o /dev/null 'http://localhost:8082/index.bundle?platform=ios&dev=true&lazy=true&minify=false&modulesOnly=false&runModule=true&app=com.tester.federationV2' + echo 'Both bundles served; compilers are warm.' + + # match the runtime to the default Xcode's SDK (16.4 → iOS 18.x), not just newest available + - name: Boot simulator + run: | + RT=$(xcrun simctl list runtimes -j | jq -r '.runtimes | map(select(.isAvailable and (.identifier | startswith("com.apple.CoreSimulator.SimRuntime.iOS-18-")))) | sort_by(.version) | last | .identifier') + echo "using $RT" + [ "$RT" != "null" ] && [ -n "$RT" ] + xcrun simctl create e2e "iPhone 16" "$RT" + xcrun simctl boot e2e + xcrun simctl bootstatus e2e -b + + # pod install generates the Xcode project the workspace points at + - name: Build iOS app + working-directory: apps/tester-federation-v2/ios + run: pod install && xcodebuild -workspace ModuleFederationTesterV2.xcworkspace -scheme ReactTestApp -configuration Debug -destination 'generic/platform=iOS Simulator' -derivedDataPath build CODE_SIGNING_ALLOWED=NO ARCHS=arm64 build + + - name: Run e2e + working-directory: apps/tester-federation-v2 + run: | + xcrun simctl install booted ios/build/Build/Products/Debug-iphonesimulator/*.app + pnpm exec e2e run --target ios --reporter list,junit + + - name: Upload e2e artifacts + if: ${{ !cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # tag=v7.0.1 + with: + name: e2e-ios + path: apps/tester-federation-v2/.e2e + include-hidden-files: true + if-no-files-found: error + retention-days: 7 + + - name: Show dev server logs + if: ${{ !cancelled() }} + run: tail -n 80 /tmp/hostapp.log /tmp/miniapp.log diff --git a/apps/tester-federation-v2/.gitignore b/apps/tester-federation-v2/.gitignore index de13ae3b6..86faa27e0 100644 --- a/apps/tester-federation-v2/.gitignore +++ b/apps/tester-federation-v2/.gitignore @@ -62,3 +62,6 @@ yarn-error.log # MF **/@mf-types + +# e2e +.e2e/ diff --git a/apps/tester-federation-v2/android/gradle.properties b/apps/tester-federation-v2/android/gradle.properties index 3b46be1a2..59c18ddff 100644 --- a/apps/tester-federation-v2/android/gradle.properties +++ b/apps/tester-federation-v2/android/gradle.properties @@ -56,4 +56,4 @@ hermesEnabled=true # Use this property to enable edge-to-edge display support. # This allows your app to draw behind system bars for an immersive UI. # Note: Only works with ReactActivity and should not be used with custom Activity. -edgeToEdgeEnabled=true \ No newline at end of file +edgeToEdgeEnabled=true diff --git a/apps/tester-federation-v2/configs/rspack.host-app.mts b/apps/tester-federation-v2/configs/rspack.host-app.mts index 43fedabe2..3bf821f2f 100644 --- a/apps/tester-federation-v2/configs/rspack.host-app.mts +++ b/apps/tester-federation-v2/configs/rspack.host-app.mts @@ -99,6 +99,7 @@ export default Repack.defineRspackConfig((env) => { __WITH_PRELOAD__: process.env.WITH_PRELOAD === 'true' || process.env.WITH_PRELOAD === '1', + __E2E__: process.env.E2E === 'true', }), ], }; diff --git a/apps/tester-federation-v2/configs/webpack.host-app.mts b/apps/tester-federation-v2/configs/webpack.host-app.mts index 32dc1ac25..0081f868d 100644 --- a/apps/tester-federation-v2/configs/webpack.host-app.mts +++ b/apps/tester-federation-v2/configs/webpack.host-app.mts @@ -95,6 +95,7 @@ export default Repack.defineWebpackConfig((env) => { __WITH_PRELOAD__: process.env.WITH_PRELOAD === 'true' || process.env.WITH_PRELOAD === '1', + __E2E__: process.env.E2E === 'true', }), ], }; diff --git a/apps/tester-federation-v2/e2e.config.ts b/apps/tester-federation-v2/e2e.config.ts new file mode 100644 index 000000000..1848abf52 --- /dev/null +++ b/apps/tester-federation-v2/e2e.config.ts @@ -0,0 +1,18 @@ +import { mobile } from '@e2e-dev/mobile'; +import type { E2EConfig } from 'e2e'; + +export default { + tests: 'e2e/**/*.e2e.ts', + targets: [ + { + name: 'android', + engine: mobile({ platform: 'android' }), + app: { bundleId: 'com.tester.federationV2' }, + }, + { + name: 'ios', + engine: mobile({ platform: 'ios' }), + app: { bundleId: 'com.tester.federationV2' }, + }, + ], +} satisfies E2EConfig; diff --git a/apps/tester-federation-v2/e2e/smoke.e2e.ts b/apps/tester-federation-v2/e2e/smoke.e2e.ts new file mode 100644 index 000000000..2097ed870 --- /dev/null +++ b/apps/tester-federation-v2/e2e/smoke.e2e.ts @@ -0,0 +1,40 @@ +import { test } from '@e2e-dev/mobile'; +import { expect } from 'e2e'; + +test('the host app boots to Home', async ({ app, screen }) => { + await app.open(); + + await expect(screen.getByTestId('home-details-button')).toBeVisible({ + timeout: 60_000, + }); + await expect(screen.getByTestId('home-gallery-button')).toBeVisible(); +}); + +test('navigates from Home to Detail and back', async ({ app, screen }) => { + await app.open(); + await expect(screen.getByTestId('home-details-button')).toBeVisible({ + timeout: 60_000, + }); + + await screen.getByTestId('home-details-button').tap(); + await expect(screen.getByTestId('detail-screen')).toBeVisible(); + + await app.back(); + await expect(screen.getByTestId('home-details-button')).toBeVisible(); + await expect(screen.getByTestId('detail-screen')).toBeHidden(); +}); + +test('Home opens the Gallery MiniApp', { + timeout: 120_000, +}, async ({ app, screen }) => { + await app.open(); + await expect(screen.getByTestId('home-gallery-button')).toBeVisible({ + timeout: 60_000, + }); + + await screen.getByTestId('home-gallery-button').tap(); + await expect(screen.getByTestId('miniapp-gallery')).toBeVisible({ + timeout: 45_000, + }); + await expect(screen.getByText('Picture 1')).toBeVisible(); +}); diff --git a/apps/tester-federation-v2/package.json b/apps/tester-federation-v2/package.json index a05da51ab..ac5b05183 100644 --- a/apps/tester-federation-v2/package.json +++ b/apps/tester-federation-v2/package.json @@ -9,7 +9,8 @@ "pods:update": "(cd ios && bundle install && bundle exec pod update)", "start:hostapp": "react-native webpack-start --config config.host-app.mts", "start:miniapp": "react-native webpack-start --config config.mini-app.mts --port 8082", - "start:expo-host-widget": "react-native webpack-start --config config.expo-host-widget.mts --port 8083" + "start:expo-host-widget": "react-native webpack-start --config config.expo-host-widget.mts --port 8083", + "e2e": "pnpm exec e2e run" }, "dependencies": { "@callstack/repack": "workspace:*", @@ -25,6 +26,7 @@ }, "devDependencies": { "@babel/core": "^7.29.7", + "@e2e-dev/mobile": "0.11.0", "@react-native-community/cli": "catalog:testers", "@react-native-community/cli-platform-android": "catalog:testers", "@react-native-community/cli-platform-ios": "catalog:testers", @@ -36,6 +38,7 @@ "@swc/helpers": "catalog:", "@types/jest": "^29.5.13", "@types/react": "catalog:testers", + "e2e": "0.19.0", "react-native-test-app": "catalog:testers", "terser-webpack-plugin": "catalog:", "typescript": "catalog:", diff --git a/apps/tester-federation-v2/src/host/index.js b/apps/tester-federation-v2/src/host/index.js index 61610d410..2c2e6dae9 100644 --- a/apps/tester-federation-v2/src/host/index.js +++ b/apps/tester-federation-v2/src/host/index.js @@ -1,11 +1,15 @@ import { ScriptManager } from '@callstack/repack/client'; import { preloadRemote } from '@module-federation/runtime'; import AsyncStorage from '@react-native-async-storage/async-storage'; -import { AppRegistry } from 'react-native'; +import { AppRegistry, LogBox } from 'react-native'; import { components } from '../../app.json'; import App from './App'; +if (__E2E__) { + LogBox.ignoreAllLogs(true); +} + if (__WITH_PRELOAD__) { // enable caching of scripts in the AsyncStorage ScriptManager.shared.setStorage(AsyncStorage); diff --git a/apps/tester-federation-v2/src/host/screens/DetailScreen.tsx b/apps/tester-federation-v2/src/host/screens/DetailScreen.tsx index 211051c96..3097b001a 100644 --- a/apps/tester-federation-v2/src/host/screens/DetailScreen.tsx +++ b/apps/tester-federation-v2/src/host/screens/DetailScreen.tsx @@ -2,7 +2,7 @@ import { StyleSheet, Text, View } from 'react-native'; const DetailScreen = () => { return ( - + DetailScreen ); diff --git a/apps/tester-federation-v2/src/host/screens/HomeScreen.tsx b/apps/tester-federation-v2/src/host/screens/HomeScreen.tsx index 766bce133..20dfbb5b6 100644 --- a/apps/tester-federation-v2/src/host/screens/HomeScreen.tsx +++ b/apps/tester-federation-v2/src/host/screens/HomeScreen.tsx @@ -13,6 +13,7 @@ const HomeScreen = () => { Explore the host app and its features navigation.navigate('Detail')} > @@ -29,6 +30,7 @@ const HomeScreen = () => { Mini Apps navigation.navigate('MiniApp')} > diff --git a/apps/tester-federation-v2/src/host/screens/MiniAppScreen.tsx b/apps/tester-federation-v2/src/host/screens/MiniAppScreen.tsx index af41cfd97..47e70358a 100644 --- a/apps/tester-federation-v2/src/host/screens/MiniAppScreen.tsx +++ b/apps/tester-federation-v2/src/host/screens/MiniAppScreen.tsx @@ -16,7 +16,7 @@ class ErrorBoundary extends React.Component< render() { if (this.state.hasError) { return ( - + Failed to load Mini App ); diff --git a/apps/tester-federation-v2/src/mini/screens/GalleryScreen.tsx b/apps/tester-federation-v2/src/mini/screens/GalleryScreen.tsx index d8a0b5023..4d4af8175 100644 --- a/apps/tester-federation-v2/src/mini/screens/GalleryScreen.tsx +++ b/apps/tester-federation-v2/src/mini/screens/GalleryScreen.tsx @@ -41,6 +41,7 @@ const Row = ({ const GalleryScreen = () => { return ( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 77ea2415a..164de7fa7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -563,6 +563,9 @@ importers: '@babel/core': specifier: ^7.29.7 version: 7.29.7 + '@e2e-dev/mobile': + specifier: 0.11.0 + version: 0.11.0(e2e@0.19.0) '@react-native-community/cli': specifier: catalog:testers version: 20.2.0(typescript@7.0.2) @@ -596,6 +599,9 @@ importers: '@types/react': specifier: catalog:testers version: 19.2.17 + e2e: + specifier: 0.19.0 + version: 0.19.0 react-native-test-app: specifier: catalog:testers version: 5.4.5(@expo/config-plugins@57.0.10(typescript@7.0.2))(@react-native-community/cli-types@20.2.0)(memfs@4.64.0)(metro@0.84.6)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.2.0(typescript@7.0.2))(@types/react@19.2.17)(react@19.2.8))(react@19.2.8) @@ -1137,6 +1143,10 @@ importers: packages: + '@ai-sdk/provider@4.0.21': + resolution: {integrity: sha512-UpbC9C1oht8dhfKPbVXSLRZS3DI8uk8n5v2uMjBPNIYGsC2kL045ywq7D8Hh9KgnH9rP5W/GxQdYtWScRouqlA==} + engines: {node: '>=22'} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -2081,6 +2091,16 @@ packages: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} + '@e2e-dev/mobile@0.11.0': + resolution: {integrity: sha512-wvZ/VVUmNs65qnXqgh49huCw1Rgl1+3sngOEd4umLaNtPAcQBTzbX9Fz5zdXR5tQuN3HPejVez7IOSHYE3/dyQ==} + engines: {node: ^22.22.3 || >=24.8.0} + peerDependencies: + ai: ^7.0.0 + e2e: '>=0.15.0 <1' + peerDependenciesMeta: + ai: + optional: true + '@egjs/hammerjs@2.0.17': resolution: {integrity: sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==} engines: {node: '>=0.8.0'} @@ -2882,6 +2902,14 @@ packages: '@types/react': '>=16' react: '>=16' + '@modelcontextprotocol/core@2.3.0': + resolution: {integrity: sha512-09BHFaNVBe5pB2AhzCfLj0cqmsKLERvZQ4FblzP90UMGLaXVqdZPByFjURwsUYCmxonUW2yAwgTTvle+8O2/8w==} + engines: {node: '>=20'} + + '@modelcontextprotocol/server@2.3.0': + resolution: {integrity: sha512-+6b0LdsQLmHsvS7J6sQJ4weTpzUn61fU1JKS+321/+IyDYoHW5CR2Tgqcs6GgAAQvlesCo6yX3xttgmx+XGtJw==} + engines: {node: '>=20'} + '@modern-js/node-bundle-require@2.67.6': resolution: {integrity: sha512-rRiDQkrm3kgn0E/GNrcvqo4c71PaUs2R8Xmpv6GUKbEr6lz7VNgfZmAhdAQPtNfRfiBe+1sFLzEcwfEdDo/dTA==} @@ -3291,6 +3319,257 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@oxc-parser/binding-android-arm-eabi@0.152.0': + resolution: {integrity: sha512-393VtirINkae4XHBTkq/yQxhOwEr4V+r0et37P2fp+sYZSdYgZTRDPo5Um8vBh0QjMZ+RmaMy4PP0qwr4cTMwA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.152.0': + resolution: {integrity: sha512-iYFcExATJNZ0rZj0o02THbD1Nc9jAW0nVgIQPee6ejbSASpbZXlIMw8jxtpzaG14JQTWwGSZ6U5dEtRJXE6V3Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.152.0': + resolution: {integrity: sha512-fnU+DfUzi5/rFy0LzmipYRz06PAUUkOS8kCJQ3Eg6oqlunC1XZXoffFqxmdL51RmcbYtUG3P8DVG1U5fckoa2A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.152.0': + resolution: {integrity: sha512-Y3U2okryWTs7hDld6UhjJwLNf8/do+x4g4C+1U5SiGwrTLwbO83WJYJA8+l9iFSilWBgnfpd/+U76tfeNxUHzA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.152.0': + resolution: {integrity: sha512-XkhkRrrPffEw+ZLv/d/5pEni0buxinm/E/N6fLbJCunD1DBbeCTtVP23ESV+4CWCWdSz397asks1ViGslpmWiQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.152.0': + resolution: {integrity: sha512-MLzsRRbdfmdzo6JEhbC1yKU9ItfvM8tEf18HM32jCiNYZEz6J3bRBRl3cTkKR0vBdrk9ZVlcARXtwHccs+epDA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.152.0': + resolution: {integrity: sha512-HHerTEJz7Iy10Ji6BGsk4gqG6PU0UzFuBu91jIEOX89FnrI5ECWSEr7BLpkYQhp7U0QFtMnBhNfQKI0hYZK78w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.152.0': + resolution: {integrity: sha512-9Bb8GyzZ2uSLzRDZ27usaKokr07mxiBe2dDF5b6bd2janmaJTT1gL51UfP7AU30yFiXrJvcRyFKREAgKwZYs0w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-arm64-musl@0.152.0': + resolution: {integrity: sha512-om7iDMUroI74A/lfRRWY9/Ev3VWrVsrUZ4tyvyh8raBey/sdcfyAD7tYWc+iykAiotnmftyKTpUKFr1QnaBrAQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-ppc64-gnu@0.152.0': + resolution: {integrity: sha512-Lf1OL2lMTqwDMFC6jnRb30B+WYHKV0XT+bPHcFM6xHtNVwxpobiTzUIvKH6a0LWpvfwyru8Yu6zCGHqqFHH+gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-gnu@0.152.0': + resolution: {integrity: sha512-BYWSMQK+YJ9ZILStPcmmiSVq8KEG9Qdq/c4BHPC3rUEq/fEDCVjUIwdj5ZzjgZ9P9is9PKu0tcAXzq1ZXg4urQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.152.0': + resolution: {integrity: sha512-d2LqHNNNqdH185sRK5dPKWZu201JrNu9/CRFuVEuT+ht7n/QaVblEMWT9MALQPrDWYvhG10P+NSKJFquDWZ09w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.152.0': + resolution: {integrity: sha512-8gBeHt5rA/8+pKhHl+zLggfa3L/V/L2at96p7ty6pBE7id6lFO19nFUc15u2PcjC4OYeYK465MKy1fhhbpFIGw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-gnu@0.152.0': + resolution: {integrity: sha512-ltgrSF6L+yPTVsIdaAyqKq3zTqzj/1VlNbxWZuxAe8flR3sWdyNsKvdXukoO8/ucJbLickhmieRBge+o/e8Ugw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-musl@0.152.0': + resolution: {integrity: sha512-LcMp6BvrBnewSfdkLHoSqidhoAq4syKOTBV5TYHWd+2VnGkfD/nPRRVIzTBt/Lkbt7pfOFDGtkiTntFS0p6sQQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-openharmony-arm64@0.152.0': + resolution: {integrity: sha512-jbhoMCslVxzRCRNQ8TSHxjYdSKRvk8O6ciLVDkahJvmyHDzBqba/bMQHM6ULKj/rgHNNbSzlIiakdx8SEfkJzA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-win32-arm64-msvc@0.152.0': + resolution: {integrity: sha512-JZYVlhv36mr/nertnRB7vchJECOBb1Lz6lltalILsB+o9M/is+H6BEDRLQKdqA4JKs6NjJszo46+iM5sfwj6jQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.152.0': + resolution: {integrity: sha512-1X9oganvVsunRH0lH10UU05N+H9atpX6WsOybByUjwnfcd3h/0tYwvuv16XuHglrIUOnxV2TodbLVEwwafd64g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.152.0': + resolution: {integrity: sha512-sw0M80/dn9rcpu+Rqqph/C+MjvDDce6MlXmFjsNQtPEymha0eJy3ml6+foSUTqFfpDIDyHZZ4eoJ43IoMe8bJw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/runtime@0.152.0': + resolution: {integrity: sha512-fsq/7JVJo8YAmMPnt3Z7akMdMoCokRND5jjawa5DxuKJBEaI0Dl3ZqEXFlLabKoZhlApIexZ7aycXFcjcTcWvw==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@oxc-project/types@0.152.0': + resolution: {integrity: sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==} + + '@oxc-transform/binding-android-arm-eabi@0.152.0': + resolution: {integrity: sha512-GBVYZdUggANq7ndUuE9HBi1jO6d2+oud3cevfQkpqrV7JsDNwQU5UCGa8rZCvzjIvyn6ikQuJu+sj4RvLyN6Uw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-transform/binding-android-arm64@0.152.0': + resolution: {integrity: sha512-pqeCLGUnozzyJE9Ed9js2w+V1lHQ43qPImMfgTh7it+a3LJqUjAt+OesJ6pqkPycRFV6b0pUWZm6EkpM8mk7ag==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-transform/binding-darwin-arm64@0.152.0': + resolution: {integrity: sha512-0d68wdepj2bjnDijPdvC/3MnrZcs8hoCp19qnGX2tyxnSIPevgqa9RX5i31ex5JMczht/VGp4aKeV1p12dwzQQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-transform/binding-darwin-x64@0.152.0': + resolution: {integrity: sha512-jFTRTyZzYhqSrxkaI9hTwClPTXtdqRyPSXUFGLJmStYS+b6qnhbI1ottivWdAg0HJXytyg8YeqgUJWSu2lU/jA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-transform/binding-freebsd-x64@0.152.0': + resolution: {integrity: sha512-L4Y8IfrcaxjK9kK3ddRAyZPa6nzJHn6fgkVIKoBlP53vmCobPL1mtuIdXQThD0VmV6e4aSAJg2An6qkdjeZC8Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-transform/binding-linux-arm-gnueabihf@0.152.0': + resolution: {integrity: sha512-GrohqmIKEK/YXCzAGP8XGQrPVBdUHPpvqdhvw+VutpWZofxDOq39Zed+Gqlea2mycXo+/UNeq1194eaAYkqKQQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-transform/binding-linux-arm-musleabihf@0.152.0': + resolution: {integrity: sha512-wUI+JoZEs4+GntxjK2YOKtQDG822wv+aGYm1OUZ2+NAgi8ghOTUP3RH/GX8Vq0qJ2Yw3OwKLUIZzufXklQ7pCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-transform/binding-linux-arm64-gnu@0.152.0': + resolution: {integrity: sha512-EcY52e0BOfynOA/brCInvQO3ZSukbAEc+BsbpRoRdJ4qV10NK0aS/FheBvkj/V3cFidee6QlrUpqgpN4WKV+Sg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-transform/binding-linux-arm64-musl@0.152.0': + resolution: {integrity: sha512-L4OGw0UuuyYKAPnUvvW+tvq6nS/tBoolVuPekNJqYnDYfe5Nh1619ImfhP0o0Wz+SG4OQW35ByRWjVf/oK3x0w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-transform/binding-linux-ppc64-gnu@0.152.0': + resolution: {integrity: sha512-5K9qNjNKlyhBdGkkIhYFonO/H3Yc37miy5cXefTB71aPOv9jv6HQrDuj6AJcBkggbmkeyEJhlFamUbGMsMGBiA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-transform/binding-linux-riscv64-gnu@0.152.0': + resolution: {integrity: sha512-WlGvnZhmRItWqjsGd2OBKPan75FbqpF4HPuGL7P6f7Oz1VHm6IatnwCK2MI16QXH7BEs6GRohZHpmgThc0V/uw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-transform/binding-linux-riscv64-musl@0.152.0': + resolution: {integrity: sha512-TWZX7QGkU4LF9NS+HBUVWsbQXWDuWt58r+L/31gSJ/iHCJw/3jM2yD408IRVjXYX5jFgmsJbxrUlW3xlVeAL3Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-transform/binding-linux-s390x-gnu@0.152.0': + resolution: {integrity: sha512-VIP7463d0nIAGFv2VXb4mRf20qrpZg0kNHBw8E3fLuHj0bt4HVHDgsqAgeFWOonWwSwsM415TFjNmHhlaStCLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-transform/binding-linux-x64-gnu@0.152.0': + resolution: {integrity: sha512-hwnHMVCnGIoS/eMh7T836mJpWim2iCSWGlCS+aZ+SjhEEUvSSUE54ljua7bk25vaj4L84cFnruTCa7HqyaQ2YQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-transform/binding-linux-x64-musl@0.152.0': + resolution: {integrity: sha512-5NPItMgM9GfIW0SbpOXvEFIijin5VcNEParmBEm9j+RXr8Ko7pZ3a1ydk/Sy+EOvaSTkXMY6TkVyLfczwdudSQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-transform/binding-openharmony-arm64@0.152.0': + resolution: {integrity: sha512-pNzMq4AhBUV8E/cc41SgYTjzW/AWq1DtEKz1l56Mn1TKfidJwP8sGnIw0EXhD6zKFUlPoqkWfvoe5TiDAYeT+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-transform/binding-win32-arm64-msvc@0.152.0': + resolution: {integrity: sha512-PU6cGmVDYo2HHG9ul6vKj189YJPZrcscdWzQakJD6NU4CqvqISbZLUHwz6DVe0v8CbOe2d24zUdipILaRofZ5g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-transform/binding-win32-ia32-msvc@0.152.0': + resolution: {integrity: sha512-Sdcoky580dPgj9Qp+i4zrCV7NpIYZW6bBrcQvOwyDmNlJQ2iYK/V3Q3FGNHLulrxgXrpzqQwMcLs8KRIg0u0yw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-transform/binding-win32-x64-msvc@0.152.0': + resolution: {integrity: sha512-QnN+/Hsig/vI9zoCNVbbqv0GmZWUGqlPg2ZTEyVw/PvgAXd8pRRfN6CxdKEtJTnaVwJFVYaw/5LR6ZWCVBwDeQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@pinojs/redact@0.4.0': resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} @@ -4901,6 +5180,9 @@ packages: '@vitest/expect@4.1.11': resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} + '@vitest/expect@5.0.3': + resolution: {integrity: sha512-N/VGY122AqBx25OB4EatqlVu8PBayvm9dQzpJ8kVq/pOvqAhXTE5MAmoSMzdB8klDU9wcgSEUliwgMNMtxPa1w==} + '@vitest/mocker@4.1.11': resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} peerDependencies: @@ -4915,6 +5197,9 @@ packages: '@vitest/pretty-format@4.1.11': resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} + '@vitest/pretty-format@5.0.3': + resolution: {integrity: sha512-3hr5eV5z2ff0l2Q1vaKQbB4j3M3tElw8stDokEOTqXJB3Es5VrT8gBRIMY59gGuFp8Y61sWlWFK1aNUH5dHDYw==} + '@vitest/runner@4.1.11': resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} @@ -4924,9 +5209,15 @@ packages: '@vitest/spy@4.1.11': resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} + '@vitest/utils@4.1.11': resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} + '@vitest/utils@5.0.3': + resolution: {integrity: sha512-77fDTZv8eu1CzlIfgPU2yrl5MXSUaUAOvgdZEeVm3X3WIxUubIX2SforqS6176lT32HCF6uBVMYVqfHxw7o6eg==} + '@vscode/sudo-prompt@9.3.2': resolution: {integrity: sha512-gcXoCN00METUNFeQOFJ+C9xUI0DKB+0EGMVg7wbVYRHBw2Eq3fKisDZOkRdOz3kqXRKOENMfShPOmypw1/8nOw==} @@ -5051,6 +5342,16 @@ packages: engines: {node: '>=18.18'} hasBin: true + agent-device@0.21.22: + resolution: {integrity: sha512-ndUh5b+lZ2fRzzY80TTl/KmxR4bh+3FezSH0UGCjPCpN//1trSqtzzNUWunc4woY/vW3DEiPx5xYIwG+fBOXWA==} + engines: {node: '>=22.12'} + hasBin: true + peerDependencies: + ai: ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + ai: + optional: true + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} @@ -5565,6 +5866,10 @@ packages: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -5890,6 +6195,31 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + e2e@0.19.0: + resolution: {integrity: sha512-tTWgVR9tQTeESXkAxCh8H/gR4bGxbSbidw07/GngKT5P5tAPdsJ+bW5bQwIaAL/LUTwIG106V64/oiWLXkS29Q==} + engines: {node: ^22.22.3 || >=24.8.0} + hasBin: true + peerDependencies: + '@ai-sdk/anthropic': ^4.0.0 + '@ai-sdk/google': ^4.0.0 + '@ai-sdk/openai': ^4.0.0 + '@ai-sdk/openai-compatible': ^3.0.0 + '@ai-sdk/xai': ^5.0.0 + ai: ^7.0.0 + peerDependenciesMeta: + '@ai-sdk/anthropic': + optional: true + '@ai-sdk/google': + optional: true + '@ai-sdk/openai': + optional: true + '@ai-sdk/openai-compatible': + optional: true + '@ai-sdk/xai': + optional: true + ai: + optional: true + ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} @@ -6261,6 +6591,9 @@ packages: fast-json-stringify@7.0.1: resolution: {integrity: sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==} + fast-png@8.0.0: + resolution: {integrity: sha512-gCysNasJ8KEMgfdYIKd/wTDo6ENK1PWT0RJO7O+0pgmuHPw2O6tA1WvdxFRJoLf9V8yFYpG0FA1YgI8X97OhJA==} + fast-querystring@1.1.2: resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} @@ -6322,6 +6655,9 @@ packages: fetch-nodeshim@0.4.10: resolution: {integrity: sha512-m6I8ALe4L4XpdETy7MJZWs6L1IVMbjs99bwbpIKphxX+0CTns4IKDWJY0LWfr4YsFjfg+z1TjzTMU8lKl8rG0w==} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + figures@6.1.0: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} @@ -6511,6 +6847,9 @@ packages: resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} engines: {node: '>=18'} + get-tsconfig@4.14.3: + resolution: {integrity: sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==} + getenv@2.0.0: resolution: {integrity: sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==} engines: {node: '>=6'} @@ -6798,6 +7137,9 @@ packages: invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + iobuffer@6.0.1: + resolution: {integrity: sha512-SZWYkWNfjIXIBYSDpXDYIgshqtbOPsi4lviawAEceR1Kqk+sHDlcQjWrzNQsii80AyBY0q5c8HCTNjqo74ul+Q==} + ipaddr.js@2.4.0: resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==} engines: {node: '>= 10'} @@ -7156,6 +7498,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stream-stringify@3.0.1: resolution: {integrity: sha512-vuxs3G1ocFDiAQ/SX0okcZbtqXwgj1g71qE9+vrjJ2EkjKQlEFDAcUNRxRU8O+GekV4v5cM2qXP0Wyt/EMDBiQ==} @@ -8102,6 +8447,14 @@ packages: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} + oxc-parser@0.152.0: + resolution: {integrity: sha512-ZztsZgaCWmPeKYMdoTT5e0oV/oxD1NeLWNR3kAtr/G707cUPYaaozRlVDHLCGnszHJRszN9mJwmPNpqgwloC0A==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-transform@0.152.0: + resolution: {integrity: sha512-9rs8qImolzWswXGlhmye9Yz9Mce9hOe4Pivb7rSEhF/6VKn1Q122BUe9Mr1O9xQAoBX+SrF6xXfdgxKHeQf+uQ==} + engines: {node: ^20.19.0 || >=22.12.0} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -8260,6 +8613,10 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} + pixelmatch@8.0.0: + resolution: {integrity: sha512-cIqOLfh5k52us715qf3B3+kTOxZv+WDv77HN7xokLScYQGYQHi4m/ynoDIPpvF2LvCyUJGyvzY64i0QCXtzu1g==} + hasBin: true + pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -8280,6 +8637,10 @@ packages: resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} engines: {node: '>=4.0.0'} + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + portfinder@1.0.38: resolution: {integrity: sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==} engines: {node: '>= 10.12'} @@ -8781,6 +9142,9 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve-workspace-root@2.0.1: resolution: {integrity: sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w==} @@ -9330,6 +9694,10 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tinyrainbow@3.2.0: + resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} + engines: {node: '>=14.0.0'} + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -9866,11 +10234,18 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.6.1: + resolution: {integrity: sha512-341aRWQsve0rvronKNTqZpjmzdbUDlFuzHaI/XLg/Ej82qffDJRRfBTCuv7+9q/rMjB6LSLyEBnW4InJeMtt/Q==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} snapshots: + '@ai-sdk/provider@4.0.21': + dependencies: + json-schema: 0.4.0 + '@alloc/quick-lru@5.2.0': {} '@ast-grep/napi-darwin-arm64@0.37.0': @@ -11019,6 +11394,13 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} + '@e2e-dev/mobile@0.11.0(e2e@0.19.0)': + dependencies: + agent-device: 0.21.22 + e2e: 0.19.0 + pngjs: 7.0.0 + zod: 4.6.1 + '@egjs/hammerjs@2.0.17': dependencies: '@types/hammerjs': 2.0.46 @@ -12157,6 +12539,15 @@ snapshots: '@types/react': 19.3.0 react: 19.2.8 + '@modelcontextprotocol/core@2.3.0': + dependencies: + zod: 4.6.1 + + '@modelcontextprotocol/server@2.3.0': + dependencies: + '@modelcontextprotocol/core': 2.3.0 + zod: 4.6.1 + '@modern-js/node-bundle-require@2.67.6': dependencies: '@modern-js/utils': 2.67.6 @@ -12860,6 +13251,124 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@oxc-parser/binding-android-arm-eabi@0.152.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.152.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.152.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.152.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.152.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.152.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.152.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.152.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.152.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.152.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.152.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.152.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.152.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.152.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.152.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.152.0': + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.152.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.152.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.152.0': + optional: true + + '@oxc-project/runtime@0.152.0': {} + + '@oxc-project/types@0.152.0': {} + + '@oxc-transform/binding-android-arm-eabi@0.152.0': + optional: true + + '@oxc-transform/binding-android-arm64@0.152.0': + optional: true + + '@oxc-transform/binding-darwin-arm64@0.152.0': + optional: true + + '@oxc-transform/binding-darwin-x64@0.152.0': + optional: true + + '@oxc-transform/binding-freebsd-x64@0.152.0': + optional: true + + '@oxc-transform/binding-linux-arm-gnueabihf@0.152.0': + optional: true + + '@oxc-transform/binding-linux-arm-musleabihf@0.152.0': + optional: true + + '@oxc-transform/binding-linux-arm64-gnu@0.152.0': + optional: true + + '@oxc-transform/binding-linux-arm64-musl@0.152.0': + optional: true + + '@oxc-transform/binding-linux-ppc64-gnu@0.152.0': + optional: true + + '@oxc-transform/binding-linux-riscv64-gnu@0.152.0': + optional: true + + '@oxc-transform/binding-linux-riscv64-musl@0.152.0': + optional: true + + '@oxc-transform/binding-linux-s390x-gnu@0.152.0': + optional: true + + '@oxc-transform/binding-linux-x64-gnu@0.152.0': + optional: true + + '@oxc-transform/binding-linux-x64-musl@0.152.0': + optional: true + + '@oxc-transform/binding-openharmony-arm64@0.152.0': + optional: true + + '@oxc-transform/binding-win32-arm64-msvc@0.152.0': + optional: true + + '@oxc-transform/binding-win32-ia32-msvc@0.152.0': + optional: true + + '@oxc-transform/binding-win32-x64-msvc@0.152.0': + optional: true + '@pinojs/redact@0.4.0': {} '@pnpm/deps.graph-sequencer@1100.0.1': {} @@ -14903,6 +15412,15 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 + '@vitest/expect@5.0.3': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 5.0.3 + '@vitest/utils': 5.0.3 + chai: 6.2.2 + tinyrainbow: 3.2.0 + '@vitest/mocker@4.1.11(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.51.2)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.11 @@ -14923,6 +15441,10 @@ snapshots: dependencies: tinyrainbow: 3.1.0 + '@vitest/pretty-format@5.0.3': + dependencies: + tinyrainbow: 3.2.0 + '@vitest/runner@4.1.11': dependencies: '@vitest/utils': 4.1.11 @@ -14937,12 +15459,20 @@ snapshots: '@vitest/spy@4.1.11': {} + '@vitest/spy@5.0.3': {} + '@vitest/utils@4.1.11': dependencies: '@vitest/pretty-format': 4.1.11 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@vitest/utils@5.0.3': + dependencies: + '@vitest/pretty-format': 5.0.3 + convert-source-map: 2.0.0 + tinyrainbow: 3.2.0 + '@vscode/sudo-prompt@9.3.2': {} '@webassemblyjs/ast@1.14.1': @@ -15077,6 +15607,8 @@ snapshots: agent-cli-detector@0.1.7: {} + agent-device@0.21.22: {} + ajv-formats@2.1.1: dependencies: ajv: 8.20.0 @@ -15714,6 +16246,8 @@ snapshots: commander@12.1.0: {} + commander@15.0.0: {} + commander@2.20.3: {} commander@4.1.1: {} @@ -15995,6 +16529,23 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + e2e@0.19.0: + dependencies: + '@ai-sdk/provider': 4.0.21 + '@clack/prompts': 1.8.1 + '@jridgewell/remapping': 2.3.5 + '@modelcontextprotocol/server': 2.3.0 + '@oxc-project/runtime': 0.152.0 + '@vitest/expect': 5.0.3 + commander: 15.0.0 + fast-png: 8.0.0 + get-tsconfig: 4.14.3 + oxc-parser: 0.152.0 + oxc-transform: 0.152.0 + picocolors: 1.1.1 + pixelmatch: 8.0.0 + zod: 4.6.1 + ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer: 5.2.1 @@ -16628,6 +17179,11 @@ snapshots: json-schema-ref-resolver: 3.0.0 rfdc: 1.4.1 + fast-png@8.0.0: + dependencies: + fflate: 0.8.3 + iobuffer: 6.0.1 + fast-querystring@1.1.2: dependencies: fast-decode-uri-component: 1.0.1 @@ -16702,6 +17258,8 @@ snapshots: fetch-nodeshim@0.4.10: {} + fflate@0.8.3: {} + figures@6.1.0: dependencies: is-unicode-supported: 2.1.0 @@ -16884,6 +17442,10 @@ snapshots: '@sec-ant/readable-stream': 0.4.1 is-stream: 4.0.1 + get-tsconfig@4.14.3: + dependencies: + resolve-pkg-maps: 1.0.0 + getenv@2.0.0: {} glob-parent@5.1.2: @@ -17279,6 +17841,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + iobuffer@6.0.1: {} + ipaddr.js@2.4.0: {} is-absolute-url@4.0.1: {} @@ -17813,6 +18377,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema@0.4.0: {} + json-stream-stringify@3.0.1: {} json5@2.2.3: {} @@ -19256,6 +19822,52 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 + oxc-parser@0.152.0: + dependencies: + '@oxc-project/types': 0.152.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.152.0 + '@oxc-parser/binding-android-arm64': 0.152.0 + '@oxc-parser/binding-darwin-arm64': 0.152.0 + '@oxc-parser/binding-darwin-x64': 0.152.0 + '@oxc-parser/binding-freebsd-x64': 0.152.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.152.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.152.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.152.0 + '@oxc-parser/binding-linux-arm64-musl': 0.152.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.152.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.152.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.152.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.152.0 + '@oxc-parser/binding-linux-x64-gnu': 0.152.0 + '@oxc-parser/binding-linux-x64-musl': 0.152.0 + '@oxc-parser/binding-openharmony-arm64': 0.152.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.152.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.152.0 + '@oxc-parser/binding-win32-x64-msvc': 0.152.0 + + oxc-transform@0.152.0: + optionalDependencies: + '@oxc-transform/binding-android-arm-eabi': 0.152.0 + '@oxc-transform/binding-android-arm64': 0.152.0 + '@oxc-transform/binding-darwin-arm64': 0.152.0 + '@oxc-transform/binding-darwin-x64': 0.152.0 + '@oxc-transform/binding-freebsd-x64': 0.152.0 + '@oxc-transform/binding-linux-arm-gnueabihf': 0.152.0 + '@oxc-transform/binding-linux-arm-musleabihf': 0.152.0 + '@oxc-transform/binding-linux-arm64-gnu': 0.152.0 + '@oxc-transform/binding-linux-arm64-musl': 0.152.0 + '@oxc-transform/binding-linux-ppc64-gnu': 0.152.0 + '@oxc-transform/binding-linux-riscv64-gnu': 0.152.0 + '@oxc-transform/binding-linux-riscv64-musl': 0.152.0 + '@oxc-transform/binding-linux-s390x-gnu': 0.152.0 + '@oxc-transform/binding-linux-x64-gnu': 0.152.0 + '@oxc-transform/binding-linux-x64-musl': 0.152.0 + '@oxc-transform/binding-openharmony-arm64': 0.152.0 + '@oxc-transform/binding-win32-arm64-msvc': 0.152.0 + '@oxc-transform/binding-win32-ia32-msvc': 0.152.0 + '@oxc-transform/binding-win32-x64-msvc': 0.152.0 + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -19395,6 +20007,10 @@ snapshots: pirates@4.0.7: {} + pixelmatch@8.0.0: + dependencies: + pngjs: 7.0.0 + pkg-dir@4.2.0: dependencies: find-up: 4.1.0 @@ -19415,6 +20031,8 @@ snapshots: pngjs@3.4.0: {} + pngjs@7.0.0: {} + portfinder@1.0.38: dependencies: async: 3.2.6 @@ -20149,6 +20767,8 @@ snapshots: resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve-workspace-root@2.0.1: {} resolve.exports@2.0.3: {} @@ -20745,6 +21365,8 @@ snapshots: tinyrainbow@3.1.0: {} + tinyrainbow@3.2.0: {} + tmpl@1.0.5: {} to-regex-range@5.0.1: @@ -21332,4 +21954,6 @@ snapshots: zod@3.25.76: {} + zod@4.6.1: {} + zwitch@2.0.4: {} From 02544191710538652fc91ef18fca526134af60bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Mon, 19 Oct 2026 13:45:49 +0200 Subject: [PATCH 03/21] fix: CR fixes --- .github/workflows/e2e.yml | 73 ++++++++++++------- .../configs/rspack.host-app.mts | 15 ++-- .../configs/rspack.mini-app.mts | 14 +++- .../configs/webpack.host-app.mts | 15 ++-- .../configs/webpack.mini-app.mts | 14 +++- apps/tester-federation-v2/e2e.config.ts | 6 +- apps/tester-federation-v2/package.json | 8 +- apps/tester-federation-v2/src/globals.d.ts | 8 ++ apps/tester-federation-v2/src/host/index.js | 6 +- .../src/host/screens/MiniAppScreen.tsx | 2 +- apps/tester-federation-v2/tsconfig.json | 1 + pnpm-lock.yaml | 17 ++++- pnpm-workspace.yaml | 2 + 13 files changed, 122 insertions(+), 59 deletions(-) create mode 100644 apps/tester-federation-v2/src/globals.d.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 6d7fdbcb8..8b63b191f 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,37 +1,42 @@ name: e2e on: - pull_request_target: - types: [opened, reopened, synchronize, labeled, unlabeled] + pull_request: + paths: + - 'apps/tester-federation-v2/**' + - 'packages/**' + - 'pnpm-lock.yaml' + - '.github/workflows/e2e.yml' push: branches: [main] + paths: + - 'apps/tester-federation-v2/**' + - 'packages/**' + - 'pnpm-lock.yaml' + - '.github/workflows/e2e.yml' workflow_dispatch: # one run per PR; a new push cancels the older one concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ github.event_name != 'push' }} # keep it read-only permissions: contents: read +env: + E2E_TELEMETRY_DISABLED: '1' + jobs: android: name: e2e (Android) - # same-repo PRs - always run; forks - run only with the `e2e` label - if: >- - github.event_name != 'pull_request_target' || - github.event.pull_request.head.repo.full_name == github.repository || - contains(github.event.pull_request.labels.*.name, 'e2e') runs-on: ubuntu-latest timeout-minutes: 45 steps: - - name: Checkout pull request head + - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # tag=v4.2.2 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Setup workspace uses: ./.github/actions/setup-pnpm-node @@ -56,8 +61,6 @@ jobs: # nohup/disown: the runner kills step processes at the end. /status wait: RNTA picks its bundle source once, never retries - name: Start dev servers working-directory: apps/tester-federation-v2 - env: - E2E: 'true' run: | nohup pnpm start:hostapp > /tmp/hostapp.log 2>&1 & disown @@ -85,6 +88,7 @@ jobs: target: google_apis arch: x86_64 profile: pixel_7 + avd-name: e2e working-directory: apps/tester-federation-v2 script: | adb shell settings put global hide_error_dialogs 1 @@ -94,8 +98,15 @@ jobs: adb reverse --list adb reverse --list | grep -q 'tcp:8081' || { echo '::error::adb reverse missing for tcp:8081'; exit 1; } adb reverse --list | grep -q 'tcp:8082' || { echo '::error::adb reverse missing for tcp:8082'; exit 1; } - adb install -r -g android/app/build/outputs/apk/debug/app-debug.apk - pnpm exec e2e run --target android --reporter list,junit; ec=$?; adb logcat -d -v time > .e2e/logcat.txt 2>&1; exit $ec + adb install -r -g android/app/build/outputs/apk/debug/app-debug.apk || { echo '::error::adb install failed'; exit 1; } + E2E_DEVICE="${ANDROID_SERIAL:-$(adb get-serialno)}" pnpm exec e2e run --target android --reporter list,junit; ec=$?; adb logcat -d -v time > .e2e/logcat.txt 2>&1; exit $ec + + - name: Collect dev server logs + if: ${{ !cancelled() }} + working-directory: apps/tester-federation-v2 + run: | + mkdir -p .e2e + cp /tmp/hostapp.log /tmp/miniapp.log .e2e/ - name: Upload e2e artifacts if: ${{ !cancelled() }} @@ -104,7 +115,7 @@ jobs: name: e2e-android path: apps/tester-federation-v2/.e2e include-hidden-files: true - if-no-files-found: error + if-no-files-found: warn retention-days: 7 - name: Show dev server logs @@ -114,15 +125,13 @@ jobs: # dispatch + main only for now — no PR triggers until this lane has greens ios: name: e2e (iOS) - if: github.event_name != 'pull_request_target' + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' runs-on: macos-15 timeout-minutes: 45 steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # tag=v4.2.2 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Setup workspace uses: ./.github/actions/setup-pnpm-node @@ -135,8 +144,6 @@ jobs: # start both dev servers in the background, wait until both answer /status - name: Start dev servers working-directory: apps/tester-federation-v2 - env: - E2E: 'true' run: | nohup pnpm start:hostapp > /tmp/hostapp.log 2>&1 & disown @@ -152,27 +159,41 @@ jobs: curl -sf --max-time 300 -o /dev/null 'http://localhost:8082/index.bundle?platform=ios&dev=true&lazy=true&minify=false&modulesOnly=false&runModule=true&app=com.tester.federationV2' echo 'Both bundles served; compilers are warm.' - # match the runtime to the default Xcode's SDK (16.4 → iOS 18.x), not just newest available + # SIM_RUNTIME_PREFIX is tied to the runner image: macos-15's default Xcode + # (16.4) targets iOS 18.x. If the image moves, this check fails every run — + # bump the prefix (and SIM_DEVICE) to the new SDK's major. - name: Boot simulator + env: + SIM_RUNTIME_PREFIX: com.apple.CoreSimulator.SimRuntime.iOS-18- + SIM_DEVICE: iPhone 16 run: | - RT=$(xcrun simctl list runtimes -j | jq -r '.runtimes | map(select(.isAvailable and (.identifier | startswith("com.apple.CoreSimulator.SimRuntime.iOS-18-")))) | sort_by(.version) | last | .identifier') + RT=$(xcrun simctl list runtimes -j | jq -r --arg prefix "$SIM_RUNTIME_PREFIX" '.runtimes | map(select(.isAvailable and (.identifier | startswith($prefix)))) | sort_by(.version) | last | .identifier') echo "using $RT" [ "$RT" != "null" ] && [ -n "$RT" ] - xcrun simctl create e2e "iPhone 16" "$RT" + xcrun simctl create e2e "$SIM_DEVICE" "$RT" xcrun simctl boot e2e xcrun simctl bootstatus e2e -b # pod install generates the Xcode project the workspace points at - name: Build iOS app working-directory: apps/tester-federation-v2/ios - run: pod install && xcodebuild -workspace ModuleFederationTesterV2.xcworkspace -scheme ReactTestApp -configuration Debug -destination 'generic/platform=iOS Simulator' -derivedDataPath build CODE_SIGNING_ALLOWED=NO ARCHS=arm64 build + run: bundle install && bundle exec pod install && xcodebuild -workspace ModuleFederationTesterV2.xcworkspace -scheme ReactTestApp -configuration Debug -destination 'generic/platform=iOS Simulator' -derivedDataPath build CODE_SIGNING_ALLOWED=NO ARCHS=arm64 build - name: Run e2e working-directory: apps/tester-federation-v2 + env: + E2E_DEVICE: e2e run: | xcrun simctl install booted ios/build/Build/Products/Debug-iphonesimulator/*.app pnpm exec e2e run --target ios --reporter list,junit + - name: Collect dev server logs + if: ${{ !cancelled() }} + working-directory: apps/tester-federation-v2 + run: | + mkdir -p .e2e + cp /tmp/hostapp.log /tmp/miniapp.log .e2e/ + - name: Upload e2e artifacts if: ${{ !cancelled() }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # tag=v7.0.1 @@ -180,7 +201,7 @@ jobs: name: e2e-ios path: apps/tester-federation-v2/.e2e include-hidden-files: true - if-no-files-found: error + if-no-files-found: warn retention-days: 7 - name: Show dev server logs diff --git a/apps/tester-federation-v2/configs/rspack.host-app.mts b/apps/tester-federation-v2/configs/rspack.host-app.mts index 3bf821f2f..7c0e206bc 100644 --- a/apps/tester-federation-v2/configs/rspack.host-app.mts +++ b/apps/tester-federation-v2/configs/rspack.host-app.mts @@ -2,6 +2,12 @@ import * as Repack from '@callstack/repack'; import rspack from '@rspack/core'; import reactPkg from 'react/package.json' with { type: 'json' }; import reactNativePkg from 'react-native/package.json' with { type: 'json' }; +import safeAreaPkg from 'react-native-safe-area-context/package.json' with { + type: 'json', +}; +import screensPkg from 'react-native-screens/package.json' with { + type: 'json', +}; import pkg from '../package.json' with { type: 'json' }; export default Repack.defineRspackConfig((env) => { @@ -80,14 +86,14 @@ export default Repack.defineRspackConfig((env) => { 'react-native-safe-area-context': { singleton: true, eager: true, - version: pkg.dependencies['react-native-safe-area-context'], - requiredVersion: pkg.dependencies['react-native-safe-area-context'], + version: safeAreaPkg.version, + requiredVersion: safeAreaPkg.version, }, 'react-native-screens': { singleton: true, eager: true, - version: pkg.dependencies['react-native-screens'], - requiredVersion: pkg.dependencies['react-native-screens'], + version: screensPkg.version, + requiredVersion: screensPkg.version, }, }, }), @@ -99,7 +105,6 @@ export default Repack.defineRspackConfig((env) => { __WITH_PRELOAD__: process.env.WITH_PRELOAD === 'true' || process.env.WITH_PRELOAD === '1', - __E2E__: process.env.E2E === 'true', }), ], }; diff --git a/apps/tester-federation-v2/configs/rspack.mini-app.mts b/apps/tester-federation-v2/configs/rspack.mini-app.mts index c8043906c..e1e6270d7 100644 --- a/apps/tester-federation-v2/configs/rspack.mini-app.mts +++ b/apps/tester-federation-v2/configs/rspack.mini-app.mts @@ -2,6 +2,12 @@ import * as Repack from '@callstack/repack'; import rspack from '@rspack/core'; import reactPkg from 'react/package.json' with { type: 'json' }; import reactNativePkg from 'react-native/package.json' with { type: 'json' }; +import safeAreaPkg from 'react-native-safe-area-context/package.json' with { + type: 'json', +}; +import screensPkg from 'react-native-screens/package.json' with { + type: 'json', +}; import pkg from '../package.json' with { type: 'json' }; export default Repack.defineRspackConfig((env) => { @@ -77,14 +83,14 @@ export default Repack.defineRspackConfig((env) => { 'react-native-safe-area-context': { singleton: true, eager: false, - version: pkg.dependencies['react-native-safe-area-context'], - requiredVersion: pkg.dependencies['react-native-safe-area-context'], + version: safeAreaPkg.version, + requiredVersion: safeAreaPkg.version, }, 'react-native-screens': { singleton: true, eager: false, - version: pkg.dependencies['react-native-screens'], - requiredVersion: pkg.dependencies['react-native-screens'], + version: screensPkg.version, + requiredVersion: screensPkg.version, }, }, }), diff --git a/apps/tester-federation-v2/configs/webpack.host-app.mts b/apps/tester-federation-v2/configs/webpack.host-app.mts index 0081f868d..a8329c8d6 100644 --- a/apps/tester-federation-v2/configs/webpack.host-app.mts +++ b/apps/tester-federation-v2/configs/webpack.host-app.mts @@ -2,6 +2,12 @@ import * as Repack from '@callstack/repack'; import reactPkg from 'react/package.json' with { type: 'json' }; import reactNativePkg from 'react-native/package.json' with { type: 'json' }; +import safeAreaPkg from 'react-native-safe-area-context/package.json' with { + type: 'json', +}; +import screensPkg from 'react-native-screens/package.json' with { + type: 'json', +}; import webpack from 'webpack'; import pkg from '../package.json' with { type: 'json' }; @@ -76,14 +82,14 @@ export default Repack.defineWebpackConfig((env) => { 'react-native-safe-area-context': { singleton: true, eager: true, - version: pkg.dependencies['react-native-safe-area-context'], - requiredVersion: pkg.dependencies['react-native-safe-area-context'], + version: safeAreaPkg.version, + requiredVersion: safeAreaPkg.version, }, 'react-native-screens': { singleton: true, eager: true, - version: pkg.dependencies['react-native-screens'], - requiredVersion: pkg.dependencies['react-native-screens'], + version: screensPkg.version, + requiredVersion: screensPkg.version, }, }, }), @@ -95,7 +101,6 @@ export default Repack.defineWebpackConfig((env) => { __WITH_PRELOAD__: process.env.WITH_PRELOAD === 'true' || process.env.WITH_PRELOAD === '1', - __E2E__: process.env.E2E === 'true', }), ], }; diff --git a/apps/tester-federation-v2/configs/webpack.mini-app.mts b/apps/tester-federation-v2/configs/webpack.mini-app.mts index fedc3334f..331e6e855 100644 --- a/apps/tester-federation-v2/configs/webpack.mini-app.mts +++ b/apps/tester-federation-v2/configs/webpack.mini-app.mts @@ -1,6 +1,12 @@ import * as Repack from '@callstack/repack'; import reactPkg from 'react/package.json' with { type: 'json' }; import reactNativePkg from 'react-native/package.json' with { type: 'json' }; +import safeAreaPkg from 'react-native-safe-area-context/package.json' with { + type: 'json', +}; +import screensPkg from 'react-native-screens/package.json' with { + type: 'json', +}; import webpack from 'webpack'; import pkg from '../package.json' with { type: 'json' }; @@ -75,14 +81,14 @@ export default Repack.defineWebpackConfig((env) => { 'react-native-safe-area-context': { singleton: true, eager: false, - version: pkg.dependencies['react-native-safe-area-context'], - requiredVersion: pkg.dependencies['react-native-safe-area-context'], + version: safeAreaPkg.version, + requiredVersion: safeAreaPkg.version, }, 'react-native-screens': { singleton: true, eager: false, - version: pkg.dependencies['react-native-screens'], - requiredVersion: pkg.dependencies['react-native-screens'], + version: screensPkg.version, + requiredVersion: screensPkg.version, }, }, }), diff --git a/apps/tester-federation-v2/e2e.config.ts b/apps/tester-federation-v2/e2e.config.ts index 1848abf52..07b4f6ff2 100644 --- a/apps/tester-federation-v2/e2e.config.ts +++ b/apps/tester-federation-v2/e2e.config.ts @@ -3,15 +3,17 @@ import type { E2EConfig } from 'e2e'; export default { tests: 'e2e/**/*.e2e.ts', + retries: 0, + workers: 1, targets: [ { name: 'android', - engine: mobile({ platform: 'android' }), + engine: mobile({ platform: 'android', device: process.env.E2E_DEVICE }), app: { bundleId: 'com.tester.federationV2' }, }, { name: 'ios', - engine: mobile({ platform: 'ios' }), + engine: mobile({ platform: 'ios', device: process.env.E2E_DEVICE }), app: { bundleId: 'com.tester.federationV2' }, }, ], diff --git a/apps/tester-federation-v2/package.json b/apps/tester-federation-v2/package.json index ac5b05183..86a581a4b 100644 --- a/apps/tester-federation-v2/package.json +++ b/apps/tester-federation-v2/package.json @@ -10,7 +10,8 @@ "start:hostapp": "react-native webpack-start --config config.host-app.mts", "start:miniapp": "react-native webpack-start --config config.mini-app.mts --port 8082", "start:expo-host-widget": "react-native webpack-start --config config.expo-host-widget.mts --port 8083", - "e2e": "pnpm exec e2e run" + "e2e": "pnpm exec e2e run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@callstack/repack": "workspace:*", @@ -26,7 +27,7 @@ }, "devDependencies": { "@babel/core": "^7.29.7", - "@e2e-dev/mobile": "0.11.0", + "@e2e-dev/mobile": "catalog:testers", "@react-native-community/cli": "catalog:testers", "@react-native-community/cli-platform-android": "catalog:testers", "@react-native-community/cli-platform-ios": "catalog:testers", @@ -37,8 +38,9 @@ "@rspack/core": "catalog:", "@swc/helpers": "catalog:", "@types/jest": "^29.5.13", + "@types/node": "catalog:", "@types/react": "catalog:testers", - "e2e": "0.19.0", + "e2e": "catalog:testers", "react-native-test-app": "catalog:testers", "terser-webpack-plugin": "catalog:", "typescript": "catalog:", diff --git a/apps/tester-federation-v2/src/globals.d.ts b/apps/tester-federation-v2/src/globals.d.ts new file mode 100644 index 000000000..f51151309 --- /dev/null +++ b/apps/tester-federation-v2/src/globals.d.ts @@ -0,0 +1,8 @@ +declare const __WITH_PRELOAD__: boolean; + +declare module 'MiniApp/MiniAppNavigator' { + import type { ComponentType } from 'react'; + + const MiniAppNavigator: ComponentType; + export default MiniAppNavigator; +} diff --git a/apps/tester-federation-v2/src/host/index.js b/apps/tester-federation-v2/src/host/index.js index 2c2e6dae9..61610d410 100644 --- a/apps/tester-federation-v2/src/host/index.js +++ b/apps/tester-federation-v2/src/host/index.js @@ -1,15 +1,11 @@ import { ScriptManager } from '@callstack/repack/client'; import { preloadRemote } from '@module-federation/runtime'; import AsyncStorage from '@react-native-async-storage/async-storage'; -import { AppRegistry, LogBox } from 'react-native'; +import { AppRegistry } from 'react-native'; import { components } from '../../app.json'; import App from './App'; -if (__E2E__) { - LogBox.ignoreAllLogs(true); -} - if (__WITH_PRELOAD__) { // enable caching of scripts in the AsyncStorage ScriptManager.shared.setStorage(AsyncStorage); diff --git a/apps/tester-federation-v2/src/host/screens/MiniAppScreen.tsx b/apps/tester-federation-v2/src/host/screens/MiniAppScreen.tsx index 47e70358a..af41cfd97 100644 --- a/apps/tester-federation-v2/src/host/screens/MiniAppScreen.tsx +++ b/apps/tester-federation-v2/src/host/screens/MiniAppScreen.tsx @@ -16,7 +16,7 @@ class ErrorBoundary extends React.Component< render() { if (this.state.hasError) { return ( - + Failed to load Mini App ); diff --git a/apps/tester-federation-v2/tsconfig.json b/apps/tester-federation-v2/tsconfig.json index ad74e4bc0..51cf5653c 100644 --- a/apps/tester-federation-v2/tsconfig.json +++ b/apps/tester-federation-v2/tsconfig.json @@ -4,6 +4,7 @@ "exclude": ["**/node_modules", "**/Pods"], "compilerOptions": { "module": "ES2020", // for dynamic imports + "types": ["jest", "node"], "paths": { "*": ["./@mf-types/*"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7762c4fc6..39fdebae6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,6 +68,9 @@ catalogs: specifier: ~2.32.0 version: 2.32.0 testers: + '@e2e-dev/mobile': + specifier: 0.11.0 + version: 0.11.0 '@module-federation/enhanced': specifier: 2.9.2 version: 2.9.2 @@ -98,6 +101,9 @@ catalogs: '@types/react': specifier: ^19.2.0 version: 19.2.17 + e2e: + specifier: 0.19.0 + version: 0.19.0 react-native-reanimated: specifier: 4.5.3 version: 4.5.3 @@ -564,7 +570,7 @@ importers: specifier: ^7.29.7 version: 7.29.7 '@e2e-dev/mobile': - specifier: 0.11.0 + specifier: catalog:testers version: 0.11.0(e2e@0.19.0) '@react-native-community/cli': specifier: catalog:testers @@ -596,11 +602,14 @@ importers: '@types/jest': specifier: ^29.5.13 version: 29.5.14 + '@types/node': + specifier: 'catalog:' + version: 20.19.43 '@types/react': specifier: catalog:testers version: 19.2.17 e2e: - specifier: 0.19.0 + specifier: catalog:testers version: 0.19.0 react-native-test-app: specifier: catalog:testers @@ -15253,7 +15262,7 @@ snapshots: '@types/jsonwebtoken@9.0.10': dependencies: '@types/ms': 2.1.0 - '@types/node': 20.19.43 + '@types/node': 26.1.1 '@types/mdast@4.0.4': dependencies: @@ -15323,7 +15332,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 20.19.43 + '@types/node': 26.1.1 '@types/yargs-parser@21.0.3': {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3696e70c5..7d756336a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -69,6 +69,8 @@ catalogs: "expo-status-bar": ~57.0.1 "react-native-gesture-handler": ~2.32.0 testers: + "e2e": 0.19.0 + "@e2e-dev/mobile": 0.11.0 "@types/react": ^19.2.0 # Held: 5.4.5 is the newest release, and it caps react-native at <0.87. "react-native-test-app": 5.4.5 From da3f15eafd5573c10e762fa8a5e62a8e6ee03720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Mon, 19 Oct 2026 14:14:19 +0200 Subject: [PATCH 04/21] fix: pin ruby --- .github/workflows/e2e.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 8b63b191f..69b58f32c 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -133,6 +133,13 @@ jobs: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # tag=v4.2.2 + - name: Setup Ruby + uses: ruby/setup-ruby@651ff0d62c5c79bed6d53ec518387aae20e72c1a # tag=v1.329.0 + with: + ruby-version: '3.4' + bundler-cache: true + gemfile: apps/tester-federation-v2/Gemfile + - name: Setup workspace uses: ./.github/actions/setup-pnpm-node with: From 53bca6def10486c8b9e62091e5827aa1433b518a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Mon, 19 Oct 2026 15:14:50 +0200 Subject: [PATCH 05/21] fix: warn --- .github/workflows/e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 69b58f32c..04269228c 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -138,7 +138,7 @@ jobs: with: ruby-version: '3.4' bundler-cache: true - gemfile: apps/tester-federation-v2/Gemfile + working-directory: apps/tester-federation-v2 - name: Setup workspace uses: ./.github/actions/setup-pnpm-node From 62fd861da9b3decf6878ee9f75ff00438f4351af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Tue, 20 Oct 2026 12:26:31 +0200 Subject: [PATCH 06/21] fix: cr fixes v2 --- .github/workflows/e2e.yml | 21 ++++++++ .../configs/rspack.host-app.mts | 50 ++----------------- .../configs/rspack.mini-app.mts | 50 ++----------------- .../configs/sharedSingletons.mts | 32 ++++++++++++ .../configs/webpack.host-app.mts | 50 ++----------------- .../configs/webpack.mini-app.mts | 50 ++----------------- 6 files changed, 65 insertions(+), 188 deletions(-) create mode 100644 apps/tester-federation-v2/configs/sharedSingletons.mts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 04269228c..861fa3f8f 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -76,6 +76,15 @@ jobs: curl -sf --max-time 300 -o /dev/null 'http://localhost:8082/android/index.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.tester.federationV2&modulesOnly=false&runModule=true' echo 'Both bundles served; compilers are warm.' + - name: Check shared versions are real semver + run: | + for p in 8081 8082; do + M=$(curl -sf --retry 3 --retry-delay 2 "http://localhost:$p/android/mf-manifest.json") || { echo "::error::port $p: mf-manifest.json not served"; exit 1; } + [ "$(jq '.shared | length' <<<"$M")" -gt 0 ] || { echo "::error::port $p: manifest has no shared entries"; exit 1; } + BAD=$(jq -r '.shared[] | select((.version // "") | test("^[0-9]+\\.[0-9]+\\.[0-9]+") | not) | "\(.name): \(.version)"' <<<"$M") + [ -z "$BAD" ] || { echo "::error::port $p has non-semver shared versions: $BAD"; exit 1; } + done + - name: Build Android app working-directory: apps/tester-federation-v2/android run: ./gradlew :app:assembleDebug -PreactNativeArchitectures=x86_64 @@ -166,6 +175,15 @@ jobs: curl -sf --max-time 300 -o /dev/null 'http://localhost:8082/index.bundle?platform=ios&dev=true&lazy=true&minify=false&modulesOnly=false&runModule=true&app=com.tester.federationV2' echo 'Both bundles served; compilers are warm.' + - name: Check shared versions are real semver + run: | + for p in 8081 8082; do + M=$(curl -sf --retry 3 --retry-delay 2 "http://localhost:$p/ios/mf-manifest.json") || { echo "::error::port $p: mf-manifest.json not served"; exit 1; } + [ "$(jq '.shared | length' <<<"$M")" -gt 0 ] || { echo "::error::port $p: manifest has no shared entries"; exit 1; } + BAD=$(jq -r '.shared[] | select((.version // "") | test("^[0-9]+\\.[0-9]+\\.[0-9]+") | not) | "\(.name): \(.version)"' <<<"$M") + [ -z "$BAD" ] || { echo "::error::port $p has non-semver shared versions: $BAD"; exit 1; } + done + # SIM_RUNTIME_PREFIX is tied to the runner image: macos-15's default Xcode # (16.4) targets iOS 18.x. If the image moves, this check fails every run — # bump the prefix (and SIM_DEVICE) to the new SDK's major. @@ -200,6 +218,9 @@ jobs: run: | mkdir -p .e2e cp /tmp/hostapp.log /tmp/miniapp.log .e2e/ + xcrun simctl spawn booted log show --style compact --last 30m \ + --predicate 'process == "ReactTestApp"' \ + > .e2e/ios-syslog.txt 2>&1 || true - name: Upload e2e artifacts if: ${{ !cancelled() }} diff --git a/apps/tester-federation-v2/configs/rspack.host-app.mts b/apps/tester-federation-v2/configs/rspack.host-app.mts index 7c0e206bc..138dbf24c 100644 --- a/apps/tester-federation-v2/configs/rspack.host-app.mts +++ b/apps/tester-federation-v2/configs/rspack.host-app.mts @@ -1,14 +1,7 @@ import * as Repack from '@callstack/repack'; import rspack from '@rspack/core'; -import reactPkg from 'react/package.json' with { type: 'json' }; -import reactNativePkg from 'react-native/package.json' with { type: 'json' }; -import safeAreaPkg from 'react-native-safe-area-context/package.json' with { - type: 'json', -}; -import screensPkg from 'react-native-screens/package.json' with { - type: 'json', -}; -import pkg from '../package.json' with { type: 'json' }; + +import { buildSharedSingletons } from './sharedSingletons.mts'; export default Repack.defineRspackConfig((env) => { const { mode, context, platform } = env; @@ -58,44 +51,7 @@ export default Repack.defineRspackConfig((env) => { MiniApp: `MiniApp@http://localhost:8082/${platform}/mf-manifest.json`, }, dts: false, - shared: { - react: { - singleton: true, - eager: true, - version: reactPkg.version, - requiredVersion: reactPkg.version, - }, - 'react-native': { - singleton: true, - eager: true, - version: reactNativePkg.version, - requiredVersion: reactNativePkg.version, - }, - '@react-navigation/native': { - singleton: true, - eager: true, - version: pkg.dependencies['@react-navigation/native'], - requiredVersion: pkg.dependencies['@react-navigation/native'], - }, - '@react-navigation/native-stack': { - singleton: true, - eager: true, - version: pkg.dependencies['@react-navigation/native-stack'], - requiredVersion: pkg.dependencies['@react-navigation/native-stack'], - }, - 'react-native-safe-area-context': { - singleton: true, - eager: true, - version: safeAreaPkg.version, - requiredVersion: safeAreaPkg.version, - }, - 'react-native-screens': { - singleton: true, - eager: true, - version: screensPkg.version, - requiredVersion: screensPkg.version, - }, - }, + shared: buildSharedSingletons(true), }), // silence missing @react-native-masked-view optionally required by @react-navigation/elements new rspack.IgnorePlugin({ diff --git a/apps/tester-federation-v2/configs/rspack.mini-app.mts b/apps/tester-federation-v2/configs/rspack.mini-app.mts index e1e6270d7..716925212 100644 --- a/apps/tester-federation-v2/configs/rspack.mini-app.mts +++ b/apps/tester-federation-v2/configs/rspack.mini-app.mts @@ -1,14 +1,7 @@ import * as Repack from '@callstack/repack'; import rspack from '@rspack/core'; -import reactPkg from 'react/package.json' with { type: 'json' }; -import reactNativePkg from 'react-native/package.json' with { type: 'json' }; -import safeAreaPkg from 'react-native-safe-area-context/package.json' with { - type: 'json', -}; -import screensPkg from 'react-native-screens/package.json' with { - type: 'json', -}; -import pkg from '../package.json' with { type: 'json' }; + +import { buildSharedSingletons } from './sharedSingletons.mts'; export default Repack.defineRspackConfig((env) => { const { mode, context, platform } = env; @@ -55,44 +48,7 @@ export default Repack.defineRspackConfig((env) => { './MiniAppNavigator': './src/mini/navigation/MainNavigator', }, dts: false, - shared: { - react: { - singleton: true, - eager: false, - version: reactPkg.version, - requiredVersion: reactPkg.version, - }, - 'react-native': { - singleton: true, - eager: false, - version: reactNativePkg.version, - requiredVersion: reactNativePkg.version, - }, - '@react-navigation/native': { - singleton: true, - eager: false, - version: pkg.dependencies['@react-navigation/native'], - requiredVersion: pkg.dependencies['@react-navigation/native'], - }, - '@react-navigation/native-stack': { - singleton: true, - eager: false, - version: pkg.dependencies['@react-navigation/native-stack'], - requiredVersion: pkg.dependencies['@react-navigation/native-stack'], - }, - 'react-native-safe-area-context': { - singleton: true, - eager: false, - version: safeAreaPkg.version, - requiredVersion: safeAreaPkg.version, - }, - 'react-native-screens': { - singleton: true, - eager: false, - version: screensPkg.version, - requiredVersion: screensPkg.version, - }, - }, + shared: buildSharedSingletons(false), }), // silence missing @react-native-masked-view optionally required by @react-navigation/elements new rspack.IgnorePlugin({ diff --git a/apps/tester-federation-v2/configs/sharedSingletons.mts b/apps/tester-federation-v2/configs/sharedSingletons.mts new file mode 100644 index 000000000..e434dc450 --- /dev/null +++ b/apps/tester-federation-v2/configs/sharedSingletons.mts @@ -0,0 +1,32 @@ +import nativePkg from '@react-navigation/native/package.json' with { + type: 'json', +}; +import nativeStackPkg from '@react-navigation/native-stack/package.json' with { + type: 'json', +}; +import reactPkg from 'react/package.json' with { type: 'json' }; +import reactNativePkg from 'react-native/package.json' with { type: 'json' }; +import safeAreaPkg from 'react-native-safe-area-context/package.json' with { + type: 'json', +}; +import screensPkg from 'react-native-screens/package.json' with { + type: 'json', +}; + +const SHARED_SINGLETONS: Array<[name: string, version: string]> = [ + ['react', reactPkg.version], + ['react-native', reactNativePkg.version], + ['@react-navigation/native', nativePkg.version], + ['@react-navigation/native-stack', nativeStackPkg.version], + ['react-native-safe-area-context', safeAreaPkg.version], + ['react-native-screens', screensPkg.version], +]; + +export function buildSharedSingletons(eager: boolean) { + return Object.fromEntries( + SHARED_SINGLETONS.map(([name, version]) => [ + name, + { singleton: true, eager, version, requiredVersion: version }, + ]) + ); +} diff --git a/apps/tester-federation-v2/configs/webpack.host-app.mts b/apps/tester-federation-v2/configs/webpack.host-app.mts index a8329c8d6..45ac79aed 100644 --- a/apps/tester-federation-v2/configs/webpack.host-app.mts +++ b/apps/tester-federation-v2/configs/webpack.host-app.mts @@ -1,15 +1,8 @@ // @ts-check import * as Repack from '@callstack/repack'; -import reactPkg from 'react/package.json' with { type: 'json' }; -import reactNativePkg from 'react-native/package.json' with { type: 'json' }; -import safeAreaPkg from 'react-native-safe-area-context/package.json' with { - type: 'json', -}; -import screensPkg from 'react-native-screens/package.json' with { - type: 'json', -}; import webpack from 'webpack'; -import pkg from '../package.json' with { type: 'json' }; + +import { buildSharedSingletons } from './sharedSingletons.mts'; export default Repack.defineWebpackConfig((env) => { const { mode, context, platform } = env; @@ -54,44 +47,7 @@ export default Repack.defineWebpackConfig((env) => { MiniApp: `MiniApp@http://localhost:8082/${platform}/mf-manifest.json`, }, dts: false, - shared: { - react: { - singleton: true, - eager: true, - version: reactPkg.version, - requiredVersion: reactPkg.version, - }, - 'react-native': { - singleton: true, - eager: true, - version: reactNativePkg.version, - requiredVersion: reactNativePkg.version, - }, - '@react-navigation/native': { - singleton: true, - eager: true, - version: pkg.dependencies['@react-navigation/native'], - requiredVersion: pkg.dependencies['@react-navigation/native'], - }, - '@react-navigation/native-stack': { - singleton: true, - eager: true, - version: pkg.dependencies['@react-navigation/native-stack'], - requiredVersion: pkg.dependencies['@react-navigation/native-stack'], - }, - 'react-native-safe-area-context': { - singleton: true, - eager: true, - version: safeAreaPkg.version, - requiredVersion: safeAreaPkg.version, - }, - 'react-native-screens': { - singleton: true, - eager: true, - version: screensPkg.version, - requiredVersion: screensPkg.version, - }, - }, + shared: buildSharedSingletons(true), }), // silence missing @react-native-masked-view optionally required by @react-navigation/elements new webpack.IgnorePlugin({ diff --git a/apps/tester-federation-v2/configs/webpack.mini-app.mts b/apps/tester-federation-v2/configs/webpack.mini-app.mts index 331e6e855..59e14fde9 100644 --- a/apps/tester-federation-v2/configs/webpack.mini-app.mts +++ b/apps/tester-federation-v2/configs/webpack.mini-app.mts @@ -1,14 +1,7 @@ import * as Repack from '@callstack/repack'; -import reactPkg from 'react/package.json' with { type: 'json' }; -import reactNativePkg from 'react-native/package.json' with { type: 'json' }; -import safeAreaPkg from 'react-native-safe-area-context/package.json' with { - type: 'json', -}; -import screensPkg from 'react-native-screens/package.json' with { - type: 'json', -}; import webpack from 'webpack'; -import pkg from '../package.json' with { type: 'json' }; + +import { buildSharedSingletons } from './sharedSingletons.mts'; export default Repack.defineWebpackConfig((env) => { const { mode, context, platform } = env; @@ -53,44 +46,7 @@ export default Repack.defineWebpackConfig((env) => { './MiniAppNavigator': './src/mini/navigation/MainNavigator', }, dts: false, - shared: { - react: { - singleton: true, - eager: false, - version: reactPkg.version, - requiredVersion: reactPkg.version, - }, - 'react-native': { - singleton: true, - eager: false, - version: reactNativePkg.version, - requiredVersion: reactNativePkg.version, - }, - '@react-navigation/native': { - singleton: true, - eager: false, - version: pkg.dependencies['@react-navigation/native'], - requiredVersion: pkg.dependencies['@react-navigation/native'], - }, - '@react-navigation/native-stack': { - singleton: true, - eager: false, - version: pkg.dependencies['@react-navigation/native-stack'], - requiredVersion: pkg.dependencies['@react-navigation/native-stack'], - }, - 'react-native-safe-area-context': { - singleton: true, - eager: false, - version: safeAreaPkg.version, - requiredVersion: safeAreaPkg.version, - }, - 'react-native-screens': { - singleton: true, - eager: false, - version: screensPkg.version, - requiredVersion: screensPkg.version, - }, - }, + shared: buildSharedSingletons(false), }), // silence missing @react-native-masked-view optionally required by @react-navigation/elements new webpack.IgnorePlugin({ From 31c9af0414377abf4bbb3c402e1697efc0300f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Fri, 23 Oct 2026 11:18:38 +0200 Subject: [PATCH 07/21] feat: More features in MF2 test app --- .../configs/rspack.host-app.mts | 4 + .../configs/webpack.host-app.mts | 4 + .../e2e/async-chunk.e2e.ts | 17 ++ apps/tester-federation-v2/e2e/detail.e2e.ts | 16 ++ apps/tester-federation-v2/e2e/gallery.e2e.ts | 19 ++ .../e2e/host-assets.e2e.ts | 12 + .../e2e/miniapp-install.e2e.ts | 35 +++ .../e2e/remote-chunk.e2e.ts | 30 +++ .../e2e/runtime-events.e2e.ts | 47 ++++ apps/tester-federation-v2/e2e/smoke.e2e.ts | 40 ---- .../tester-federation-v2/src/host/FEATURES.ts | 56 +++++ .../src/host/features/async/Async.local.tsx | 21 ++ .../host/features/async/AsyncChunkSection.tsx | 34 +++ .../src/host/features/miniapp/MiniApp.tsx | 21 ++ .../host/features/miniapp/MiniAppSection.tsx | 106 +++++++++ .../src/host/features/remote/Remote.tsx | 21 ++ .../features/remote/RemoteChunkSection.tsx | 120 ++++++++++ .../src/host/features/runtime/events.ts | 146 +++++++++++++ apps/tester-federation-v2/src/host/index.js | 1 + .../src/host/navigation/MainNavigator.tsx | 12 + .../src/host/screens/AsyncChunkScreen.tsx | 28 +++ .../src/host/screens/DetailScreen.tsx | 2 +- .../src/host/screens/HomeScreen.tsx | 205 +++++------------- .../src/host/screens/HostAssetsScreen.tsx | 1 + .../src/host/screens/MiniAppScreen.tsx | 15 +- .../src/host/screens/MiniappInstallScreen.tsx | 36 +++ .../src/host/screens/RemoteChunkScreen.tsx | 48 ++++ .../src/host/screens/RuntimeEventsScreen.tsx | 67 ++++++ apps/tester-federation-v2/src/host/setup.ts | 31 +++ .../src/host/ui/Report.tsx | 25 +++ 30 files changed, 1026 insertions(+), 194 deletions(-) create mode 100644 apps/tester-federation-v2/e2e/async-chunk.e2e.ts create mode 100644 apps/tester-federation-v2/e2e/detail.e2e.ts create mode 100644 apps/tester-federation-v2/e2e/gallery.e2e.ts create mode 100644 apps/tester-federation-v2/e2e/host-assets.e2e.ts create mode 100644 apps/tester-federation-v2/e2e/miniapp-install.e2e.ts create mode 100644 apps/tester-federation-v2/e2e/remote-chunk.e2e.ts create mode 100644 apps/tester-federation-v2/e2e/runtime-events.e2e.ts delete mode 100644 apps/tester-federation-v2/e2e/smoke.e2e.ts create mode 100644 apps/tester-federation-v2/src/host/FEATURES.ts create mode 100644 apps/tester-federation-v2/src/host/features/async/Async.local.tsx create mode 100644 apps/tester-federation-v2/src/host/features/async/AsyncChunkSection.tsx create mode 100644 apps/tester-federation-v2/src/host/features/miniapp/MiniApp.tsx create mode 100644 apps/tester-federation-v2/src/host/features/miniapp/MiniAppSection.tsx create mode 100644 apps/tester-federation-v2/src/host/features/remote/Remote.tsx create mode 100644 apps/tester-federation-v2/src/host/features/remote/RemoteChunkSection.tsx create mode 100644 apps/tester-federation-v2/src/host/features/runtime/events.ts create mode 100644 apps/tester-federation-v2/src/host/screens/AsyncChunkScreen.tsx create mode 100644 apps/tester-federation-v2/src/host/screens/MiniappInstallScreen.tsx create mode 100644 apps/tester-federation-v2/src/host/screens/RemoteChunkScreen.tsx create mode 100644 apps/tester-federation-v2/src/host/screens/RuntimeEventsScreen.tsx create mode 100644 apps/tester-federation-v2/src/host/setup.ts create mode 100644 apps/tester-federation-v2/src/host/ui/Report.tsx diff --git a/apps/tester-federation-v2/configs/rspack.host-app.mts b/apps/tester-federation-v2/configs/rspack.host-app.mts index 7c0e206bc..928272b20 100644 --- a/apps/tester-federation-v2/configs/rspack.host-app.mts +++ b/apps/tester-federation-v2/configs/rspack.host-app.mts @@ -44,6 +44,10 @@ export default Repack.defineRspackConfig((env) => { plugins: [ new Repack.RepackPlugin({ extraChunks: [ + { + include: /.+local.+/, + type: 'local', + }, { include: /.*/, type: 'remote', diff --git a/apps/tester-federation-v2/configs/webpack.host-app.mts b/apps/tester-federation-v2/configs/webpack.host-app.mts index a8329c8d6..b09268213 100644 --- a/apps/tester-federation-v2/configs/webpack.host-app.mts +++ b/apps/tester-federation-v2/configs/webpack.host-app.mts @@ -39,6 +39,10 @@ export default Repack.defineWebpackConfig((env) => { // @ts-expect-error new Repack.RepackPlugin({ extraChunks: [ + { + include: /.+local.+/, + type: 'local', + }, { include: /.*/, type: 'remote', diff --git a/apps/tester-federation-v2/e2e/async-chunk.e2e.ts b/apps/tester-federation-v2/e2e/async-chunk.e2e.ts new file mode 100644 index 000000000..ebfc13cd3 --- /dev/null +++ b/apps/tester-federation-v2/e2e/async-chunk.e2e.ts @@ -0,0 +1,17 @@ +import { test } from '@e2e-dev/mobile'; +import { expect } from 'e2e'; + +test('Async chunk loads through ScriptManager and renders', async ({ + app, + screen, +}) => { + await app.open(); + await expect(screen.getByTestId('home-screen')).toBeVisible(); + + const entry = screen.getByTestId('home-async-chunk-button'); + await screen.scrollUntilVisible(entry); + await entry.tap(); + await expect(screen.getByTestId('async-chunk-screen')).toBeVisible(); + + await expect(screen.getByTestId('async-chunk-content')).toBeVisible(); +}); diff --git a/apps/tester-federation-v2/e2e/detail.e2e.ts b/apps/tester-federation-v2/e2e/detail.e2e.ts new file mode 100644 index 000000000..7fb1cdecd --- /dev/null +++ b/apps/tester-federation-v2/e2e/detail.e2e.ts @@ -0,0 +1,16 @@ +import { test } from '@e2e-dev/mobile'; +import { expect } from 'e2e'; + +test('Detail screen renders and goes back to Home', async ({ app, screen }) => { + await app.open(); + await expect(screen.getByTestId('home-screen')).toBeVisible(); + + const entry = screen.getByTestId('home-details-button'); + await screen.scrollUntilVisible(entry); + await entry.tap(); + await expect(screen.getByTestId('details-screen')).toBeVisible(); + + await app.back(); + await expect(screen.getByTestId('details-screen')).toBeHidden(); + await expect(screen.getByTestId('home-details-button')).toBeVisible(); +}); diff --git a/apps/tester-federation-v2/e2e/gallery.e2e.ts b/apps/tester-federation-v2/e2e/gallery.e2e.ts new file mode 100644 index 000000000..e63758b6d --- /dev/null +++ b/apps/tester-federation-v2/e2e/gallery.e2e.ts @@ -0,0 +1,19 @@ +import { test } from '@e2e-dev/mobile'; +import { expect } from 'e2e'; + +test('Gallery MiniApp loads and renders its own navigator', async ({ + app, + screen, +}) => { + await app.open(); + await expect(screen.getByTestId('home-screen')).toBeVisible(); + + const entry = screen.getByTestId('home-gallery-button'); + await screen.scrollUntilVisible(entry); + await entry.tap(); + + await expect(screen.getByTestId('miniapp-gallery')).toBeVisible({ + timeout: 45_000, + }); + await expect(screen.getByText('Picture 1')).toBeVisible(); +}); diff --git a/apps/tester-federation-v2/e2e/host-assets.e2e.ts b/apps/tester-federation-v2/e2e/host-assets.e2e.ts new file mode 100644 index 000000000..f7a977e24 --- /dev/null +++ b/apps/tester-federation-v2/e2e/host-assets.e2e.ts @@ -0,0 +1,12 @@ +import { test } from '@e2e-dev/mobile'; +import { expect } from 'e2e'; + +test('Host Assets screen renders', async ({ app, screen }) => { + await app.open(); + await expect(screen.getByTestId('home-screen')).toBeVisible(); + + const entry = screen.getByTestId('home-host-assets-button'); + await screen.scrollUntilVisible(entry); + await entry.tap(); + await expect(screen.getByTestId('host-assets-screen')).toBeVisible(); +}); diff --git a/apps/tester-federation-v2/e2e/miniapp-install.e2e.ts b/apps/tester-federation-v2/e2e/miniapp-install.e2e.ts new file mode 100644 index 000000000..d5bb3fd58 --- /dev/null +++ b/apps/tester-federation-v2/e2e/miniapp-install.e2e.ts @@ -0,0 +1,35 @@ +import { test } from '@e2e-dev/mobile'; +import { expect } from 'e2e'; + +test('install, show, and remove the host mini-app chunk', async ({ + app, + screen, +}) => { + await app.open(); + await expect(screen.getByTestId('home-screen')).toBeVisible(); + + const entry = screen.getByTestId('home-miniapp-install-button'); + await screen.scrollUntilVisible(entry); + await entry.tap(); + await expect(screen.getByTestId('miniapp-install-screen')).toBeVisible(); + + await screen.getByTestId('miniapp-install-button').tap(); + await expect(screen.getByTestId('miniapp-install-button')).toBeDisabled(); + + await screen.getByTestId('miniapp-toggle-button').tap(); + await expect(screen.getByTestId('miniapp-install-content')).toBeVisible(); + + await screen.getByTestId('miniapp-toggle-button').tap(); + await expect(screen.getByTestId('miniapp-install-content')).toBeHidden(); + + await screen.getByTestId('miniapp-toggle-button').tap(); + await expect(screen.getByTestId('miniapp-install-content')).toBeVisible(); + + await screen.getByTestId('miniapp-uninstall-button').tap(); + await expect(screen.getByTestId('miniapp-install-content')).toBeHidden(); + await expect( + screen.getByTestId('miniapp-install-invalidated-report') + ).toHaveText('invalidated: true'); + await expect(screen.getByTestId('miniapp-install-button')).toBeEnabled(); + await expect(screen.getByTestId('miniapp-toggle-button')).toBeDisabled(); +}); diff --git a/apps/tester-federation-v2/e2e/remote-chunk.e2e.ts b/apps/tester-federation-v2/e2e/remote-chunk.e2e.ts new file mode 100644 index 000000000..0d0fafe98 --- /dev/null +++ b/apps/tester-federation-v2/e2e/remote-chunk.e2e.ts @@ -0,0 +1,30 @@ +import { test } from '@e2e-dev/mobile'; +import { expect } from 'e2e'; + +test('prefetch, load, and invalidate the host remote chunk', async ({ + app, + screen, +}) => { + await app.open(); + await expect(screen.getByTestId('home-screen')).toBeVisible(); + + const entry = screen.getByTestId('home-remote-chunk-button'); + await screen.scrollUntilVisible(entry); + await entry.tap(); + await expect(screen.getByTestId('remote-chunk-screen')).toBeVisible(); + + await screen.getByTestId('remote-chunk-prefetch-button').tap(); + await expect(screen.getByTestId('remote-chunk-prefetched-report')).toHaveText( + 'prefetched: true' + ); + + await screen.getByTestId('remote-chunk-load-button').tap(); + await expect(screen.getByTestId('remote-chunk-content')).toBeVisible(); + + await screen.getByTestId('remote-chunk-invalidate-button').tap(); + await expect( + screen.getByTestId('remote-chunk-invalidated-report') + ).toHaveText('invalidated: true'); + await expect(screen.getByTestId('remote-chunk-content')).toBeHidden(); + await expect(screen.getByTestId('remote-chunk-load-button')).toBeVisible(); +}); diff --git a/apps/tester-federation-v2/e2e/runtime-events.e2e.ts b/apps/tester-federation-v2/e2e/runtime-events.e2e.ts new file mode 100644 index 000000000..7a4905151 --- /dev/null +++ b/apps/tester-federation-v2/e2e/runtime-events.e2e.ts @@ -0,0 +1,47 @@ +import { test } from '@e2e-dev/mobile'; +import { expect } from 'e2e'; + +const EVENTS = [ + 'resolving', + 'resolved', + 'loading', + 'loaded', + 'beforeResolve', + 'resolve', + 'afterResolve', + 'beforeLoad', + 'load', + 'afterLoad', +]; + +test('opening the mini app drives every event and hook', async ({ + app, + screen, +}) => { + await app.open(); + await expect(screen.getByTestId('home-screen')).toBeVisible(); + + const gallery = screen.getByTestId('home-gallery-button'); + await screen.scrollUntilVisible(gallery); + await gallery.tap(); + await expect(screen.getByTestId('miniapp-gallery')).toBeVisible({ + timeout: 45_000, + }); + + await app.back(); + const runtime = screen.getByTestId('home-runtime-events-button'); + await screen.scrollUntilVisible(runtime); + await runtime.tap(); + await expect(screen.getByTestId('runtime-events-screen')).toBeVisible(); + + for (const event of EVENTS) { + await expect( + screen.getByTestId(`runtime-event-${event}-report`) + ).toHaveText(new RegExp(`${event}: [1-9]`)); + } + + await expect(screen.getByTestId('runtime-events-pipeline-report')).toHaveText( + 'pipeline: settled', + { timeout: 30_000 } + ); +}); diff --git a/apps/tester-federation-v2/e2e/smoke.e2e.ts b/apps/tester-federation-v2/e2e/smoke.e2e.ts deleted file mode 100644 index 2097ed870..000000000 --- a/apps/tester-federation-v2/e2e/smoke.e2e.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { test } from '@e2e-dev/mobile'; -import { expect } from 'e2e'; - -test('the host app boots to Home', async ({ app, screen }) => { - await app.open(); - - await expect(screen.getByTestId('home-details-button')).toBeVisible({ - timeout: 60_000, - }); - await expect(screen.getByTestId('home-gallery-button')).toBeVisible(); -}); - -test('navigates from Home to Detail and back', async ({ app, screen }) => { - await app.open(); - await expect(screen.getByTestId('home-details-button')).toBeVisible({ - timeout: 60_000, - }); - - await screen.getByTestId('home-details-button').tap(); - await expect(screen.getByTestId('detail-screen')).toBeVisible(); - - await app.back(); - await expect(screen.getByTestId('home-details-button')).toBeVisible(); - await expect(screen.getByTestId('detail-screen')).toBeHidden(); -}); - -test('Home opens the Gallery MiniApp', { - timeout: 120_000, -}, async ({ app, screen }) => { - await app.open(); - await expect(screen.getByTestId('home-gallery-button')).toBeVisible({ - timeout: 60_000, - }); - - await screen.getByTestId('home-gallery-button').tap(); - await expect(screen.getByTestId('miniapp-gallery')).toBeVisible({ - timeout: 45_000, - }); - await expect(screen.getByText('Picture 1')).toBeVisible(); -}); diff --git a/apps/tester-federation-v2/src/host/FEATURES.ts b/apps/tester-federation-v2/src/host/FEATURES.ts new file mode 100644 index 000000000..d21bc9e3b --- /dev/null +++ b/apps/tester-federation-v2/src/host/FEATURES.ts @@ -0,0 +1,56 @@ +export type FeatureGroup = + | 'Mini Apps' + | 'Navigation' + | 'Runtime' + | 'Chunks' + | 'Plugins'; + +export type Feature = { + key: string; + name: string; + group: FeatureGroup; + screen: keyof import('./navigation/MainNavigator').MainStackParamList; +}; + +export const FEATURES: Feature[] = [ + { key: 'gallery', name: 'Gallery', group: 'Mini Apps', screen: 'MiniApp' }, + { key: 'details', name: 'Detail', group: 'Navigation', screen: 'Detail' }, + { + key: 'host-assets', + name: 'Host assets', + group: 'Runtime', + screen: 'HostAssets', + }, + { + key: 'runtime-events', + name: 'Runtime events', + group: 'Runtime', + screen: 'RuntimeEvents', + }, + { + key: 'async-chunk', + name: 'Async chunk', + group: 'Chunks', + screen: 'AsyncChunk', + }, + { + key: 'remote-chunk', + name: 'Remote chunk', + group: 'Chunks', + screen: 'RemoteChunk', + }, + { + key: 'miniapp-install', + name: 'Mini-app install', + group: 'Chunks', + screen: 'MiniappInstall', + }, +]; + +export const FEATURE_GROUPS: FeatureGroup[] = [ + 'Navigation', + 'Runtime', + 'Mini Apps', + 'Chunks', + 'Plugins', +]; diff --git a/apps/tester-federation-v2/src/host/features/async/Async.local.tsx b/apps/tester-federation-v2/src/host/features/async/Async.local.tsx new file mode 100644 index 000000000..32e54d35d --- /dev/null +++ b/apps/tester-federation-v2/src/host/features/async/Async.local.tsx @@ -0,0 +1,21 @@ +import { StyleSheet, Text, View } from 'react-native'; + +export default function AsyncLocal() { + return ( + + + Async: this text comes from async chunk + + + ); +} + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + }, + text: { + fontSize: 16, + color: '#200a12', + }, +}); diff --git a/apps/tester-federation-v2/src/host/features/async/AsyncChunkSection.tsx b/apps/tester-federation-v2/src/host/features/async/AsyncChunkSection.tsx new file mode 100644 index 000000000..1ddfa9cea --- /dev/null +++ b/apps/tester-federation-v2/src/host/features/async/AsyncChunkSection.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; +import { ActivityIndicator, StyleSheet, View } from 'react-native'; + +const AsyncLocal = React.lazy( + () => import(/* webpackChunkName: "host-async-local" */ './Async.local') +); + +const Fallback = () => ( + + + +); + +const AsyncChunkSection = () => { + return ( + + }> + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + alignSelf: 'stretch', + paddingVertical: 12, + }, + fallback: { + paddingVertical: 24, + }, +}); + +export default AsyncChunkSection; diff --git a/apps/tester-federation-v2/src/host/features/miniapp/MiniApp.tsx b/apps/tester-federation-v2/src/host/features/miniapp/MiniApp.tsx new file mode 100644 index 000000000..c72bf92eb --- /dev/null +++ b/apps/tester-federation-v2/src/host/features/miniapp/MiniApp.tsx @@ -0,0 +1,21 @@ +import { StyleSheet, Text, View } from 'react-native'; + +export default function MiniAppLocal() { + return ( + + + MiniApp: this text comes from MiniApp chunk + + + ); +} + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + }, + text: { + fontSize: 16, + color: '#200a12', + }, +}); diff --git a/apps/tester-federation-v2/src/host/features/miniapp/MiniAppSection.tsx b/apps/tester-federation-v2/src/host/features/miniapp/MiniAppSection.tsx new file mode 100644 index 000000000..ae0bb578b --- /dev/null +++ b/apps/tester-federation-v2/src/host/features/miniapp/MiniAppSection.tsx @@ -0,0 +1,106 @@ +import { ScriptManager } from '@callstack/repack/client'; +import * as React from 'react'; +import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; + +const MiniAppChunkId = 'miniapp'; + +type Props = { + onInvalidated: () => void; +}; + +const MiniAppSection = ({ onInvalidated }: Props) => { + const [isInstalled, setIsInstalled] = React.useState(false); + const [isVisible, setIsVisible] = React.useState(false); + const [MiniAppComponent, setMiniAppComponent] = React.useState< + React.ComponentType | undefined + >(undefined); + + const install = async () => { + const bundle = await import(/* webpackChunkName: "miniapp" */ './MiniApp'); + setMiniAppComponent(() => bundle.default); + setIsInstalled(true); + }; + + const uninstall = async () => { + await ScriptManager.shared.invalidateScripts([MiniAppChunkId]); + setMiniAppComponent(undefined); + setIsInstalled(false); + setIsVisible(false); + onInvalidated(); + }; + + return ( + + + Install + + + + Remove + + + setIsVisible((visible) => !visible)} + > + + {isVisible ? 'Hide' : 'Show'} + + + {isInstalled && isVisible && MiniAppComponent ? ( + + ) : null} + + ); +}; + +const styles = StyleSheet.create({ + container: { + alignSelf: 'stretch', + paddingVertical: 12, + gap: 8, + }, + button: { + alignSelf: 'stretch', + paddingVertical: 12, + paddingHorizontal: 16, + borderRadius: 8, + backgroundColor: 'rgba(56, 30, 114, 1)', + }, + secondaryButton: { + backgroundColor: '#d8cbe8', + }, + buttonText: { + color: '#ffffff', + fontSize: 16, + textAlign: 'center', + }, + secondaryButtonText: { + color: '#200a12', + }, + buttonDisabled: { + opacity: 0.5, + }, +}); + +export default MiniAppSection; diff --git a/apps/tester-federation-v2/src/host/features/remote/Remote.tsx b/apps/tester-federation-v2/src/host/features/remote/Remote.tsx new file mode 100644 index 000000000..ab95161b1 --- /dev/null +++ b/apps/tester-federation-v2/src/host/features/remote/Remote.tsx @@ -0,0 +1,21 @@ +import { StyleSheet, Text, View } from 'react-native'; + +export default function Remote() { + return ( + + + Remote: this text comes from remote chunk + + + ); +} + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + }, + text: { + fontSize: 16, + color: '#200a12', + }, +}); diff --git a/apps/tester-federation-v2/src/host/features/remote/RemoteChunkSection.tsx b/apps/tester-federation-v2/src/host/features/remote/RemoteChunkSection.tsx new file mode 100644 index 000000000..e784241f6 --- /dev/null +++ b/apps/tester-federation-v2/src/host/features/remote/RemoteChunkSection.tsx @@ -0,0 +1,120 @@ +import { ScriptManager } from '@callstack/repack/client'; +import * as React from 'react'; +import { + ActivityIndicator, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; + +const RemoteChunkId = 'remote'; + +const Remote = React.lazy( + () => import(/* webpackChunkName: "remote" */ './Remote') +); + +const Fallback = () => ( + + + +); + +type Props = { + onPrefetched: () => void; + onInvalidated: () => void; +}; + +const RemoteChunkSection = ({ onPrefetched, onInvalidated }: Props) => { + const [isPrefetched, setIsPrefetched] = React.useState(false); + const [isLoaded, setIsLoaded] = React.useState(false); + + const prefetch = async () => { + await ScriptManager.shared.prefetchScript(RemoteChunkId); + setIsPrefetched(true); + onPrefetched(); + }; + + const invalidate = async () => { + await ScriptManager.shared.invalidateScripts([RemoteChunkId]); + setIsLoaded(false); + setIsPrefetched(false); + onInvalidated(); + }; + + return ( + + {isLoaded ? ( + }> + + + ) : ( + + + + {isPrefetched ? 'Prefetched' : 'Prefetch chunk'} + + + setIsLoaded(true)} + > + Load chunk + + + )} + + + Invalidate + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + alignSelf: 'stretch', + paddingVertical: 12, + gap: 8, + }, + actions: { + gap: 8, + }, + fallback: { + paddingVertical: 24, + }, + button: { + alignSelf: 'stretch', + paddingVertical: 12, + paddingHorizontal: 16, + borderRadius: 8, + backgroundColor: 'rgba(56, 30, 114, 1)', + }, + secondaryButton: { + backgroundColor: '#d8cbe8', + }, + buttonText: { + color: '#ffffff', + fontSize: 16, + textAlign: 'center', + }, + secondaryButtonText: { + color: '#200a12', + }, + buttonDisabled: { + opacity: 0.5, + }, +}); + +export default RemoteChunkSection; diff --git a/apps/tester-federation-v2/src/host/features/runtime/events.ts b/apps/tester-federation-v2/src/host/features/runtime/events.ts new file mode 100644 index 000000000..6bc04eac5 --- /dev/null +++ b/apps/tester-federation-v2/src/host/features/runtime/events.ts @@ -0,0 +1,146 @@ +import { ScriptManager } from '@callstack/repack/client'; + +export type ScriptManagerCounts = { + resolving: number; + resolved: number; + prefetching: number; + loading: number; + loaded: number; + error: number; + beforeResolve: number; + resolve: number; + afterResolve: number; + beforeLoad: number; + load: number; + afterLoad: number; +}; + +const counts: ScriptManagerCounts = { + resolving: 0, + resolved: 0, + prefetching: 0, + loading: 0, + loaded: 0, + error: 0, + beforeResolve: 0, + resolve: 0, + afterResolve: 0, + beforeLoad: 0, + load: 0, + afterLoad: 0, +}; + +const listeners = new Set<(counts: ScriptManagerCounts) => void>(); + +export function subscribeEventCounts( + listener: (counts: ScriptManagerCounts) => void +) { + listeners.add(listener); + return () => { + listeners.delete(listener); + }; +} + +export function readEventCounts(): ScriptManagerCounts { + return { ...counts }; +} + +function bump(key: keyof ScriptManagerCounts) { + counts[key] += 1; + for (const listener of listeners) { + listener(readEventCounts()); + } +} + +const RESOLVE_STAGES: Array = [ + 'beforeResolve', + 'resolving', + 'resolve', + 'afterResolve', + 'resolved', +]; + +const LOAD_STAGES: Array = [ + 'beforeLoad', + 'loading', + 'load', + 'afterLoad', + 'loaded', +]; + +export type PipelineStatus = { + settled: boolean; + label: string; +}; + +function findStuckStage( + counts: ScriptManagerCounts, + stages: Array, + expected: number +) { + const stage = stages.find((name) => counts[name] !== expected); + return stage === undefined ? undefined : { stage, expected }; +} + +export function getPipelineStatus(counts: ScriptManagerCounts): PipelineStatus { + const loaded = counts.beforeResolve - counts.prefetching; + + const stuck = + findStuckStage(counts, RESOLVE_STAGES, counts.beforeResolve) ?? + findStuckStage(counts, LOAD_STAGES, loaded); + + if (stuck === undefined) { + return { settled: true, label: 'settled' }; + } + + return { + settled: false, + label: `${stuck.stage}=${counts[stuck.stage]} of ${stuck.expected}`, + }; +} + +export function registerEventCounter() { + ScriptManager.shared.on('resolving', () => bump('resolving')); + ScriptManager.shared.on('resolved', () => bump('resolved')); + ScriptManager.shared.on('prefetching', () => bump('prefetching')); + ScriptManager.shared.on('loading', () => bump('loading')); + ScriptManager.shared.on('loaded', () => bump('loaded')); + ScriptManager.shared.on('error', () => bump('error')); + + ScriptManager.shared.hooks.beforeResolve((args) => { + bump('beforeResolve'); + return args; + }); + + ScriptManager.shared.hooks.resolve(async (args) => { + bump('resolve'); + const { scriptId, caller, referenceUrl } = args.options; + for (const [, , resolve] of args.resolvers) { + const locator = await resolve(scriptId, caller, referenceUrl); + if (locator) { + return locator; + } + } + }); + + ScriptManager.shared.hooks.afterResolve((args) => { + bump('afterResolve'); + return args; + }); + + ScriptManager.shared.hooks.beforeLoad((args) => { + bump('beforeLoad'); + return args; + }); + + ScriptManager.shared.hooks.load(async (args) => { + bump('load'); + await args.loadScript(); + return true; + }); + + ScriptManager.shared.hooks.afterLoad((args) => { + bump('afterLoad'); + return args; + }); +} diff --git a/apps/tester-federation-v2/src/host/index.js b/apps/tester-federation-v2/src/host/index.js index 61610d410..e79698f47 100644 --- a/apps/tester-federation-v2/src/host/index.js +++ b/apps/tester-federation-v2/src/host/index.js @@ -4,6 +4,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import { AppRegistry } from 'react-native'; import { components } from '../../app.json'; +import './setup'; import App from './App'; if (__WITH_PRELOAD__) { diff --git a/apps/tester-federation-v2/src/host/navigation/MainNavigator.tsx b/apps/tester-federation-v2/src/host/navigation/MainNavigator.tsx index 2c544c03e..97d7c1c3e 100644 --- a/apps/tester-federation-v2/src/host/navigation/MainNavigator.tsx +++ b/apps/tester-federation-v2/src/host/navigation/MainNavigator.tsx @@ -4,16 +4,24 @@ import { } from '@react-navigation/native-stack'; import { StyleSheet } from 'react-native'; +import AsyncChunkScreen from '../screens/AsyncChunkScreen'; import DetailScreen from '../screens/DetailScreen'; import HomeScreen from '../screens/HomeScreen'; import HostAssetsScreen from '../screens/HostAssetsScreen'; import MiniAppScreen from '../screens/MiniAppScreen'; +import MiniappInstallScreen from '../screens/MiniappInstallScreen'; +import RemoteChunkScreen from '../screens/RemoteChunkScreen'; +import RuntimeEventsScreen from '../screens/RuntimeEventsScreen'; export type MainStackParamList = { Home: undefined; Detail: undefined; HostAssets: undefined; MiniApp: undefined; + RuntimeEvents: undefined; + AsyncChunk: undefined; + RemoteChunk: undefined; + MiniappInstall: undefined; }; export type MainStackNavigationProp = @@ -54,6 +62,10 @@ const MainNavigator = () => { component={MiniAppScreen} options={{ headerStyle: { backgroundColor: '#FF9F00' } }} /> + + + + ); }; diff --git a/apps/tester-federation-v2/src/host/screens/AsyncChunkScreen.tsx b/apps/tester-federation-v2/src/host/screens/AsyncChunkScreen.tsx new file mode 100644 index 000000000..7daa90291 --- /dev/null +++ b/apps/tester-federation-v2/src/host/screens/AsyncChunkScreen.tsx @@ -0,0 +1,28 @@ +import { ScrollView, StyleSheet, View } from 'react-native'; + +import AsyncChunkSection from '../features/async/AsyncChunkSection'; + +const AsyncChunkScreen = () => { + return ( + + + + + + ); +}; + +const styles = StyleSheet.create({ + content: { + paddingVertical: 16, + }, + container: { + paddingHorizontal: 16, + gap: 4, + }, +}); + +export default AsyncChunkScreen; diff --git a/apps/tester-federation-v2/src/host/screens/DetailScreen.tsx b/apps/tester-federation-v2/src/host/screens/DetailScreen.tsx index 3097b001a..068888dc9 100644 --- a/apps/tester-federation-v2/src/host/screens/DetailScreen.tsx +++ b/apps/tester-federation-v2/src/host/screens/DetailScreen.tsx @@ -2,7 +2,7 @@ import { StyleSheet, Text, View } from 'react-native'; const DetailScreen = () => { return ( - + DetailScreen ); diff --git a/apps/tester-federation-v2/src/host/screens/HomeScreen.tsx b/apps/tester-federation-v2/src/host/screens/HomeScreen.tsx index 20dfbb5b6..4e4ed7bf1 100644 --- a/apps/tester-federation-v2/src/host/screens/HomeScreen.tsx +++ b/apps/tester-federation-v2/src/host/screens/HomeScreen.tsx @@ -1,175 +1,84 @@ import { useNavigation } from '@react-navigation/native'; -import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { + ScrollView, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; + +import { FEATURE_GROUPS, FEATURES } from '../FEATURES'; import type { MainStackNavigationProp } from '../navigation/MainNavigator'; const HomeScreen = () => { const navigation = useNavigation(); return ( - - - Welcome Home - - Explore the host app and its features - - navigation.navigate('Detail')} - > - See details - - navigation.navigate('HostAssets')} - > - Host assets - - - - Mini Apps - - navigation.navigate('MiniApp')} - > - 🖼️ - - Gallery - - - - 💬 - - Messages - - - - ⚙️ - - Settings - - + + Welcome Home + Explore the host app and its features + {FEATURE_GROUPS.filter((group) => + FEATURES.some((feature) => feature.group === group) + ).map((group) => ( + + {group} + {FEATURES.filter((feature) => feature.group === group).map( + (feature) => ( + navigation.navigate(feature.screen)} + > + {feature.name} + + ) + )} - - + ))} + ); }; const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#F5F9FF', - }, - hostSection: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - paddingHorizontal: 20, + content: { + padding: 20, + gap: 8, }, title: { fontSize: 28, - fontWeight: 'bold', - marginBottom: 10, - color: '#2C3E50', + fontWeight: '700', + color: '#200a12', }, subtitle: { fontSize: 16, - color: '#34495E', - marginBottom: 30, - textAlign: 'center', - }, - button: { - backgroundColor: '#3498DB', - padding: 15, - borderRadius: 10, - alignItems: 'center', + color: '#55424a', + marginBottom: 12, }, - secondaryButton: { - marginTop: 12, - backgroundColor: '#FFFFFF', - borderColor: '#3498DB', - borderWidth: 1, + group: { + gap: 6, + marginBottom: 16, }, - buttonText: { - color: 'white', - fontSize: 16, + groupTitle: { + fontSize: 18, fontWeight: '600', + color: 'rgba(56, 30, 114, 1)', + marginBottom: 4, }, - secondaryButtonText: { - color: '#3498DB', - fontSize: 16, - fontWeight: '600', - }, - miniAppSection: { - backgroundColor: 'white', - padding: 20, - borderTopLeftRadius: 30, - borderTopRightRadius: 30, - shadowColor: '#34495E', - shadowOffset: { width: 0, height: -3 }, - shadowOpacity: 0.1, - shadowRadius: 5, - elevation: 5, - }, - sectionTitle: { - fontSize: 24, - fontWeight: '600', - marginBottom: 20, - color: '#2C3E50', - textAlign: 'center', - }, - miniAppContainer: { - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'center', - minHeight: 150, - }, - miniAppButton: { - backgroundColor: '#ECF0F1', - width: 100, - height: 100, - borderColor: '#BDC3C7', - borderWidth: 1, - borderRadius: 15, - alignItems: 'center', - justifyContent: 'center', - margin: 5, - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 4, - elevation: 3, - }, - galleryButton: { - borderColor: '#FF9F00', - backgroundColor: '#FFF5E6', - }, - galleryButtonText: { - color: '#FF9F00', - }, - miniAppIcon: { - fontSize: 40, - marginBottom: 5, - color: '#FFFFFF', + button: { + backgroundColor: 'rgba(56, 30, 114, 1)', + paddingVertical: 12, + paddingHorizontal: 16, + borderRadius: 8, }, - miniAppText: { - color: '#FFFFFF', + buttonText: { + color: '#ffffff', fontSize: 16, - fontWeight: '700', - }, - disabledMiniApp: { - opacity: 0.5, - }, - disabledText: { - color: '#95A5A6', + textAlign: 'center', }, }); diff --git a/apps/tester-federation-v2/src/host/screens/HostAssetsScreen.tsx b/apps/tester-federation-v2/src/host/screens/HostAssetsScreen.tsx index 62e2333ee..96789fa46 100644 --- a/apps/tester-federation-v2/src/host/screens/HostAssetsScreen.tsx +++ b/apps/tester-federation-v2/src/host/screens/HostAssetsScreen.tsx @@ -75,6 +75,7 @@ const AssetRow = ({ const HostAssetsScreen = () => { return ( ( const MiniAppScreen = () => { return ( - - }> - - - + + + }> + + + + ); }; const styles = StyleSheet.create({ + screen: { + flex: 1, + }, container: { flex: 1, justifyContent: 'center', diff --git a/apps/tester-federation-v2/src/host/screens/MiniappInstallScreen.tsx b/apps/tester-federation-v2/src/host/screens/MiniappInstallScreen.tsx new file mode 100644 index 000000000..8aa32576e --- /dev/null +++ b/apps/tester-federation-v2/src/host/screens/MiniappInstallScreen.tsx @@ -0,0 +1,36 @@ +import * as React from 'react'; +import { ScrollView, StyleSheet, View } from 'react-native'; +import MiniAppSection from '../features/miniapp/MiniAppSection'; +import Report from '../ui/Report'; + +const MiniappInstallScreen = () => { + const [invalidated, setInvalidated] = React.useState(false); + + return ( + + + setInvalidated(true)} /> + + + + ); +}; + +const styles = StyleSheet.create({ + content: { + paddingVertical: 16, + }, + container: { + paddingHorizontal: 16, + gap: 4, + }, +}); + +export default MiniappInstallScreen; diff --git a/apps/tester-federation-v2/src/host/screens/RemoteChunkScreen.tsx b/apps/tester-federation-v2/src/host/screens/RemoteChunkScreen.tsx new file mode 100644 index 000000000..4ccf272a5 --- /dev/null +++ b/apps/tester-federation-v2/src/host/screens/RemoteChunkScreen.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import { ScrollView, StyleSheet, View } from 'react-native'; +import RemoteChunkSection from '../features/remote/RemoteChunkSection'; +import Report from '../ui/Report'; + +const RemoteChunkScreen = () => { + const [prefetched, setPrefetched] = React.useState(false); + const [invalidated, setInvalidated] = React.useState(false); + + return ( + + + setPrefetched(true)} + onInvalidated={() => { + setInvalidated(true); + setPrefetched(false); + }} + /> + + + + + ); +}; + +const styles = StyleSheet.create({ + content: { + paddingVertical: 16, + }, + container: { + paddingHorizontal: 16, + gap: 4, + }, +}); + +export default RemoteChunkScreen; diff --git a/apps/tester-federation-v2/src/host/screens/RuntimeEventsScreen.tsx b/apps/tester-federation-v2/src/host/screens/RuntimeEventsScreen.tsx new file mode 100644 index 000000000..521159e90 --- /dev/null +++ b/apps/tester-federation-v2/src/host/screens/RuntimeEventsScreen.tsx @@ -0,0 +1,67 @@ +import * as React from 'react'; +import { ScrollView, StyleSheet, View } from 'react-native'; +import { + getPipelineStatus, + readEventCounts, + type ScriptManagerCounts, + subscribeEventCounts, +} from '../features/runtime/events'; +import Report from '../ui/Report'; + +const EVENT_REPORTS: Array = [ + 'resolving', + 'resolved', + 'prefetching', + 'loading', + 'loaded', + 'error', + 'beforeResolve', + 'resolve', + 'afterResolve', + 'beforeLoad', + 'load', + 'afterLoad', +]; + +const RuntimeEventsScreen = () => { + const [counts, setCounts] = React.useState(() => + readEventCounts() + ); + + React.useEffect(() => subscribeEventCounts((next) => setCounts(next)), []); + + return ( + + + {EVENT_REPORTS.map((event) => ( + + ))} + + + + ); +}; + +const styles = StyleSheet.create({ + content: { + paddingVertical: 16, + }, + container: { + paddingHorizontal: 16, + gap: 4, + }, +}); + +export default RuntimeEventsScreen; diff --git a/apps/tester-federation-v2/src/host/setup.ts b/apps/tester-federation-v2/src/host/setup.ts new file mode 100644 index 000000000..ed46d6140 --- /dev/null +++ b/apps/tester-federation-v2/src/host/setup.ts @@ -0,0 +1,31 @@ +import { Script, ScriptManager } from '@callstack/repack/client'; + +import { registerEventCounter } from './features/runtime/events'; + +const HOST_OWNED_CHUNKS = ['remote', 'miniapp', 'host-async-local']; + +ScriptManager.shared.addResolver(async (scriptId) => { + if (!HOST_OWNED_CHUNKS.includes(scriptId)) { + return undefined; + } + + if (__DEV__) { + return { + url: Script.getDevServerURL(scriptId), + cache: false, + }; + } + + if (scriptId.includes('local')) { + return { + url: Script.getFileSystemURL(scriptId), + cache: false, + }; + } + + return { + url: Script.getRemoteURL(`http://localhost:9999/${scriptId}`), + }; +}); + +registerEventCounter(); diff --git a/apps/tester-federation-v2/src/host/ui/Report.tsx b/apps/tester-federation-v2/src/host/ui/Report.tsx new file mode 100644 index 000000000..bce95ac28 --- /dev/null +++ b/apps/tester-federation-v2/src/host/ui/Report.tsx @@ -0,0 +1,25 @@ +import { StyleSheet, Text } from 'react-native'; + +type ReportProps = { + testID: string; + name: string; + value: unknown; +}; + +const Report = ({ testID, name, value }: ReportProps) => ( + + {`${name}: ${String(value)}`} + +); + +const styles = StyleSheet.create({ + row: { + alignSelf: 'stretch', + paddingVertical: 6, + paddingHorizontal: 12, + fontSize: 14, + color: '#200a12', + }, +}); + +export default Report; From 144812f3d45b7e68dbb56892825e5e5778e75c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Fri, 23 Oct 2026 11:53:39 +0200 Subject: [PATCH 08/21] fix: lock --- pnpm-lock.yaml | 637 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 635 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 198908012..b55062204 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ catalogs: specifier: ~3.3.0 version: 3.3.0 testers: + '@e2e-dev/mobile': + specifier: 0.11.0 + version: 0.11.0 '@module-federation/enhanced': specifier: 2.9.2 version: 2.9.2 @@ -104,6 +107,9 @@ catalogs: '@types/react': specifier: ^19.2.0 version: 19.2.17 + e2e: + specifier: 0.19.0 + version: 0.19.0 react-native-reanimated: specifier: 4.7.1 version: 4.7.1 @@ -575,6 +581,9 @@ importers: '@babel/core': specifier: ^7.29.7 version: 7.29.7 + '@e2e-dev/mobile': + specifier: catalog:testers + version: 0.11.0(e2e@0.19.0) '@react-native-community/cli': specifier: catalog:testers version: 20.2.0(typescript@7.0.2) @@ -608,9 +617,15 @@ importers: '@types/jest': specifier: ^29.5.13 version: 29.5.14 + '@types/node': + specifier: 'catalog:' + version: 20.19.43 '@types/react': specifier: catalog:testers version: 19.2.17 + e2e: + specifier: catalog:testers + version: 0.19.0 react-native-test-app: specifier: catalog:testers version: 5.4.14(@expo/config-plugins@58.0.9(typescript@7.0.2))(@react-native-community/cli-types@20.2.0)(memfs@4.64.0)(metro@0.87.1)(react-native@0.88.0(@babel/core@7.29.7)(@react-native-community/cli@20.2.0(typescript@7.0.2))(@react-native/metro-config@0.88.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.3.0))(react@19.3.0) @@ -1173,6 +1188,10 @@ packages: typescript: optional: true + '@ai-sdk/provider@4.0.21': + resolution: {integrity: sha512-UpbC9C1oht8dhfKPbVXSLRZS3DI8uk8n5v2uMjBPNIYGsC2kL045ywq7D8Hh9KgnH9rP5W/GxQdYtWScRouqlA==} + engines: {node: '>=22'} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -2117,6 +2136,16 @@ packages: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} + '@e2e-dev/mobile@0.11.0': + resolution: {integrity: sha512-wvZ/VVUmNs65qnXqgh49huCw1Rgl1+3sngOEd4umLaNtPAcQBTzbX9Fz5zdXR5tQuN3HPejVez7IOSHYE3/dyQ==} + engines: {node: ^22.22.3 || >=24.8.0} + peerDependencies: + ai: ^7.0.0 + e2e: '>=0.15.0 <1' + peerDependenciesMeta: + ai: + optional: true + '@emnapi/core@1.11.3': resolution: {integrity: sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==} @@ -2910,6 +2939,14 @@ packages: '@types/react': '>=16' react: '>=16' + '@modelcontextprotocol/core@2.3.0': + resolution: {integrity: sha512-09BHFaNVBe5pB2AhzCfLj0cqmsKLERvZQ4FblzP90UMGLaXVqdZPByFjURwsUYCmxonUW2yAwgTTvle+8O2/8w==} + engines: {node: '>=20'} + + '@modelcontextprotocol/server@2.3.0': + resolution: {integrity: sha512-+6b0LdsQLmHsvS7J6sQJ4weTpzUn61fU1JKS+321/+IyDYoHW5CR2Tgqcs6GgAAQvlesCo6yX3xttgmx+XGtJw==} + engines: {node: '>=20'} + '@modern-js/node-bundle-require@2.67.6': resolution: {integrity: sha512-rRiDQkrm3kgn0E/GNrcvqo4c71PaUs2R8Xmpv6GUKbEr6lz7VNgfZmAhdAQPtNfRfiBe+1sFLzEcwfEdDo/dTA==} @@ -3371,9 +3408,260 @@ packages: cpu: [x64] os: [win32] + '@oxc-parser/binding-android-arm-eabi@0.152.0': + resolution: {integrity: sha512-393VtirINkae4XHBTkq/yQxhOwEr4V+r0et37P2fp+sYZSdYgZTRDPo5Um8vBh0QjMZ+RmaMy4PP0qwr4cTMwA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.152.0': + resolution: {integrity: sha512-iYFcExATJNZ0rZj0o02THbD1Nc9jAW0nVgIQPee6ejbSASpbZXlIMw8jxtpzaG14JQTWwGSZ6U5dEtRJXE6V3Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.152.0': + resolution: {integrity: sha512-fnU+DfUzi5/rFy0LzmipYRz06PAUUkOS8kCJQ3Eg6oqlunC1XZXoffFqxmdL51RmcbYtUG3P8DVG1U5fckoa2A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.152.0': + resolution: {integrity: sha512-Y3U2okryWTs7hDld6UhjJwLNf8/do+x4g4C+1U5SiGwrTLwbO83WJYJA8+l9iFSilWBgnfpd/+U76tfeNxUHzA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.152.0': + resolution: {integrity: sha512-XkhkRrrPffEw+ZLv/d/5pEni0buxinm/E/N6fLbJCunD1DBbeCTtVP23ESV+4CWCWdSz397asks1ViGslpmWiQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.152.0': + resolution: {integrity: sha512-MLzsRRbdfmdzo6JEhbC1yKU9ItfvM8tEf18HM32jCiNYZEz6J3bRBRl3cTkKR0vBdrk9ZVlcARXtwHccs+epDA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.152.0': + resolution: {integrity: sha512-HHerTEJz7Iy10Ji6BGsk4gqG6PU0UzFuBu91jIEOX89FnrI5ECWSEr7BLpkYQhp7U0QFtMnBhNfQKI0hYZK78w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.152.0': + resolution: {integrity: sha512-9Bb8GyzZ2uSLzRDZ27usaKokr07mxiBe2dDF5b6bd2janmaJTT1gL51UfP7AU30yFiXrJvcRyFKREAgKwZYs0w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-arm64-musl@0.152.0': + resolution: {integrity: sha512-om7iDMUroI74A/lfRRWY9/Ev3VWrVsrUZ4tyvyh8raBey/sdcfyAD7tYWc+iykAiotnmftyKTpUKFr1QnaBrAQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-ppc64-gnu@0.152.0': + resolution: {integrity: sha512-Lf1OL2lMTqwDMFC6jnRb30B+WYHKV0XT+bPHcFM6xHtNVwxpobiTzUIvKH6a0LWpvfwyru8Yu6zCGHqqFHH+gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-gnu@0.152.0': + resolution: {integrity: sha512-BYWSMQK+YJ9ZILStPcmmiSVq8KEG9Qdq/c4BHPC3rUEq/fEDCVjUIwdj5ZzjgZ9P9is9PKu0tcAXzq1ZXg4urQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.152.0': + resolution: {integrity: sha512-d2LqHNNNqdH185sRK5dPKWZu201JrNu9/CRFuVEuT+ht7n/QaVblEMWT9MALQPrDWYvhG10P+NSKJFquDWZ09w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.152.0': + resolution: {integrity: sha512-8gBeHt5rA/8+pKhHl+zLggfa3L/V/L2at96p7ty6pBE7id6lFO19nFUc15u2PcjC4OYeYK465MKy1fhhbpFIGw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-gnu@0.152.0': + resolution: {integrity: sha512-ltgrSF6L+yPTVsIdaAyqKq3zTqzj/1VlNbxWZuxAe8flR3sWdyNsKvdXukoO8/ucJbLickhmieRBge+o/e8Ugw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-musl@0.152.0': + resolution: {integrity: sha512-LcMp6BvrBnewSfdkLHoSqidhoAq4syKOTBV5TYHWd+2VnGkfD/nPRRVIzTBt/Lkbt7pfOFDGtkiTntFS0p6sQQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-openharmony-arm64@0.152.0': + resolution: {integrity: sha512-jbhoMCslVxzRCRNQ8TSHxjYdSKRvk8O6ciLVDkahJvmyHDzBqba/bMQHM6ULKj/rgHNNbSzlIiakdx8SEfkJzA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-win32-arm64-msvc@0.152.0': + resolution: {integrity: sha512-JZYVlhv36mr/nertnRB7vchJECOBb1Lz6lltalILsB+o9M/is+H6BEDRLQKdqA4JKs6NjJszo46+iM5sfwj6jQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.152.0': + resolution: {integrity: sha512-1X9oganvVsunRH0lH10UU05N+H9atpX6WsOybByUjwnfcd3h/0tYwvuv16XuHglrIUOnxV2TodbLVEwwafd64g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.152.0': + resolution: {integrity: sha512-sw0M80/dn9rcpu+Rqqph/C+MjvDDce6MlXmFjsNQtPEymha0eJy3ml6+foSUTqFfpDIDyHZZ4eoJ43IoMe8bJw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/runtime@0.152.0': + resolution: {integrity: sha512-fsq/7JVJo8YAmMPnt3Z7akMdMoCokRND5jjawa5DxuKJBEaI0Dl3ZqEXFlLabKoZhlApIexZ7aycXFcjcTcWvw==} + engines: {node: ^20.19.0 || >=22.12.0} + '@oxc-project/types@0.141.0': resolution: {integrity: sha512-S4as7z0j0xQkXcJlyY5ehntwK8/wRkQb9Cyqw+J/N2rkWGQGK0SxD6X6DhQTc7qsxVTBxXbxZtBJh3mr3PtIzQ==} + '@oxc-project/types@0.152.0': + resolution: {integrity: sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==} + + '@oxc-transform/binding-android-arm-eabi@0.152.0': + resolution: {integrity: sha512-GBVYZdUggANq7ndUuE9HBi1jO6d2+oud3cevfQkpqrV7JsDNwQU5UCGa8rZCvzjIvyn6ikQuJu+sj4RvLyN6Uw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-transform/binding-android-arm64@0.152.0': + resolution: {integrity: sha512-pqeCLGUnozzyJE9Ed9js2w+V1lHQ43qPImMfgTh7it+a3LJqUjAt+OesJ6pqkPycRFV6b0pUWZm6EkpM8mk7ag==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-transform/binding-darwin-arm64@0.152.0': + resolution: {integrity: sha512-0d68wdepj2bjnDijPdvC/3MnrZcs8hoCp19qnGX2tyxnSIPevgqa9RX5i31ex5JMczht/VGp4aKeV1p12dwzQQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-transform/binding-darwin-x64@0.152.0': + resolution: {integrity: sha512-jFTRTyZzYhqSrxkaI9hTwClPTXtdqRyPSXUFGLJmStYS+b6qnhbI1ottivWdAg0HJXytyg8YeqgUJWSu2lU/jA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-transform/binding-freebsd-x64@0.152.0': + resolution: {integrity: sha512-L4Y8IfrcaxjK9kK3ddRAyZPa6nzJHn6fgkVIKoBlP53vmCobPL1mtuIdXQThD0VmV6e4aSAJg2An6qkdjeZC8Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-transform/binding-linux-arm-gnueabihf@0.152.0': + resolution: {integrity: sha512-GrohqmIKEK/YXCzAGP8XGQrPVBdUHPpvqdhvw+VutpWZofxDOq39Zed+Gqlea2mycXo+/UNeq1194eaAYkqKQQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-transform/binding-linux-arm-musleabihf@0.152.0': + resolution: {integrity: sha512-wUI+JoZEs4+GntxjK2YOKtQDG822wv+aGYm1OUZ2+NAgi8ghOTUP3RH/GX8Vq0qJ2Yw3OwKLUIZzufXklQ7pCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-transform/binding-linux-arm64-gnu@0.152.0': + resolution: {integrity: sha512-EcY52e0BOfynOA/brCInvQO3ZSukbAEc+BsbpRoRdJ4qV10NK0aS/FheBvkj/V3cFidee6QlrUpqgpN4WKV+Sg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-transform/binding-linux-arm64-musl@0.152.0': + resolution: {integrity: sha512-L4OGw0UuuyYKAPnUvvW+tvq6nS/tBoolVuPekNJqYnDYfe5Nh1619ImfhP0o0Wz+SG4OQW35ByRWjVf/oK3x0w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-transform/binding-linux-ppc64-gnu@0.152.0': + resolution: {integrity: sha512-5K9qNjNKlyhBdGkkIhYFonO/H3Yc37miy5cXefTB71aPOv9jv6HQrDuj6AJcBkggbmkeyEJhlFamUbGMsMGBiA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-transform/binding-linux-riscv64-gnu@0.152.0': + resolution: {integrity: sha512-WlGvnZhmRItWqjsGd2OBKPan75FbqpF4HPuGL7P6f7Oz1VHm6IatnwCK2MI16QXH7BEs6GRohZHpmgThc0V/uw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-transform/binding-linux-riscv64-musl@0.152.0': + resolution: {integrity: sha512-TWZX7QGkU4LF9NS+HBUVWsbQXWDuWt58r+L/31gSJ/iHCJw/3jM2yD408IRVjXYX5jFgmsJbxrUlW3xlVeAL3Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-transform/binding-linux-s390x-gnu@0.152.0': + resolution: {integrity: sha512-VIP7463d0nIAGFv2VXb4mRf20qrpZg0kNHBw8E3fLuHj0bt4HVHDgsqAgeFWOonWwSwsM415TFjNmHhlaStCLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-transform/binding-linux-x64-gnu@0.152.0': + resolution: {integrity: sha512-hwnHMVCnGIoS/eMh7T836mJpWim2iCSWGlCS+aZ+SjhEEUvSSUE54ljua7bk25vaj4L84cFnruTCa7HqyaQ2YQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-transform/binding-linux-x64-musl@0.152.0': + resolution: {integrity: sha512-5NPItMgM9GfIW0SbpOXvEFIijin5VcNEParmBEm9j+RXr8Ko7pZ3a1ydk/Sy+EOvaSTkXMY6TkVyLfczwdudSQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-transform/binding-openharmony-arm64@0.152.0': + resolution: {integrity: sha512-pNzMq4AhBUV8E/cc41SgYTjzW/AWq1DtEKz1l56Mn1TKfidJwP8sGnIw0EXhD6zKFUlPoqkWfvoe5TiDAYeT+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-transform/binding-win32-arm64-msvc@0.152.0': + resolution: {integrity: sha512-PU6cGmVDYo2HHG9ul6vKj189YJPZrcscdWzQakJD6NU4CqvqISbZLUHwz6DVe0v8CbOe2d24zUdipILaRofZ5g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-transform/binding-win32-ia32-msvc@0.152.0': + resolution: {integrity: sha512-Sdcoky580dPgj9Qp+i4zrCV7NpIYZW6bBrcQvOwyDmNlJQ2iYK/V3Q3FGNHLulrxgXrpzqQwMcLs8KRIg0u0yw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-transform/binding-win32-x64-msvc@0.152.0': + resolution: {integrity: sha512-QnN+/Hsig/vI9zoCNVbbqv0GmZWUGqlPg2ZTEyVw/PvgAXd8pRRfN6CxdKEtJTnaVwJFVYaw/5LR6ZWCVBwDeQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@pinojs/redact@0.4.0': resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} @@ -4942,6 +5230,9 @@ packages: '@vitest/expect@4.1.11': resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} + '@vitest/expect@5.0.3': + resolution: {integrity: sha512-N/VGY122AqBx25OB4EatqlVu8PBayvm9dQzpJ8kVq/pOvqAhXTE5MAmoSMzdB8klDU9wcgSEUliwgMNMtxPa1w==} + '@vitest/mocker@4.1.11': resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} peerDependencies: @@ -4956,6 +5247,9 @@ packages: '@vitest/pretty-format@4.1.11': resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} + '@vitest/pretty-format@5.0.3': + resolution: {integrity: sha512-3hr5eV5z2ff0l2Q1vaKQbB4j3M3tElw8stDokEOTqXJB3Es5VrT8gBRIMY59gGuFp8Y61sWlWFK1aNUH5dHDYw==} + '@vitest/runner@4.1.11': resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} @@ -4965,9 +5259,15 @@ packages: '@vitest/spy@4.1.11': resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} + '@vitest/utils@4.1.11': resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} + '@vitest/utils@5.0.3': + resolution: {integrity: sha512-77fDTZv8eu1CzlIfgPU2yrl5MXSUaUAOvgdZEeVm3X3WIxUubIX2SforqS6176lT32HCF6uBVMYVqfHxw7o6eg==} + '@vscode/sudo-prompt@9.3.2': resolution: {integrity: sha512-gcXoCN00METUNFeQOFJ+C9xUI0DKB+0EGMVg7wbVYRHBw2Eq3fKisDZOkRdOz3kqXRKOENMfShPOmypw1/8nOw==} @@ -5088,6 +5388,16 @@ packages: engines: {node: '>=18.18'} hasBin: true + agent-device@0.21.22: + resolution: {integrity: sha512-ndUh5b+lZ2fRzzY80TTl/KmxR4bh+3FezSH0UGCjPCpN//1trSqtzzNUWunc4woY/vW3DEiPx5xYIwG+fBOXWA==} + engines: {node: '>=22.12'} + hasBin: true + peerDependencies: + ai: ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + ai: + optional: true + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} @@ -5592,6 +5902,10 @@ packages: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -5918,6 +6232,31 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + e2e@0.19.0: + resolution: {integrity: sha512-tTWgVR9tQTeESXkAxCh8H/gR4bGxbSbidw07/GngKT5P5tAPdsJ+bW5bQwIaAL/LUTwIG106V64/oiWLXkS29Q==} + engines: {node: ^22.22.3 || >=24.8.0} + hasBin: true + peerDependencies: + '@ai-sdk/anthropic': ^4.0.0 + '@ai-sdk/google': ^4.0.0 + '@ai-sdk/openai': ^4.0.0 + '@ai-sdk/openai-compatible': ^3.0.0 + '@ai-sdk/xai': ^5.0.0 + ai: ^7.0.0 + peerDependenciesMeta: + '@ai-sdk/anthropic': + optional: true + '@ai-sdk/google': + optional: true + '@ai-sdk/openai': + optional: true + '@ai-sdk/openai-compatible': + optional: true + '@ai-sdk/xai': + optional: true + ai: + optional: true + ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} @@ -6291,6 +6630,9 @@ packages: fast-json-stringify@7.0.1: resolution: {integrity: sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==} + fast-png@8.0.0: + resolution: {integrity: sha512-gCysNasJ8KEMgfdYIKd/wTDo6ENK1PWT0RJO7O+0pgmuHPw2O6tA1WvdxFRJoLf9V8yFYpG0FA1YgI8X97OhJA==} + fast-querystring@1.1.2: resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} @@ -6352,6 +6694,9 @@ packages: fetch-nodeshim@0.4.10: resolution: {integrity: sha512-m6I8ALe4L4XpdETy7MJZWs6L1IVMbjs99bwbpIKphxX+0CTns4IKDWJY0LWfr4YsFjfg+z1TjzTMU8lKl8rG0w==} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + figures@6.1.0: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} @@ -6542,6 +6887,9 @@ packages: resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} engines: {node: '>=18'} + get-tsconfig@4.14.3: + resolution: {integrity: sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==} + getenv@2.0.0: resolution: {integrity: sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==} engines: {node: '>=6'} @@ -6820,6 +7168,9 @@ packages: invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + iobuffer@6.0.1: + resolution: {integrity: sha512-SZWYkWNfjIXIBYSDpXDYIgshqtbOPsi4lviawAEceR1Kqk+sHDlcQjWrzNQsii80AyBY0q5c8HCTNjqo74ul+Q==} + ipaddr.js@2.4.0: resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==} engines: {node: '>= 10'} @@ -7178,6 +7529,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stream-stringify@3.0.1: resolution: {integrity: sha512-vuxs3G1ocFDiAQ/SX0okcZbtqXwgj1g71qE9+vrjJ2EkjKQlEFDAcUNRxRU8O+GekV4v5cM2qXP0Wyt/EMDBiQ==} @@ -8070,6 +8424,14 @@ packages: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} + oxc-parser@0.152.0: + resolution: {integrity: sha512-ZztsZgaCWmPeKYMdoTT5e0oV/oxD1NeLWNR3kAtr/G707cUPYaaozRlVDHLCGnszHJRszN9mJwmPNpqgwloC0A==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-transform@0.152.0: + resolution: {integrity: sha512-9rs8qImolzWswXGlhmye9Yz9Mce9hOe4Pivb7rSEhF/6VKn1Q122BUe9Mr1O9xQAoBX+SrF6xXfdgxKHeQf+uQ==} + engines: {node: ^20.19.0 || >=22.12.0} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -8228,6 +8590,10 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} + pixelmatch@8.0.0: + resolution: {integrity: sha512-cIqOLfh5k52us715qf3B3+kTOxZv+WDv77HN7xokLScYQGYQHi4m/ynoDIPpvF2LvCyUJGyvzY64i0QCXtzu1g==} + hasBin: true + pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -8248,6 +8614,10 @@ packages: resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} engines: {node: '>=4.0.0'} + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + portfinder@1.0.38: resolution: {integrity: sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==} engines: {node: '>= 10.12'} @@ -8713,6 +9083,9 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve-workspace-root@2.0.1: resolution: {integrity: sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w==} @@ -9265,6 +9638,10 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tinyrainbow@3.2.0: + resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} + engines: {node: '>=14.0.0'} + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -9775,6 +10152,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.6.1: + resolution: {integrity: sha512-341aRWQsve0rvronKNTqZpjmzdbUDlFuzHaI/XLg/Ej82qffDJRRfBTCuv7+9q/rMjB6LSLyEBnW4InJeMtt/Q==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -9784,6 +10164,10 @@ snapshots: optionalDependencies: typescript: 7.0.2 + '@ai-sdk/provider@4.0.21': + dependencies: + json-schema: 0.4.0 + '@alloc/quick-lru@5.2.0': {} '@ast-grep/napi-darwin-arm64@0.37.0': @@ -10932,6 +11316,13 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} + '@e2e-dev/mobile@0.11.0(e2e@0.19.0)': + dependencies: + agent-device: 0.21.22 + e2e: 0.19.0 + pngjs: 7.0.0 + zod: 4.6.1 + '@emnapi/core@1.11.3': dependencies: '@emnapi/wasi-threads': 1.2.3 @@ -12077,6 +12468,15 @@ snapshots: '@types/react': 19.3.0 react: 19.3.0 + '@modelcontextprotocol/core@2.3.0': + dependencies: + zod: 4.6.1 + + '@modelcontextprotocol/server@2.3.0': + dependencies: + '@modelcontextprotocol/core': 2.3.0 + zod: 4.6.1 + '@modern-js/node-bundle-require@2.67.6': dependencies: '@modern-js/utils': 2.67.6 @@ -12804,8 +13204,126 @@ snapshots: '@noxcturnal/noxcturnal-win32-x64-msvc@0.1.2': optional: true + '@oxc-parser/binding-android-arm-eabi@0.152.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.152.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.152.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.152.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.152.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.152.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.152.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.152.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.152.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.152.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.152.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.152.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.152.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.152.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.152.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.152.0': + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.152.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.152.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.152.0': + optional: true + + '@oxc-project/runtime@0.152.0': {} + '@oxc-project/types@0.141.0': {} + '@oxc-project/types@0.152.0': {} + + '@oxc-transform/binding-android-arm-eabi@0.152.0': + optional: true + + '@oxc-transform/binding-android-arm64@0.152.0': + optional: true + + '@oxc-transform/binding-darwin-arm64@0.152.0': + optional: true + + '@oxc-transform/binding-darwin-x64@0.152.0': + optional: true + + '@oxc-transform/binding-freebsd-x64@0.152.0': + optional: true + + '@oxc-transform/binding-linux-arm-gnueabihf@0.152.0': + optional: true + + '@oxc-transform/binding-linux-arm-musleabihf@0.152.0': + optional: true + + '@oxc-transform/binding-linux-arm64-gnu@0.152.0': + optional: true + + '@oxc-transform/binding-linux-arm64-musl@0.152.0': + optional: true + + '@oxc-transform/binding-linux-ppc64-gnu@0.152.0': + optional: true + + '@oxc-transform/binding-linux-riscv64-gnu@0.152.0': + optional: true + + '@oxc-transform/binding-linux-riscv64-musl@0.152.0': + optional: true + + '@oxc-transform/binding-linux-s390x-gnu@0.152.0': + optional: true + + '@oxc-transform/binding-linux-x64-gnu@0.152.0': + optional: true + + '@oxc-transform/binding-linux-x64-musl@0.152.0': + optional: true + + '@oxc-transform/binding-openharmony-arm64@0.152.0': + optional: true + + '@oxc-transform/binding-win32-arm64-msvc@0.152.0': + optional: true + + '@oxc-transform/binding-win32-ia32-msvc@0.152.0': + optional: true + + '@oxc-transform/binding-win32-x64-msvc@0.152.0': + optional: true + '@pinojs/redact@0.4.0': {} '@pnpm/deps.graph-sequencer@1100.0.1': {} @@ -14608,7 +15126,7 @@ snapshots: '@types/jsonwebtoken@9.0.10': dependencies: '@types/ms': 2.1.0 - '@types/node': 20.19.43 + '@types/node': 26.1.1 '@types/mdast@4.0.4': dependencies: @@ -14678,7 +15196,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 20.19.43 + '@types/node': 26.1.1 '@types/yargs-parser@21.0.3': {} @@ -14770,6 +15288,15 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 + '@vitest/expect@5.0.3': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 5.0.3 + '@vitest/utils': 5.0.3 + chai: 6.2.2 + tinyrainbow: 3.2.0 + '@vitest/mocker@4.1.11(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.51.2)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.11 @@ -14790,6 +15317,10 @@ snapshots: dependencies: tinyrainbow: 3.1.0 + '@vitest/pretty-format@5.0.3': + dependencies: + tinyrainbow: 3.2.0 + '@vitest/runner@4.1.11': dependencies: '@vitest/utils': 4.1.11 @@ -14804,12 +15335,20 @@ snapshots: '@vitest/spy@4.1.11': {} + '@vitest/spy@5.0.3': {} + '@vitest/utils@4.1.11': dependencies: '@vitest/pretty-format': 4.1.11 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@vitest/utils@5.0.3': + dependencies: + '@vitest/pretty-format': 5.0.3 + convert-source-map: 2.0.0 + tinyrainbow: 3.2.0 + '@vscode/sudo-prompt@9.3.2': {} '@webassemblyjs/ast@1.14.1': @@ -14940,6 +15479,8 @@ snapshots: agent-cli-detector@0.1.7: {} + agent-device@0.21.22: {} + ajv-formats@2.1.1: dependencies: ajv: 8.20.0 @@ -15567,6 +16108,8 @@ snapshots: commander@12.1.0: {} + commander@15.0.0: {} + commander@2.20.3: {} commander@4.1.1: {} @@ -15848,6 +16391,23 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + e2e@0.19.0: + dependencies: + '@ai-sdk/provider': 4.0.21 + '@clack/prompts': 1.8.1 + '@jridgewell/remapping': 2.3.5 + '@modelcontextprotocol/server': 2.3.0 + '@oxc-project/runtime': 0.152.0 + '@vitest/expect': 5.0.3 + commander: 15.0.0 + fast-png: 8.0.0 + get-tsconfig: 4.14.3 + oxc-parser: 0.152.0 + oxc-transform: 0.152.0 + picocolors: 1.1.1 + pixelmatch: 8.0.0 + zod: 4.6.1 + ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer: 5.2.1 @@ -16449,6 +17009,11 @@ snapshots: json-schema-ref-resolver: 3.0.0 rfdc: 1.4.1 + fast-png@8.0.0: + dependencies: + fflate: 0.8.3 + iobuffer: 6.0.1 + fast-querystring@1.1.2: dependencies: fast-decode-uri-component: 1.0.1 @@ -16523,6 +17088,8 @@ snapshots: fetch-nodeshim@0.4.10: {} + fflate@0.8.3: {} + figures@6.1.0: dependencies: is-unicode-supported: 2.1.0 @@ -16707,6 +17274,10 @@ snapshots: '@sec-ant/readable-stream': 0.4.1 is-stream: 4.0.1 + get-tsconfig@4.14.3: + dependencies: + resolve-pkg-maps: 1.0.0 + getenv@2.0.0: {} glob-parent@5.1.2: @@ -17092,6 +17663,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + iobuffer@6.0.1: {} + ipaddr.js@2.4.0: {} is-absolute-url@4.0.1: {} @@ -17626,6 +18199,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema@0.4.0: {} + json-stream-stringify@3.0.1: {} json5@2.2.3: {} @@ -18909,6 +19484,52 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 + oxc-parser@0.152.0: + dependencies: + '@oxc-project/types': 0.152.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.152.0 + '@oxc-parser/binding-android-arm64': 0.152.0 + '@oxc-parser/binding-darwin-arm64': 0.152.0 + '@oxc-parser/binding-darwin-x64': 0.152.0 + '@oxc-parser/binding-freebsd-x64': 0.152.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.152.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.152.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.152.0 + '@oxc-parser/binding-linux-arm64-musl': 0.152.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.152.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.152.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.152.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.152.0 + '@oxc-parser/binding-linux-x64-gnu': 0.152.0 + '@oxc-parser/binding-linux-x64-musl': 0.152.0 + '@oxc-parser/binding-openharmony-arm64': 0.152.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.152.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.152.0 + '@oxc-parser/binding-win32-x64-msvc': 0.152.0 + + oxc-transform@0.152.0: + optionalDependencies: + '@oxc-transform/binding-android-arm-eabi': 0.152.0 + '@oxc-transform/binding-android-arm64': 0.152.0 + '@oxc-transform/binding-darwin-arm64': 0.152.0 + '@oxc-transform/binding-darwin-x64': 0.152.0 + '@oxc-transform/binding-freebsd-x64': 0.152.0 + '@oxc-transform/binding-linux-arm-gnueabihf': 0.152.0 + '@oxc-transform/binding-linux-arm-musleabihf': 0.152.0 + '@oxc-transform/binding-linux-arm64-gnu': 0.152.0 + '@oxc-transform/binding-linux-arm64-musl': 0.152.0 + '@oxc-transform/binding-linux-ppc64-gnu': 0.152.0 + '@oxc-transform/binding-linux-riscv64-gnu': 0.152.0 + '@oxc-transform/binding-linux-riscv64-musl': 0.152.0 + '@oxc-transform/binding-linux-s390x-gnu': 0.152.0 + '@oxc-transform/binding-linux-x64-gnu': 0.152.0 + '@oxc-transform/binding-linux-x64-musl': 0.152.0 + '@oxc-transform/binding-openharmony-arm64': 0.152.0 + '@oxc-transform/binding-win32-arm64-msvc': 0.152.0 + '@oxc-transform/binding-win32-ia32-msvc': 0.152.0 + '@oxc-transform/binding-win32-x64-msvc': 0.152.0 + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -19048,6 +19669,10 @@ snapshots: pirates@4.0.7: {} + pixelmatch@8.0.0: + dependencies: + pngjs: 7.0.0 + pkg-dir@4.2.0: dependencies: find-up: 4.1.0 @@ -19068,6 +19693,8 @@ snapshots: pngjs@3.4.0: {} + pngjs@7.0.0: {} + portfinder@1.0.38: dependencies: async: 3.2.6 @@ -19741,6 +20368,8 @@ snapshots: resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve-workspace-root@2.0.1: {} resolve.exports@2.0.3: {} @@ -20339,6 +20968,8 @@ snapshots: tinyrainbow@3.1.0: {} + tinyrainbow@3.2.0: {} + tmpl@1.0.5: {} to-regex-range@5.0.1: @@ -20878,4 +21509,6 @@ snapshots: zod@3.25.76: {} + zod@4.6.1: {} + zwitch@2.0.4: {} From 46a7e94360f98b08644e45dc60cfa2cb5ef559e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Mon, 26 Oct 2026 15:14:42 +0100 Subject: [PATCH 09/21] feat: plugins --- apps/tester-federation-v2/babel.config.js | 14 ++ .../configs/rspack.host-app.mts | 6 + .../configs/webpack.host-app.mts | 6 + .../e2e/nativewind.e2e.ts | 28 +++ .../e2e/reanimated.e2e.ts | 24 +++ apps/tester-federation-v2/global.css | 26 +++ apps/tester-federation-v2/ios/Podfile.lock | 185 ++++++++++++++++++ apps/tester-federation-v2/nativewind-env.d.ts | 3 + apps/tester-federation-v2/package.json | 17 +- .../tester-federation-v2/src/host/FEATURES.ts | 14 +- .../src/host/features/nativewind/Basic.tsx | 14 ++ .../nativewind/ComponentWithVariants.tsx | 25 +++ .../features/nativewind/CustomComponent.tsx | 14 ++ .../src/host/features/nativewind/DarkMode.tsx | 27 +++ .../host/features/nativewind/FuncsDirs.tsx | 33 ++++ .../host/features/nativewind/Responsive.tsx | 27 +++ .../host/features/nativewind/Reusables.tsx | 12 ++ .../host/features/nativewind/ui/Button.tsx | 81 ++++++++ .../src/host/features/nativewind/ui/Text.tsx | 28 +++ .../src/host/features/nativewind/ui/utils.ts | 6 + .../features/reanimated/ReanimatedBox.tsx | 61 ++++++ apps/tester-federation-v2/src/host/index.js | 9 +- .../src/host/navigation/MainNavigator.tsx | 20 +- ...allScreen.tsx => MiniAppInstallScreen.tsx} | 6 +- .../src/host/screens/NativeWindScreen.tsx | 77 ++++++++ .../src/host/screens/ReanimatedScreen.tsx | 34 ++++ apps/tester-federation-v2/tailwind.config.js | 24 +++ pnpm-lock.yaml | 81 ++++++-- 28 files changed, 881 insertions(+), 21 deletions(-) create mode 100644 apps/tester-federation-v2/e2e/nativewind.e2e.ts create mode 100644 apps/tester-federation-v2/e2e/reanimated.e2e.ts create mode 100644 apps/tester-federation-v2/global.css create mode 100644 apps/tester-federation-v2/nativewind-env.d.ts create mode 100644 apps/tester-federation-v2/src/host/features/nativewind/Basic.tsx create mode 100644 apps/tester-federation-v2/src/host/features/nativewind/ComponentWithVariants.tsx create mode 100644 apps/tester-federation-v2/src/host/features/nativewind/CustomComponent.tsx create mode 100644 apps/tester-federation-v2/src/host/features/nativewind/DarkMode.tsx create mode 100644 apps/tester-federation-v2/src/host/features/nativewind/FuncsDirs.tsx create mode 100644 apps/tester-federation-v2/src/host/features/nativewind/Responsive.tsx create mode 100644 apps/tester-federation-v2/src/host/features/nativewind/Reusables.tsx create mode 100644 apps/tester-federation-v2/src/host/features/nativewind/ui/Button.tsx create mode 100644 apps/tester-federation-v2/src/host/features/nativewind/ui/Text.tsx create mode 100644 apps/tester-federation-v2/src/host/features/nativewind/ui/utils.ts create mode 100644 apps/tester-federation-v2/src/host/features/reanimated/ReanimatedBox.tsx rename apps/tester-federation-v2/src/host/screens/{MiniappInstallScreen.tsx => MiniAppInstallScreen.tsx} (90%) create mode 100644 apps/tester-federation-v2/src/host/screens/NativeWindScreen.tsx create mode 100644 apps/tester-federation-v2/src/host/screens/ReanimatedScreen.tsx create mode 100644 apps/tester-federation-v2/tailwind.config.js diff --git a/apps/tester-federation-v2/babel.config.js b/apps/tester-federation-v2/babel.config.js index d4461aec7..dabc63302 100644 --- a/apps/tester-federation-v2/babel.config.js +++ b/apps/tester-federation-v2/babel.config.js @@ -1,4 +1,18 @@ module.exports = { presets: ['module:@react-native/babel-preset'], + plugins: ['react-native-worklets/plugin'], comments: true, + overrides: [ + { + // Regex instead of a relative string: babel resolves strings against the + // launch cwd, and the rspack CLI launches from the repo root. + test: /[\\/]src[\\/]host[\\/]/, + plugins: [ + [ + '@babel/plugin-transform-react-jsx', + { runtime: 'automatic', importSource: 'nativewind' }, + ], + ], + }, + ], }; diff --git a/apps/tester-federation-v2/configs/rspack.host-app.mts b/apps/tester-federation-v2/configs/rspack.host-app.mts index 1e224eeb2..4be6b4cec 100644 --- a/apps/tester-federation-v2/configs/rspack.host-app.mts +++ b/apps/tester-federation-v2/configs/rspack.host-app.mts @@ -1,4 +1,6 @@ import * as Repack from '@callstack/repack'; +import { NativeWindPlugin } from '@callstack/repack-plugin-nativewind'; +import { ReanimatedPlugin } from '@callstack/repack-plugin-reanimated'; import rspack from '@rspack/core'; import { buildSharedSingletons } from './sharedSingletons.mts'; @@ -35,6 +37,9 @@ export default Repack.defineRspackConfig((env) => { ], }, plugins: [ + new NativeWindPlugin({ + cssInteropOptions: { inlineRem: 16 }, + }), new Repack.RepackPlugin({ extraChunks: [ { @@ -66,6 +71,7 @@ export default Repack.defineRspackConfig((env) => { process.env.WITH_PRELOAD === 'true' || process.env.WITH_PRELOAD === '1', }), + new ReanimatedPlugin({ unstable_disableTransform: true }), ], }; }); diff --git a/apps/tester-federation-v2/configs/webpack.host-app.mts b/apps/tester-federation-v2/configs/webpack.host-app.mts index 585e416e5..aa0968401 100644 --- a/apps/tester-federation-v2/configs/webpack.host-app.mts +++ b/apps/tester-federation-v2/configs/webpack.host-app.mts @@ -1,5 +1,7 @@ // @ts-check import * as Repack from '@callstack/repack'; +import { NativeWindPlugin } from '@callstack/repack-plugin-nativewind'; +import { ReanimatedPlugin } from '@callstack/repack-plugin-reanimated'; import webpack from 'webpack'; import { buildSharedSingletons } from './sharedSingletons.mts'; @@ -29,6 +31,9 @@ export default Repack.defineWebpackConfig((env) => { ], }, plugins: [ + new NativeWindPlugin({ + cssInteropOptions: { inlineRem: 16 }, + }), // @ts-expect-error new Repack.RepackPlugin({ extraChunks: [ @@ -62,6 +67,7 @@ export default Repack.defineWebpackConfig((env) => { process.env.WITH_PRELOAD === 'true' || process.env.WITH_PRELOAD === '1', }), + new ReanimatedPlugin({ unstable_disableTransform: true }), ], }; }); diff --git a/apps/tester-federation-v2/e2e/nativewind.e2e.ts b/apps/tester-federation-v2/e2e/nativewind.e2e.ts new file mode 100644 index 000000000..672c2e475 --- /dev/null +++ b/apps/tester-federation-v2/e2e/nativewind.e2e.ts @@ -0,0 +1,28 @@ +import { test } from '@e2e-dev/mobile'; +import { expect } from 'e2e'; + +test('NativeWind styles render, respond and switch to dark', async ({ + app, + screen, +}) => { + await app.open(); + await expect(screen.getByTestId('home-screen')).toBeVisible(); + + const entry = screen.getByTestId('home-nativewind-button'); + await screen.scrollUntilVisible(entry); + await entry.tap(); + await expect(screen.getByTestId('nativewind-screen')).toBeVisible(); + + const padding = screen.getByTestId('nativewind-padding-report'); + await expect(padding).toHaveText('padding-applied: 16'); + + const calcWidth = screen.getByTestId('nativewind-calc-width-report'); + await expect(calcWidth).toHaveText('calc-width: 248'); + + const colorScheme = screen.getByTestId('nativewind-color-scheme-report'); + await expect(colorScheme).toHaveText('color-scheme: light'); + await screen.getByTestId('nativewind-dark-mode-button').tap(); + await expect(colorScheme).toHaveText('color-scheme: dark'); + await screen.getByTestId('nativewind-dark-mode-button').tap(); + await expect(colorScheme).toHaveText('color-scheme: light'); +}); diff --git a/apps/tester-federation-v2/e2e/reanimated.e2e.ts b/apps/tester-federation-v2/e2e/reanimated.e2e.ts new file mode 100644 index 000000000..9f89fb1ff --- /dev/null +++ b/apps/tester-federation-v2/e2e/reanimated.e2e.ts @@ -0,0 +1,24 @@ +import { test } from '@e2e-dev/mobile'; +import { expect } from 'e2e'; + +test('Reanimated spring tracks taps and reset', async ({ app, screen }) => { + await app.open(); + await expect(screen.getByTestId('home-screen')).toBeVisible(); + + const entry = screen.getByTestId('home-reanimated-button'); + await screen.scrollUntilVisible(entry); + await entry.tap(); + await expect(screen.getByTestId('reanimated-screen')).toBeVisible(); + + const width = screen.getByTestId('reanimated-width-report'); + await expect(width).toHaveText('width: 100'); + + await screen.getByTestId('reanimated-click-button').tap(); + await expect(width).toHaveText('width: 150'); + + await screen.getByTestId('reanimated-click-button').tap(); + await expect(width).toHaveText('width: 200'); + + await screen.getByTestId('reanimated-reset-button').tap(); + await expect(width).toHaveText('width: 100'); +}); diff --git a/apps/tester-federation-v2/global.css b/apps/tester-federation-v2/global.css new file mode 100644 index 000000000..14df9aa59 --- /dev/null +++ b/apps/tester-federation-v2/global.css @@ -0,0 +1,26 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +.custom-style-test { + color: mediumpurple; +} + +.calc-element { + width: calc(var(--my-variable) - (20px + 2rem)); +} + +:root { + --H: 200; + --S: 50%; + --L: 40%; + --my-variable: 300px; +} + +.color-element { + background-color: hsl( + calc(var(--H) + 20), + calc(var(--S) - 10%), + calc(var(--L) + 30%) + ); +} diff --git a/apps/tester-federation-v2/ios/Podfile.lock b/apps/tester-federation-v2/ios/Podfile.lock index cf9ca48e6..a7ee3042f 100644 --- a/apps/tester-federation-v2/ios/Podfile.lock +++ b/apps/tester-federation-v2/ios/Podfile.lock @@ -1891,6 +1891,109 @@ PODS: - React-Core - React-jsi - ReactTestApp-Resources (1.0.0-dev) + - RNReanimated (4.7.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNReanimated/apple (= 4.7.1) + - RNReanimated/common (= 4.7.1) + - RNReanimated/view (= 4.7.1) + - RNWorklets + - Yoga + - RNReanimated/apple (4.7.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets + - Yoga + - RNReanimated/common (4.7.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets + - Yoga + - RNReanimated/view (4.7.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets + - Yoga - RNScreens (4.30.0): - hermes-engine - RCTRequired @@ -1940,6 +2043,80 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga + - RNWorklets (0.13.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets/apple (= 0.13.0) + - RNWorklets/common (= 0.13.0) + - Yoga + - RNWorklets/apple (0.13.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - RNWorklets/common (0.13.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga - SocketRocket (0.7.1): - ReactNativeDependencies - SwiftyRSA (1.7.0): @@ -2040,7 +2217,9 @@ DEPENDENCIES: - "ReactNativeHost (from `../../../node_modules/.pnpm/react-native-test-app@5.4.14_@expo+config-plugins@58.0.9_typescript@7.0.2__@react-nativ_454696e0707abccdd63dcea849d4f6b7/node_modules/@rnx-kit/react-native-host`)" - ReactTestApp-DevSupport (from `../node_modules/react-native-test-app`) - ReactTestApp-Resources (from `..`) + - RNReanimated (from `../node_modules/react-native-reanimated`) - RNScreens (from `../node_modules/react-native-screens`) + - RNWorklets (from `../node_modules/react-native-worklets`) - SocketRocket (from `build/rndeps-facades/SocketRocket`) - Yoga (from `build/rncore-facades/Yoga`) @@ -2231,8 +2410,12 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-test-app" ReactTestApp-Resources: :path: ".." + RNReanimated: + :path: "../node_modules/react-native-reanimated" RNScreens: :path: "../node_modules/react-native-screens" + RNWorklets: + :path: "../node_modules/react-native-worklets" SocketRocket: :path: build/rndeps-facades/SocketRocket Yoga: @@ -2330,7 +2513,9 @@ SPEC CHECKSUMS: ReactNativeHost: 12775fd18bc0260080b2a4196dc0522476dde0ce ReactTestApp-DevSupport: 92d3bc3acb9ddf9f90f96976add4577957be1d90 ReactTestApp-Resources: 88392c1cd85eed4e50cc2798de87b790a9791c7f + RNReanimated: b38c0bcd07f37ce6a7b85baf7ad29fbe7eef5f0b RNScreens: 6d0d3b7819284dbf9f222899eb819f069fce4b8b + RNWorklets: ac481cd68c74e369c2835d7cb070cf7c70918c95 SocketRocket: 37aec555668fb852ec12e3c0de59a86ca58f0871 SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6 Yoga: d1c536142c5ff8ec8cd856ab2a7c227a1d875c8e diff --git a/apps/tester-federation-v2/nativewind-env.d.ts b/apps/tester-federation-v2/nativewind-env.d.ts new file mode 100644 index 000000000..958346287 --- /dev/null +++ b/apps/tester-federation-v2/nativewind-env.d.ts @@ -0,0 +1,3 @@ +/// + +// NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind. diff --git a/apps/tester-federation-v2/package.json b/apps/tester-federation-v2/package.json index ed90c3d62..f88dbce83 100644 --- a/apps/tester-federation-v2/package.json +++ b/apps/tester-federation-v2/package.json @@ -20,13 +20,24 @@ "@react-native-async-storage/async-storage": "catalog:testers", "@react-navigation/native": "7.3.14", "@react-navigation/native-stack": "7.18.6", + "@rn-primitives/slot": "^1.2.0", + "@rn-primitives/types": "^1.2.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "nativewind": "^4.2.6", "react": "catalog:", "react-native": "catalog:", + "react-native-css-interop": "0.2.6", + "react-native-reanimated": "catalog:testers", "react-native-safe-area-context": "catalog:testers", - "react-native-screens": "catalog:testers" + "react-native-screens": "catalog:testers", + "react-native-worklets": "catalog:testers", + "tailwind-merge": "^3.5.0" }, "devDependencies": { "@babel/core": "^7.29.7", + "@callstack/repack-plugin-nativewind": "workspace:*", + "@callstack/repack-plugin-reanimated": "workspace:*", "@e2e-dev/mobile": "catalog:testers", "@react-native-community/cli": "catalog:testers", "@react-native-community/cli-platform-android": "catalog:testers", @@ -41,8 +52,12 @@ "@types/jest": "^29.5.13", "@types/node": "catalog:", "@types/react": "catalog:testers", + "autoprefixer": "^10.4.20", "e2e": "catalog:testers", + "postcss": "^8.5.23", + "postcss-loader": "^8.1.1", "react-native-test-app": "catalog:testers", + "tailwindcss": "^3.4.17", "terser-webpack-plugin": "catalog:", "typescript": "catalog:", "webpack": "catalog:" diff --git a/apps/tester-federation-v2/src/host/FEATURES.ts b/apps/tester-federation-v2/src/host/FEATURES.ts index d21bc9e3b..1eed807c8 100644 --- a/apps/tester-federation-v2/src/host/FEATURES.ts +++ b/apps/tester-federation-v2/src/host/FEATURES.ts @@ -43,7 +43,19 @@ export const FEATURES: Feature[] = [ key: 'miniapp-install', name: 'Mini-app install', group: 'Chunks', - screen: 'MiniappInstall', + screen: 'MiniAppInstall', + }, + { + key: 'nativewind', + name: 'NativeWind', + group: 'Plugins', + screen: 'NativeWind', + }, + { + key: 'reanimated', + name: 'Reanimated', + group: 'Plugins', + screen: 'Reanimated', }, ]; diff --git a/apps/tester-federation-v2/src/host/features/nativewind/Basic.tsx b/apps/tester-federation-v2/src/host/features/nativewind/Basic.tsx new file mode 100644 index 000000000..788c0ceb1 --- /dev/null +++ b/apps/tester-federation-v2/src/host/features/nativewind/Basic.tsx @@ -0,0 +1,14 @@ +import { Text, View } from 'react-native'; + +export default function Basic() { + return ( + + + Colored through CSS! + + + ); +} diff --git a/apps/tester-federation-v2/src/host/features/nativewind/ComponentWithVariants.tsx b/apps/tester-federation-v2/src/host/features/nativewind/ComponentWithVariants.tsx new file mode 100644 index 000000000..7292f7e34 --- /dev/null +++ b/apps/tester-federation-v2/src/host/features/nativewind/ComponentWithVariants.tsx @@ -0,0 +1,25 @@ +import { Text } from 'react-native'; +import React from 'react'; + +const variantStyles = { + default: 'rounded', + primary: 'bg-blue-500 text-white', +}; + +type Props = { + variant: keyof typeof variantStyles; + className?: string; +} & React.ComponentProps; + +export default function ComponentWithVariants({ + variant, + className, + ...props +}: Props) { + const merged = [variantStyles.default, variantStyles[variant], className] + .filter(Boolean) + .join(' '); + return ( + + ); +} diff --git a/apps/tester-federation-v2/src/host/features/nativewind/CustomComponent.tsx b/apps/tester-federation-v2/src/host/features/nativewind/CustomComponent.tsx new file mode 100644 index 000000000..4b9411362 --- /dev/null +++ b/apps/tester-federation-v2/src/host/features/nativewind/CustomComponent.tsx @@ -0,0 +1,14 @@ +import { Text } from 'react-native'; + +import { cn } from './ui/utils'; + +export default function CustomComponent({ className }: { className: string }) { + return ( + + I am a custom component + + ); +} diff --git a/apps/tester-federation-v2/src/host/features/nativewind/DarkMode.tsx b/apps/tester-federation-v2/src/host/features/nativewind/DarkMode.tsx new file mode 100644 index 000000000..ddec886d5 --- /dev/null +++ b/apps/tester-federation-v2/src/host/features/nativewind/DarkMode.tsx @@ -0,0 +1,27 @@ +import { colorScheme } from 'nativewind'; +import { Button, Text, View } from 'react-native'; + +export default function DarkMode() { + return ( + + + ); +} diff --git a/apps/tester-federation-v2/src/host/features/nativewind/ui/Button.tsx b/apps/tester-federation-v2/src/host/features/nativewind/ui/Button.tsx new file mode 100644 index 000000000..19693965a --- /dev/null +++ b/apps/tester-federation-v2/src/host/features/nativewind/ui/Button.tsx @@ -0,0 +1,81 @@ +import { cva, type VariantProps } from 'class-variance-authority'; +import * as React from 'react'; +import { Pressable } from 'react-native'; +import { TextClassContext } from './Text'; +import { cn } from './utils'; + +const buttonVariants = cva( + 'group flex items-center justify-center rounded-md', + { + variants: { + variant: { + default: 'bg-primary active:opacity-90', + primary: 'bg-primary active:opacity-90', + }, + size: { + default: 'h-10 px-4 py-2 native:h-12 native:px-5 native:py-3', + sm: 'h-9 rounded-md px-3', + lg: 'h-11 rounded-md px-8 native:h-14', + icon: 'h-10 w-10', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + } +); + +const buttonTextVariants = cva( + 'web:whitespace-nowrap text-sm native:text-base font-medium text-foreground web:transition-colors', + { + variants: { + variant: { + default: 'text-primary-foreground', + primary: 'text-primary-foreground', + }, + size: { + default: '', + sm: '', + lg: 'native:text-lg', + icon: '', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + } +); + +type ButtonProps = React.ComponentPropsWithoutRef & + VariantProps; + +const Button = React.forwardRef< + React.ElementRef, + ButtonProps +>(({ className, variant, size, ...props }, ref) => { + return ( + + + + ); +}); +Button.displayName = 'Button'; + +export type { ButtonProps }; +export { Button, buttonTextVariants, buttonVariants }; diff --git a/apps/tester-federation-v2/src/host/features/nativewind/ui/Text.tsx b/apps/tester-federation-v2/src/host/features/nativewind/ui/Text.tsx new file mode 100644 index 000000000..a933f0829 --- /dev/null +++ b/apps/tester-federation-v2/src/host/features/nativewind/ui/Text.tsx @@ -0,0 +1,28 @@ +import * as Slot from '@rn-primitives/slot'; +import type { SlottableTextProps, TextRef } from '@rn-primitives/types'; +import * as React from 'react'; +import { Text as RNText } from 'react-native'; +import { cn } from './utils'; + +const TextClassContext = React.createContext(undefined); + +const Text = React.forwardRef( + ({ className, asChild = false, ...props }, ref) => { + const textClass = React.useContext(TextClassContext); + const Component = asChild ? Slot.Text : RNText; + return ( + + ); + } +); +Text.displayName = 'Text'; + +export { Text, TextClassContext }; diff --git a/apps/tester-federation-v2/src/host/features/nativewind/ui/utils.ts b/apps/tester-federation-v2/src/host/features/nativewind/ui/utils.ts new file mode 100644 index 000000000..9ad0df426 --- /dev/null +++ b/apps/tester-federation-v2/src/host/features/nativewind/ui/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/apps/tester-federation-v2/src/host/features/reanimated/ReanimatedBox.tsx b/apps/tester-federation-v2/src/host/features/reanimated/ReanimatedBox.tsx new file mode 100644 index 000000000..0a580b310 --- /dev/null +++ b/apps/tester-federation-v2/src/host/features/reanimated/ReanimatedBox.tsx @@ -0,0 +1,61 @@ +import * as React from 'react'; +import { Button, StyleSheet, View } from 'react-native'; +import Animated, { useSharedValue, withSpring } from 'react-native-reanimated'; + +const BASE_WIDTH = 100; + +export default function ReanimatedBox({ + onWidthChange, +}: { + onWidthChange: (width: number) => void; +}) { + const width = useSharedValue(BASE_WIDTH); + const target = React.useRef(BASE_WIDTH); + + const handlePress = () => { + target.current += 50; + width.value = withSpring(target.current); + onWidthChange(target.current); + }; + + const handleReset = () => { + target.current = BASE_WIDTH; + width.value = withSpring(target.current); + onWidthChange(target.current); + }; + + return ( + + + +