diff --git a/.changeset/calm-tasms-federate.md b/.changeset/calm-tasms-federate.md new file mode 100644 index 00000000000..d82a9688f28 --- /dev/null +++ b/.changeset/calm-tasms-federate.md @@ -0,0 +1,8 @@ +--- +'@module-federation/runtime-core': patch +'@module-federation/webpack-bundler-runtime': patch +--- + +Avoid logical assignment syntax and use a standards-safe own-property check in +embedded federation runtime initializers so ES2019 and Lynx TASM consumers can +parse the generated runtime. diff --git a/.changeset/tiny-lynxes-federate.md b/.changeset/tiny-lynxes-federate.md new file mode 100644 index 00000000000..622dc41c909 --- /dev/null +++ b/.changeset/tiny-lynxes-federate.md @@ -0,0 +1,32 @@ +--- +'@module-federation/lynx': minor +'@module-federation/runtime-core': patch +'@module-federation/sdk': patch +--- + +Add an Rspeedy build adapter plus background and Lynx for Web main-thread +transports. Dual-realm remotes publish a standard Module Federation manifest +whose remote entry resolves to one HTTP-loadable external `.lynx.bundle`. +Compiled federated `import()` calls skip Lynx's local loader for remote-only +chunk IDs while preserving normal local JavaScript chunk loading. + +Load non-eager host, remote-exposure, and descendant chunks through Lynx's +public lazy-bundle API. Split federation requires the Lynx Web Core and template +plugin releases that preserve automatic public paths, expose retryable lazy +loading, and omit assetless remote-only chunk groups. +Bootstrap the ReactLynx lazy-bundle loader before federation async startup so +initial shared chunks use FetchBundle instead of the legacy component loader. +Embed the paired container entry in native split remotes so fetched main-thread +chunks install their snapshot factories before React commits them, and reject +remote entries that omit the paired section. Emit that entry before the +background runtime wrapper so native main-thread evaluation does not require +the background-only `tt` module loader. +Accept the cache-events plugin 0.2 line used by Rspeedy 0.16. + +Resolve manifest `publicPath: 'auto'` from the fetched response URL so +root-relative manifest entries remain portable in browser-like runtimes. +Normalize Lynx split-bundle public paths so relative, root-relative, absolute, +and protocol-relative asset prefixes work with or without boundary slashes. +Self-label generated Lynx external bundles as external/non-lazy in encoded +bundle config so Web Core can prefer producer metadata over runtime caller +fallbacks. diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ddda12f3d8f..61854a94924 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -149,6 +149,12 @@ jobs: uses: ./.github/workflows/e2e-runtime.yml secrets: inherit + e2e-lynx: + needs: checkout-install + if: ${{ fromJSON(needs.checkout-install.outputs.e2e_suites).lynx }} + uses: ./.github/workflows/e2e-lynx.yml + secrets: inherit + e2e-manifest: needs: checkout-install if: ${{ fromJSON(needs.checkout-install.outputs.e2e_suites).manifest }} diff --git a/.github/workflows/e2e-lynx.yml b/.github/workflows/e2e-lynx.yml new file mode 100644 index 00000000000..079fca28702 --- /dev/null +++ b/.github/workflows/e2e-lynx.yml @@ -0,0 +1,246 @@ +# .github/workflows/e2e-lynx.yml +name: E2E Test for Lynx Module Federation + +on: + workflow_call: + +jobs: + e2e-lynx-native: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout Repository + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + with: + fetch-depth: 0 + + - name: Setup pnpm + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 + + - name: Setup Node.js 24 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: '24' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' + + - name: Export SKIP_DEVTOOLS_POSTINSTALL + run: echo "SKIP_DEVTOOLS_POSTINSTALL=true" >> "$GITHUB_ENV" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Validate Lynx and Android CI policy + run: pnpm --filter lynx-module-federation-demo run test:ci-policy + + - name: Restore Turborepo cache + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 + with: + path: .turbo/cache + key: ${{ runner.os }}-turbo-${{ github.ref_name }}-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-turbo-${{ github.ref_name }}- + ${{ runner.os }}-turbo- + + - name: Build shared packages + run: pnpm run build:packages + + - name: Test Lynx federation compiler and transport + run: pnpm --filter @module-federation/lynx test + + - name: Build and validate native Lynx artifacts + run: pnpm --filter lynx-module-federation-demo run e2e:native:ci + + - name: Validate standalone iOS project policy + run: pnpm --filter lynx-module-federation-demo run test:ios-project + + - name: Upload native artifacts for iOS + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: lynx-native-ios-input + path: | + apps/lynx-module-federation-demo/dist/catalog-native/ + apps/lynx-module-federation-demo/dist/host-native/ + apps/lynx-module-federation-demo/dist/remote-native/ + if-no-files-found: error + retention-days: 1 + + - name: Upload native failure artifacts + if: failure() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: lynx-native-e2e-failure + path: | + apps/lynx-module-federation-demo/dist/catalog-native/ + apps/lynx-module-federation-demo/dist/host-native/ + apps/lynx-module-federation-demo/dist/remote-native/ + if-no-files-found: ignore + retention-days: 7 + + e2e-lynx-web: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout Repository + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + with: + fetch-depth: 0 + + - name: Setup pnpm + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 + + - name: Setup Node.js 24 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: '24' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' + + - name: Export SKIP_DEVTOOLS_POSTINSTALL + run: echo "SKIP_DEVTOOLS_POSTINSTALL=true" >> "$GITHUB_ENV" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Restore Turborepo cache + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 + with: + path: .turbo/cache + key: ${{ runner.os }}-turbo-${{ github.ref_name }}-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-turbo-${{ github.ref_name }}- + ${{ runner.os }}-turbo- + + - name: Build shared packages + run: pnpm run build:packages + + - name: Install Playwright Chromium + run: pnpm --filter lynx-module-federation-demo exec playwright install --with-deps chromium + + - name: Run real Lynx for Web E2E + run: pnpm --filter lynx-module-federation-demo run e2e:web:ci + + - name: Upload Web E2E failure artifacts + if: failure() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: lynx-module-federation-e2e-failure + path: | + apps/lynx-module-federation-demo/test/real-web/artifacts/ + apps/lynx-module-federation-demo/dist/catalog-web/ + apps/lynx-module-federation-demo/dist/host-web/ + apps/lynx-module-federation-demo/dist/remote-web/ + if-no-files-found: ignore + retention-days: 7 + + e2e-lynx-ios: + needs: e2e-lynx-native + runs-on: macos-15 + timeout-minutes: 120 + env: + DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer + NO_COLOR: 1 + RUBY_VERSION: 3.4.9 + steps: + - name: Checkout Repository + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + with: + fetch-depth: 1 + sparse-checkout: apps/lynx-module-federation-demo + + - name: Setup Node.js 24 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: '24' + + - name: Setup Ruby + uses: ruby/setup-ruby@97ecb7b512899eb71ab1bf2310a624c6f1589ac6 # v1 + with: + ruby-version: ${{ env.RUBY_VERSION }} + + - name: Restore iOS build cache + id: ios-build-cache + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 + with: + path: | + ~/.cocoapods/repos/trunk + ~/Library/Caches/CocoaPods + apps/lynx-module-federation-demo/ios/Pods + apps/lynx-module-federation-demo/ios/OrbitControl.xcworkspace + apps/lynx-module-federation-demo/ios/vendor/bundle + apps/lynx-module-federation-demo/ios/build/DerivedData/Build + apps/lynx-module-federation-demo/ios/build/DerivedData/ModuleCache.noindex + apps/lynx-module-federation-demo/ios/build/DerivedData/SDKStatCaches.noindex + key: ${{ runner.os }}-${{ runner.arch }}-lynx-ios-build-v3-xcode-16.4-ruby-${{ env.RUBY_VERSION }}-${{ hashFiles('apps/lynx-module-federation-demo/ios/Gemfile', 'apps/lynx-module-federation-demo/ios/Gemfile.lock', 'apps/lynx-module-federation-demo/ios/Podfile', 'apps/lynx-module-federation-demo/ios/Podfile.lock') }}-${{ hashFiles('apps/lynx-module-federation-demo/ios/OrbitControl.xcodeproj/project.pbxproj') }} + + - name: Download native artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: lynx-native-ios-input + path: apps/lynx-module-federation-demo/dist + + - name: Embed native host bundle + run: node apps/lynx-module-federation-demo/scripts/sync-ios-bundle.mjs + + - name: Install iOS dependencies + if: steps.ios-build-cache.outputs.cache-hit != 'true' + working-directory: apps/lynx-module-federation-demo/ios + run: | + bundle config set path vendor/bundle + bundle install --jobs 4 --retry 3 + bundle exec pod install --deployment + + - name: Validate cached iOS dependencies + if: steps.ios-build-cache.outputs.cache-hit == 'true' + working-directory: apps/lynx-module-federation-demo/ios + run: | + bundle config set path vendor/bundle + bundle check + cmp Podfile.lock Pods/Manifest.lock + test -f OrbitControl.xcworkspace/contents.xcworkspacedata + + - name: Run standalone iOS federation E2E + run: node apps/lynx-module-federation-demo/test/ios/run.mjs + + - name: Save iOS build cache + if: >- + always() && + steps.ios-build-cache.outputs.cache-hit != 'true' && + hashFiles('apps/lynx-module-federation-demo/ios/build/DerivedData/Build/Products/Release-iphonesimulator/OrbitControl.app/OrbitControl') != '' + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 + with: + path: | + ~/.cocoapods/repos/trunk + ~/Library/Caches/CocoaPods + apps/lynx-module-federation-demo/ios/Pods + apps/lynx-module-federation-demo/ios/OrbitControl.xcworkspace + apps/lynx-module-federation-demo/ios/vendor/bundle + apps/lynx-module-federation-demo/ios/build/DerivedData/Build + apps/lynx-module-federation-demo/ios/build/DerivedData/ModuleCache.noindex + apps/lynx-module-federation-demo/ios/build/DerivedData/SDKStatCaches.noindex + key: ${{ runner.os }}-${{ runner.arch }}-lynx-ios-build-v3-xcode-16.4-ruby-${{ env.RUBY_VERSION }}-${{ hashFiles('apps/lynx-module-federation-demo/ios/Gemfile', 'apps/lynx-module-federation-demo/ios/Gemfile.lock', 'apps/lynx-module-federation-demo/ios/Podfile', 'apps/lynx-module-federation-demo/ios/Podfile.lock') }}-${{ hashFiles('apps/lynx-module-federation-demo/ios/OrbitControl.xcodeproj/project.pbxproj') }} + + - name: Upload iOS E2E evidence + if: success() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: lynx-ios-e2e + path: | + apps/lynx-module-federation-demo/ios/build/orbit-control.png + apps/lynx-module-federation-demo/ios/build/requests.json + apps/lynx-module-federation-demo/ios/build/simulator.log + if-no-files-found: error + retention-days: 7 + + - name: Upload iOS E2E failure artifacts + if: failure() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: lynx-ios-e2e-failure + path: | + apps/lynx-module-federation-demo/ios/build/OrbitControl-Release.xcresult/ + apps/lynx-module-federation-demo/ios/build/orbit-control.png + apps/lynx-module-federation-demo/ios/build/requests.json + apps/lynx-module-federation-demo/ios/build/simulator.log + if-no-files-found: ignore + retention-days: 7 diff --git a/.github/workflows/e2e-metro.yml b/.github/workflows/e2e-metro.yml index 01b33b19692..b0b5e9dd951 100644 --- a/.github/workflows/e2e-metro.yml +++ b/.github/workflows/e2e-metro.yml @@ -23,7 +23,7 @@ jobs: ANDROID_EMULATOR_API_LEVEL: 28 ANDROID_EMULATOR_TARGET: default ANDROID_EMULATOR_ARCH: x86_64 - ANDROID_EMULATOR_DISK_SPACE: 1024M + ANDROID_EMULATOR_PARTITION_SIZE_MB: 1024 ANDROID_EMULATOR_RAM_SIZE: 256M ANDROID_EMULATOR_HEAP_SIZE: 256M ANDROID_EMULATOR_BOOT_TIMEOUT: 2700 @@ -110,11 +110,12 @@ jobs: arch: ${{ env.ANDROID_EMULATOR_ARCH }} ram-size: ${{ env.ANDROID_EMULATOR_RAM_SIZE }} heap-size: ${{ env.ANDROID_EMULATOR_HEAP_SIZE }} - disk-size: ${{ env.ANDROID_EMULATOR_DISK_SPACE }} + disk-size: ${{ env.ANDROID_EMULATOR_PARTITION_SIZE_MB }}M emulator-boot-timeout: ${{ env.ANDROID_EMULATOR_BOOT_TIMEOUT }} force-avd-creation: false disable-animations: true - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + # Emulator 36.6 can ignore the AVD disk-size config unless this is explicit. + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -partition-size ${{ env.ANDROID_EMULATOR_PARTITION_SIZE_MB }} script: | node tools/scripts/run-metro-e2e.mjs --platform=android --appName=${{ env.METRO_APP_NAME }} --skip-rock-cache-auth diff --git a/apps/lynx-module-federation-demo/.gitignore b/apps/lynx-module-federation-demo/.gitignore new file mode 100644 index 00000000000..61246030974 --- /dev/null +++ b/apps/lynx-module-federation-demo/.gitignore @@ -0,0 +1,12 @@ +dist/ +.lynx-e2e-*/ +.turbo/ +test/real-web/artifacts/ +ios/.bundle/ +ios/Pods/ +ios/OrbitControl.xcworkspace/ +ios/Resources/main.lynx.bundle +ios/Resources/host-native/ +ios/Resources/static/ +ios/build/ +ios/vendor/bundle/ diff --git a/apps/lynx-module-federation-demo/README.md b/apps/lynx-module-federation-demo/README.md new file mode 100644 index 00000000000..bed047b20cc --- /dev/null +++ b/apps/lynx-module-federation-demo/README.md @@ -0,0 +1,250 @@ +# Lynx Module Federation demo + +This is an official Rspeedy + ReactLynx application, not a browser-only +simulation. It includes: + +- a standalone UIKit iOS application derived from Lynx's official + `HelloLynxSwift` starter; +- a runnable Orbit Catalog product for native Lynx and Lynx Web; +- a native background host with paired ReactLynx remote UI built as + `.lynx.bundle` artifacts; +- a Lynx for Web host mounted in the official `` custom element; +- compile-time `import ... from 'catalog/Card'`, dynamic + `import('catalog/Details')`, and runtime `loadRemote()` consumers; +- three lazy remote screens plus a nested remote chunk loaded over HTTP from + `mf-manifest.json`; +- host, Card, Details, and ActivityFeed consumers of one shared singleton; +- independent background and main-thread layer scopes. + +The remote uses the default split transport: + +```mermaid +flowchart LR + S["Catalog product source"] --> A["Standalone Catalog app"] + S --> E["Federation exposes"] + A -->|"direct imports"| X["Card + Details + ActivityFeed"] + E --> X + H["Orbit Control host"] -->|"GET"| M["mf-manifest.json"] + H -->|"asyncStartup"| T["host startup lazy .bundle"] + M -->|"remoteEntry"| C["catalog.*.lynx.bundle"] + C --> R["container only"] + R -->|"on demand"| F["ActivityFeed .bundle"] + F -->|"dynamic import"| N["activity-metadata .bundle"] + R -->|"on demand"| G["Card .bundle"] + R -->|"on demand"| D["Details .bundle"] +``` + +The host never addresses a generated `remoteEntry.js` directly. Both import +styles resolve the manifest, whose `metaData.remoteEntry` names the public +`.lynx.bundle` container. Lazy expose bundles are fetched separately. The Web +remote uses `publicPath: 'auto'`; Module Federation resolves its container and +split assets from the fetched manifest URL instead of Lynx Web's internal +`document.currentScript`. Native remotes use the explicit +`LYNX_REMOTE_ORIGIN` because they have no browser script URL. Native host assets +default to `/host-native/`, which works for a root deployment and the bundled +iOS resource provider; set `LYNX_HOST_ORIGIN` when they live elsewhere. + +## Rspeedy compatibility boundary + +Rspeedy 0.16 resolves its own `@rspack/core`, while this repository needs +`@rspack-canary/core` 2.1.5-canary-54a0d8f3-20260715194831 for the Lynx layer +and chunk behavior under test. `rspack-canary-rspeedy.mjs` is the single +compatibility boundary: it starts Rspeedy with Node resolution hooks that map +`@rspack/core` to the pinned canary and `@rsbuild/core` to the workspace's +matching package. + +Every demo build, development, and preview script in `package.json` invokes +that wrapper. Application and federation source import neither the wrapper nor +the canary package. Remove the wrapper when Rspeedy supports the repository's +Rspack package directly; then point those package scripts back to the public +Rspeedy CLI and remove the canary alias together. + +The repository also applies version-scoped pnpm patches to the published +ReactLynx, template plugin, and Web Core packages. They contain the fixes from +[lynx-family/lynx-stack#3043](https://github.com/lynx-family/lynx-stack/pull/3043) +needed by the public `FetchBundle` transport. The Web Core patch is built from +the matching `0.22.2` source baseline, so its JavaScript keeps the WASM asset +names shipped by that package. Remove all three patches together after those +upstream fixes are released. + +## Run the standalone Catalog product + +The Catalog directly renders `Card`, `Details`, and `ActivityFeed` from the +same source files published by `federation.config.mjs`. It is a complete app, +not an artifact placeholder: + +```sh +pnpm dev:catalog:native +pnpm dev:catalog:web +``` + +The native command prints a QR code for Lynx Explorer on port 3001. Production +builds emit regular root apps at `dist/catalog-native/main.lynx.bundle` and +`dist/catalog-web/main.web.bundle`. + +Catalog app and federation transport are separate Rspeedy builds by design. +ReactLynx's `experimental_isLazyBundle` mode applies to the whole compilation +and emits `DynamicComponent` bundles, so the provider build contains only the +manifest, container, three lazy expose roots, and ActivityFeed's nested lazy +chunk. The regular Catalog build bundles +its local shared-state implementation; the provider marks that implementation +`import: false`, so Orbit supplies the negotiated singleton and does not +download Catalog's standalone app or duplicate shared state. + +The federation configs explicitly enable `experiments.asyncStartup`. Shared +modules do not use `eager: true`: the host waits for share-scope initialization +and loads its singleton provider from a host `lazy-bundle/*.bundle` before +application startup. The remote's `import: false` consumer then reuses that +initialized background-realm singleton. + +Every ReactLynx build targets engine 3.9. That selects ReactLynx's public +`FetchBundle` and `loadScript` lazy-bundle transport, which can resolve the +host's non-eager startup share before the first render. Both host builds use +the built-in `jsReady` first-screen handoff; no private or manual readiness API +is involved. + +`src/app/staticCard.ts` is the standard asynchronous bootstrap boundary. It +contains a module-scope static `import * as card from 'catalog/Card'`; importing +that local boundary asynchronously delays evaluation without replacing the +federated import with a runtime loader. + +On iOS, root-relative host and standalone Catalog lazy-bundle paths resolve +against an HTTP root bundle's origin. Embedded Release host assets resolve +against the signed app bundle. + +## Run the standalone iOS app + +The app under `ios/` is a real iPhone/iPad application embedding `LynxView`. +It does not require Lynx Explorer. Its exact official starter source and commit +are recorded in `ios/UPSTREAM.md`. + +On macOS: + +```sh +pnpm ios:prepare +pnpm ios:pods +pnpm ios:open +pnpm dev +``` + +Run the `OrbitControl` scheme in Xcode. The Debug app loads +`http://localhost:3000/main.lynx.bundle`; set the `LYNX_BUNDLE_URL` scheme +environment variable to a LAN-reachable URL for a physical device. The shell +injects both Lynx template and generic resource fetchers, so the root Bundle, +federated container, and Lazy Bundles can arrive over HTTP(S). + +The same shell can launch Catalog as an independent native product by setting +`LYNX_BUNDLE_URL` to `http://localhost:3000/catalog-native/main.lynx.bundle`. +The iOS E2E launches both root apps, interacts with Catalog locally, then proves +that Orbit loads those component sources through federation. + +For a physical device, use the same LAN origin for both the host bundle and +the manifest URL compiled into it: + +```sh +export LYNX_REMOTE_ORIGIN=http://:3000 +pnpm ios:prepare +pnpm ios:device +``` + +Set the Xcode scheme's `LYNX_BUNDLE_URL` to +`http://:3000/main.lynx.bundle`. `ios:device` rejects loopback +origins and binds Rspeedy to `0.0.0.0`, so the phone can fetch the host, +manifest, container, and lazy bundles from the same server. + +Release builds embed `ios/Resources/main.lynx.bundle`. ATS permits only local +networking for the simulator/LAN demo; arbitrary and public insecure HTTP loads +remain disabled. Build the host with the production manifest origin before +syncing it: + +```sh +LYNX_REMOTE_ORIGIN=https://cdn.example.com/catalog/ pnpm build:native +pnpm ios:sync +``` + +The root host bundle and its non-eager async-startup lazy bundles are embedded. +The manifest, container, and lazy expose bundles remain separately deployable +HTTP(S) artifacts. One Release simulator build runs all three UI scenarios: it +launches the app, taps **Load remote catalog**, verifies compiled imports, +runtime `loadRemote()`, and shared singleton identity, then checks every native +bundle request observed by the test server; launches Catalog as a standalone +root; and launches without a root URL override to prove the embedded host. + +## Run with Lynx Explorer + +Build the official native host and remote and validate their manifests: + +```sh +pnpm e2e:native +``` + +For Lynx Explorer on a phone, use a LAN-reachable origin and bind the Rspeedy +server to the network: + +```sh +LYNX_DEV_HOST=0.0.0.0 \ +LYNX_REMOTE_ORIGIN=http://:3000 \ + pnpm dev +``` + +The configured official `pluginQRCode()` prints the app QR code. Open Lynx +Explorer on the device and scan it. The phone must be able to reach +`:3000`; `127.0.0.1` refers to the phone itself and will not work. +Set `CATALOG_NATIVE_MANIFEST_URL` when the remote manifest is hosted elsewhere. + +`e2e:native` is artifact and transport validation. It compiles the real Rspeedy +host, Catalog app, and remote, verifies the regular standalone root bundle, two +separately transported host lazy bundles (including the async-startup +singleton), the background container, three independently loadable remote UI +roots, and ActivityFeed's nested dynamic-import bundle; checks the manifest's +public background expose/share metadata; then fetches every artifact over HTTP. +The macOS CI job adds a real iOS Simulator runtime test of those artifacts. + +## Run the real Lynx Web E2E + +Install Chromium once, then run: + +```sh +pnpm exec playwright install chromium +pnpm e2e:web +``` + +The test builds the official Rspeedy web host, Catalog app, and remote; starts +an ephemeral HTTP server; and mounts both `dist/host-web/main.web.bundle` and +`dist/catalog-web/main.web.bundle` through +`@lynx-js/web-core`'s public ``. Playwright uses a mobile viewport +and touch input. The server indexes realpath-contained artifacts before it +listens, so URL input never becomes a filesystem path and internal errors are +not returned to clients. The test verifies: + +- manifest, container, lazy expose, and nested remote chunk requests over HTTP; +- async-startup share initialization over its host lazy-bundle request; +- static import, dynamic `import()`, and runtime `loadRemote()` results; +- rendering and navigation through the ReactLynx UI, including output from the + nested dynamic import; +- shared-state identity and mutations across host and remote consumers; +- direct local composition and shared state in the standalone Catalog; +- no federation requests while Catalog runs through direct imports; +- no Lynx, page, or console errors. + +A failed run writes `test/real-web/artifacts/failure.png`. Override artifact +paths with `LYNX_HOST_WEB_BUNDLE`, `LYNX_REMOTE_MANIFEST`, +`LYNX_REMOTE_WEB_BUNDLE`, `LYNX_CATALOG_WEB_BUNDLE`, and +`LYNX_WEB_E2E_SCREENSHOT`. + +## Test matrix + +| Command | Evidence | +| ----------------------- | ---------------------------------------------------------------- | +| `pnpm e2e:native` | Native Catalog + host/provider binary and transport validation | +| `pnpm e2e:ios` | iOS Catalog launch plus Orbit federation/runtime E2E | +| `pnpm e2e:web` | Standalone Catalog and federated Orbit in official `` | +| `pnpm test:ios-project` | Cross-platform iOS project, provenance, pod, and ATS policy gate | +| `pnpm test:ci-policy` | Wrapper ownership and Android emulator partition policy | +| `pnpm test` | Cross-platform native artifact and Lynx Web checks | + +The web remote enables `mainThread: true`, so each exposure has background and +main-thread variants. The demo deliberately scopes `orbit-shared-state` to +the semantic `background` realm; E2E proves singleton identity across the host +and all remotes in that realm. It does not claim cross-thread identity because +JavaScript objects cannot cross Lynx's realm boundary. diff --git a/apps/lynx-module-federation-demo/design/architecture-refactor.md b/apps/lynx-module-federation-demo/design/architecture-refactor.md new file mode 100644 index 00000000000..3c1eb8e5344 --- /dev/null +++ b/apps/lynx-module-federation-demo/design/architecture-refactor.md @@ -0,0 +1,287 @@ +# Lynx Federation Architecture Refactor Design + +## Status + +Approved in the Codex task on 2026-07-20. +Stored with the tracked Lynx demo design artifacts because repository `/docs` +is generated output. + +## Objective + +Refactor the Lynx Module Federation implementation and demos until the +architecture is direct, testable, and maintainable without changing the +already-proven behavior: + +- no eager shares; +- native Lynx, iOS, Web, and Node-compatible manifest loading; +- standalone Catalog and federated host flows; +- `asyncStartup` transport; +- native split and single-chunk remote bundles; +- `publicPath: 'auto'` on Web and resolved absolute native origins; +- real Web and iOS E2E coverage; +- optimized CI with retained diagnostics. + +The refactor must address every finding from the thermonuclear review and keep +the PR's public API and emitted artifact contracts stable. + +## Constraints + +- Prefer public Lynx/Rspeedy APIs. A private adapter is permitted only when no + exported configuration surface exists, and then it must be isolated, + version-bounded, and compatibility-tested. +- Preserve synchronous Lynx lazy-bundle thenable semantics. Converting the + official thenable to a native Promise is not behavior-preserving. +- Keep compilation-scoped data out of compiler/plugin-instance state. +- Do not add eager shares, new runtime dependencies, broad ATS exceptions, or + generated-source assertions to application E2E tests. +- Use test-driven development: every behavior change starts with a test that + fails for the intended reason. + +## Considered Approaches + +### 1. Minimal patches + +Fix the stale manifest URL, double-load guard, and destructive test output, but +leave the existing plugin boundaries intact. + +Rejected because it preserves hidden cross-plugin state and the complicated +lazy-load settlement model. It would make CI green without meeting the +maintainability objective. + +### 2. Boundary refactor (selected) + +Keep the proven compiler/runtime behavior, but make ownership explicit at each +boundary: public Rspeedy chain configuration, atomic manifest records, +per-compilation bundle state, one lazy-load controller, transactional React +state, isolated test output, and focused test harnesses. + +Selected because it deletes incidental coupling while preserving externally +observable behavior and allows each change to be proved independently. + +### 3. Single monolithic federation coordinator + +Replace the matcher, asset, manifest, and external bundle plugins with one +large plugin. + +Rejected because hook ordering would be more visible but responsibilities +would become less modular, and the rewrite risk is disproportionate to the +identified problems. + +## Architecture + +### Public Lynx cache-event configuration + +Rspeedy registers the exported `LynxCacheEventsPlugin` in the bundler chain as +`lynx:cache-events`. The federation adapter will configure that slot inside +`modifyBundlerChain` using the exported plugin and +`LynxCacheEventsPluginOptions`: + +```ts +chain.plugin('lynx:cache-events').use(LynxCacheEventsPlugin, [ + { setupListTransformer: () => [] }, +]); +``` + +This replaces `disableRemoteEntryEventCaching`, which currently reads a +protected `.options` field and reconstructs an already-instantiated plugin. +The public chain configuration is applied only for remote-bundle environments. +An adapter test will assert both the chain slot and options. No private API +fallback is required by the current Lynx/Rspeedy release. + +### Atomic manifest cache records + +`SnapshotHandler` will replace its parallel manifest and resolved-URL maps with +one record: + +```ts +interface ManifestCacheRecord { + manifest: Manifest; + resolvedUrl: string; +} +``` + +The record is committed only after `Response.json()` succeeds. A manifest +returned by `errorLoadRemote` uses the requested manifest URL unless that hook +eventually gains an explicit resolved URL contract. Cache clearing and +in-flight loading invalidation will be one `clearManifestCache` operation, so +remote removal cannot clear half the state. + +The SDK option will be renamed from the ambiguous `manifestUrl` to +`resolvedManifestUrl`. `generateSnapshotFromManifest` will derive one local +`publicPathUrl = resolvedManifestUrl ?? version` without a non-null assertion. + +Tests will cover: + +- redirected manifest success; +- failed JSON parsing followed by a response with an empty URL; +- `errorLoadRemote` manifest recovery after a redirected failure; +- cache invalidation and remote re-registration. + +### Per-compilation remote-bundle state + +Compilation data will move into a typed state object: + +```ts +interface RemoteBundleCompilationState { + discardedTemplateAssets: Set; + lazyBundleAssets: Set; + lazyBundleAssetByExpose: Map; + pairedBundleChunks: Set; + sourceAssets: AssetSnapshot[]; +} +``` + +A small `RemoteBundleCompilationStateStore` owns a +`WeakMap` and creates a fresh state +for each compilation. The chunk matcher, paired-assets phase, and external +bundle encoder receive the store and access state with the active +`Compilation`. They no longer share mutable arrays/maps/sets created in +`configureRemoteBundle`, and `externalBundle` no longer retains `sourceAssets` +at compiler scope. + +The existing plugins remain separate because their responsibilities are +distinct: + +- chunk matcher: identify lazy bundles and emit runtime matcher code; +- paired-assets plugin: rewrite paired background/main-thread assets; +- external-bundle plugin: snapshot, encode, preserve, and delete assets. + +A two-compilation watch-mode test will prove that the second compilation does +not observe assets from the first. + +### Explicit lazy-chunk load controller + +`loadLazyChunk` currently combines tuple slots, `active`, `insideLoader`, an +`ImmediateLazyLoad` box, a timeout, and a competing installation promise. +These responsibilities will move into one internal controller with explicit +states: + +```ts +type LazyChunkLoadState = + | { kind: 'loading' } + | { kind: 'waiting-consumes'; chunk: LynxChunk; consumes: Promise } + | { kind: 'installed'; chunk: LynxChunk } + | { kind: 'failed'; error: unknown }; +``` + +The controller owns the installed-chunk tuple, activity generation, timeout, +settlement, and rollback. It invokes the official `PromiseLike.then` directly, +so a synchronous Lynx thenable still installs modules and returns a +synchronously-observable thenable when no consumes are pending. All stale +completion checks are centralized in the controller rather than spread across +callbacks. + +Existing behavioral tests remain authoritative. The 901-line suite will be +split into: + +- `runtimeSectionLoading.test.ts`; +- `runtimeLazyBundleLoading.test.ts`; +- `runtimeChunkUrl.test.ts`; +- a small shared `runtimeChunkLoading.testUtils.ts`. + +### Transactional demo load state + +`useFederatedCatalog` will use one discriminated load state for the components, +error, shared-state proof, and readiness result. An `inFlightRef` stores the +single active load promise. Repeated taps return that promise instead of +starting another import or mutating the shared singleton twice. + +The import/validation transaction will live in a small framework-independent +`catalogLoadController.ts`. The React hook owns rendering state and delegates +deduplication and retry to that controller, so transaction behavior can be +tested without a component renderer or React lifecycle mocks. + +The transaction commits ready components and singleton evidence together. A +failed retry clears stale component data before the next transaction. Activity +and user-selected filtering remain separate UI concerns. + +Tests will prove rapid repeated calls share one import operation, failed loads +do not retain partial modules, and retry succeeds cleanly. + +### Isolated E2E output and shared server support + +`native-dev-server.mjs` will never rename or delete the canonical `dist`. +Build configs will accept an environment-provided output root, and the test +will build into a temporary directory removed in `finally`. + +Web and iOS harnesses will share a focused `test/support/artifact-server.mjs` +that owns static file serving, request recording, readiness polling, and clean +shutdown. Scenario assertions remain in their platform-specific runners. + +The Metro emulator partition size will be defined once as an environment value +and used for both AVD creation and the explicit emulator CLI flag. + +### Native iOS resource boundary + +The 396-line Objective-C fetcher will be decomposed around testable ownership: + +- `OrbitResourceURLResolver`: absolute/relative URL resolution, local-file + containment, and allowed local-network policy; +- `OrbitResourceStore`: bounded path cache and atomic data persistence; +- `OrbitResourceDownloader`: response-size enforcement, cancellation, and + temporary-file lifecycle; +- `OrbitResourceFetcher`: Lynx protocol adaptation and orchestration only. + +The production fetcher will depend on those collaborators. A new +`OrbitControlTests` target will exercise URL traversal/symlink rejection, +relative URL resolution, cache replacement and byte limits, oversized download +cancellation, and cleanup. The real UI tests remain the end-to-end proof that +the assembled fetcher loads standalone and federated bundles. + +The existing single Release `xcodebuild` invocation will include the +`OrbitControlTests` target as well as the three `OrbitControlUITests`, retaining +one compile while making the native boundary tests mandatory in CI. + +`ios-project.mjs` will retain structural policy checks for project wiring and +ATS configuration, but it will stop treating source-code regexes as behavioral +tests. + +### Artifact tests and compatibility wrapper + +Compiler-internal emitted-source assertions belong in `packages/lynx` tests, +where the adapter contract is controlled. Demo artifact tests will assert +public outputs: manifest fields, file existence, bundle counts, HTTP loading, +and runtime behavior. They will not depend on minifier text or wrapper order. + +The Rspack-canary Rspeedy wrapper remains isolated behind package scripts. The +README will document why it exists, the pinned upstream compatibility +constraint, and the condition for deleting it. + +## Error Handling and Invariants + +- Failed manifest parsing never publishes resolved-URL metadata. +- Cache invalidation removes the manifest record and in-flight load together. +- Each Rspack `Compilation` receives fresh mutable state. +- A lazy load settles or rolls back exactly once; stale generations cannot + mutate the chunk table. +- Repeated UI load requests share one in-flight transaction. +- Tests never move, overwrite, or delete canonical build artifacts. +- Native resource paths remain inside the allowed root after symlink + resolution, and downloads exceeding 64 MiB are cancelled and discarded. + +## Verification + +Each task will use red-green-refactor and the narrowest relevant test first. +The completed branch must pass: + +- runtime-core and SDK targeted regression tests; +- all `@module-federation/lynx` unit, type, build, and lint checks; +- demo project policy and artifact tests; +- local `e2e-lynx` parity including real Web E2E; +- real GitHub macOS Lynx iOS E2E and both Metro platform jobs; +- repository formatting and `git diff --check`; +- a fresh thermonuclear correctness and code-quality audit; +- every latest-head PR check. + +## Completion Criteria + +The refactor is complete only when: + +1. Every thermonuclear finding is removed or superseded by stronger evidence. +2. No production file crosses 1,000 lines and the 901-line test is decomposed. +3. No private Lynx plugin state is accessed. +4. Manifest URL metadata and compilation state are atomic and lifecycle-safe. +5. Lazy loading preserves synchronous thenables, timeout/retry, consumes, and + stale-load behavior with clearer state ownership. +6. Demo and native fetch behavior have direct tests rather than regex proxies. +7. Local and real-platform CI are green on the final commit. diff --git a/apps/lynx-module-federation-demo/design/orbit-control-mobile.png b/apps/lynx-module-federation-demo/design/orbit-control-mobile.png new file mode 100644 index 00000000000..501d48e1a08 Binary files /dev/null and b/apps/lynx-module-federation-demo/design/orbit-control-mobile.png differ diff --git a/apps/lynx-module-federation-demo/design/orbit-control-wide.png b/apps/lynx-module-federation-demo/design/orbit-control-wide.png new file mode 100644 index 00000000000..5c84b6ea6cf Binary files /dev/null and b/apps/lynx-module-federation-demo/design/orbit-control-wide.png differ diff --git a/apps/lynx-module-federation-demo/federation.config.mjs b/apps/lynx-module-federation-demo/federation.config.mjs new file mode 100644 index 00000000000..b48cd81cc22 --- /dev/null +++ b/apps/lynx-module-federation-demo/federation.config.mjs @@ -0,0 +1,105 @@ +import path from 'node:path'; +import { createRequire } from 'node:module'; +import { fileURLToPath } from 'node:url'; + +import { pluginLynxModuleFederation } from '@module-federation/lynx'; + +const appRoot = path.dirname(fileURLToPath(import.meta.url)); +const require = createRequire(import.meta.url); +const implementation = require.resolve('@module-federation/runtime-tools'); + +export const sharedStateRequest = 'orbit-shared-state'; + +export const resolveOutputRoot = (name) => + process.env.LYNX_OUTPUT_ROOT + ? path.resolve(process.env.LYNX_OUTPUT_ROOT, name) + : `dist/${name}`; + +export const resolveAliases = { + [sharedStateRequest]: path.resolve( + appRoot, + 'src/shared-app/federationState.ts', + ), +}; + +const singleton = { + singleton: true, + requiredVersion: false, +}; + +export const createAppSharedConfig = () => ({ + [sharedStateRequest]: { + ...singleton, + realm: 'background', + }, +}); + +const createRemoteSharedConfig = () => + Object.fromEntries( + Object.entries(createAppSharedConfig()).map(([name, config]) => [ + name, + { ...config, import: false }, + ]), + ); + +const createFederationOptions = (remotes) => ({ + name: 'orbit_control', + implementation, + remotes, + experiments: { asyncStartup: true }, + shareStrategy: 'loaded-first', + shared: createAppSharedConfig(), +}); + +export const createWebHostFederationPlugin = (manifestUrl) => + pluginLynxModuleFederation( + createFederationOptions({ catalog: `catalog@${manifestUrl}` }), + { + environment: 'web', + mainThread: true, + runtimePluginOptions: { timeout: 15_000 }, + }, + ); + +export const createNativeHostFederationPlugin = (manifestUrl) => + pluginLynxModuleFederation( + createFederationOptions({ catalog: `catalog@${manifestUrl}` }), + { + environment: 'lynx', + runtimePluginOptions: { timeout: 15_000 }, + }, + ); + +const createRemoteOptions = () => ({ + name: 'catalog', + implementation, + experiments: { asyncStartup: true }, + shareStrategy: 'loaded-first', + exposes: { + './ActivityFeed': './src/remote-ui/ActivityFeed.tsx', + './Card': './src/remote-ui/Card.tsx', + './Details': './src/remote-ui/Details.tsx', + }, + shared: createRemoteSharedConfig(), +}); + +export const createWebRemoteFederationPlugin = () => + pluginLynxModuleFederation(createRemoteOptions(), { + environment: 'web', + remoteBundle: { + target: 'web', + filename: 'catalog.web.lynx.bundle', + preserveSourceEntryBundles: false, + }, + }); + +export const createNativeRemoteFederationPlugin = () => + pluginLynxModuleFederation(createRemoteOptions(), { + environment: 'lynx', + remoteBundle: { + engineVersion: '3.9', + target: 'lynx', + filename: 'catalog.native.lynx.bundle', + preserveSourceEntryBundles: false, + }, + }); diff --git a/apps/lynx-module-federation-demo/ios/Artwork/OrbitControl.svg b/apps/lynx-module-federation-demo/ios/Artwork/OrbitControl.svg new file mode 100644 index 00000000000..756a969a304 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/Artwork/OrbitControl.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/apps/lynx-module-federation-demo/ios/Gemfile b/apps/lynx-module-federation-demo/ios/Gemfile new file mode 100644 index 00000000000..dfdcb67ca47 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' + +gem 'cocoapods', '1.16.2' diff --git a/apps/lynx-module-federation-demo/ios/Gemfile.lock b/apps/lynx-module-federation-demo/ios/Gemfile.lock new file mode 100644 index 00000000000..fe59c0580d5 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/Gemfile.lock @@ -0,0 +1,134 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.8) + activesupport (7.2.3.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1, < 6) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + atomos (0.1.3) + base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (4.1.2) + claide (1.1.0) + cocoapods (1.16.2) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.16.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.27.0, < 2.0) + cocoapods-core (1.16.2) + activesupport (>= 5.0, < 8) + addressable (~> 2.8) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + netrc (~> 0.11) + public_suffix (~> 4.0) + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (2.1) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.1) + cocoapods-trunk (1.6.0) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.2.0) + colored2 (3.1.2) + concurrent-ruby (1.3.7) + connection_pool (3.0.2) + drb (2.2.3) + escape (0.0.4) + ethon (0.18.0) + ffi (>= 1.15.0) + logger + ffi (1.17.4) + ffi (1.17.4-aarch64-linux-gnu) + ffi (1.17.4-aarch64-linux-musl) + ffi (1.17.4-arm-linux-gnu) + ffi (1.17.4-arm-linux-musl) + ffi (1.17.4-arm64-darwin) + ffi (1.17.4-x86-linux-gnu) + ffi (1.17.4-x86-linux-musl) + ffi (1.17.4-x86_64-darwin) + ffi (1.17.4-x86_64-linux-gnu) + ffi (1.17.4-x86_64-linux-musl) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + httpclient (2.9.0) + mutex_m + i18n (1.15.2) + concurrent-ruby (~> 1.0) + json (2.21.1) + logger (1.7.0) + minitest (5.27.0) + molinillo (0.8.0) + mutex_m (0.3.0) + nanaimo (0.4.0) + nap (1.1.0) + netrc (0.11.0) + nkf (0.3.0) + public_suffix (4.0.7) + rexml (3.4.4) + ruby-macho (2.5.1) + securerandom (0.4.1) + typhoeus (1.6.0) + ethon (>= 0.18.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + xcodeproj (1.28.1) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + base64 + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.4.0) + nkf + rexml (>= 3.3.6, < 4.0) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + ruby + x86-linux-gnu + x86-linux-musl + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + cocoapods (= 1.16.2) + +BUNDLED WITH + 2.6.9 diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl.xcodeproj/project.pbxproj b/apps/lynx-module-federation-demo/ios/OrbitControl.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..b190a03edbd --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl.xcodeproj/project.pbxproj @@ -0,0 +1,615 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 0D3C69BC3F0039D57FBCE3E2 /* libPods-OrbitControl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A53B1B55000D34C4A561687 /* libPods-OrbitControl.a */; }; + 100000000000000000000001 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 100000000000000000000011 /* AppDelegate.swift */; }; + 100000000000000000000002 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 100000000000000000000012 /* ViewController.swift */; }; + 100000000000000000000003 /* OrbitResourceFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 100000000000000000000014 /* OrbitResourceFetcher.m */; }; + 100000000000000000000004 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 100000000000000000000018 /* LaunchScreen.storyboard */; }; + 100000000000000000000005 /* main.lynx.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 100000000000000000000019 /* main.lynx.bundle */; }; + 100000000000000000000006 /* OrbitControlUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10000000000000000000001A /* OrbitControlUITests.swift */; }; + 100000000000000000000007 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 10000000000000000000001C /* Assets.xcassets */; }; + 100000000000000000000008 /* host-native in Resources */ = {isa = PBXBuildFile; fileRef = 10000000000000000000001D /* host-native */; }; + 200000000000000000000001 /* OrbitResourceURLResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 210000000000000000000002 /* OrbitResourceURLResolver.m */; }; + 200000000000000000000002 /* OrbitResourceStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 210000000000000000000004 /* OrbitResourceStore.m */; }; + 200000000000000000000003 /* OrbitResourceDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 210000000000000000000006 /* OrbitResourceDownloader.m */; }; + 200000000000000000000004 /* OrbitResourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 210000000000000000000007 /* OrbitResourceTests.m */; }; + 200000000000000000000005 /* OrbitResourceURLResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 210000000000000000000002 /* OrbitResourceURLResolver.m */; }; + 200000000000000000000006 /* OrbitResourceStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 210000000000000000000004 /* OrbitResourceStore.m */; }; + 200000000000000000000007 /* OrbitResourceDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 210000000000000000000006 /* OrbitResourceDownloader.m */; }; + 200000000000000000000008 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 210000000000000000000009 /* XCTest.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 100000000000000000000020 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 100000000000000000000030 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 100000000000000000000040; + remoteInfo = OrbitControl; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 100000000000000000000010 /* OrbitControl.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OrbitControl.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 100000000000000000000011 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 100000000000000000000012 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 100000000000000000000013 /* OrbitResourceFetcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OrbitResourceFetcher.h; sourceTree = ""; }; + 100000000000000000000014 /* OrbitResourceFetcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OrbitResourceFetcher.m; sourceTree = ""; }; + 100000000000000000000015 /* OrbitControl-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OrbitControl-Bridging-Header.h"; sourceTree = ""; }; + 100000000000000000000016 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 100000000000000000000017 /* Info.Debug.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.Debug.plist; sourceTree = ""; }; + 100000000000000000000018 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 100000000000000000000019 /* main.lynx.bundle */ = {isa = PBXFileReference; lastKnownFileType = file; path = main.lynx.bundle; sourceTree = ""; }; + 10000000000000000000001A /* OrbitControlUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrbitControlUITests.swift; sourceTree = ""; }; + 10000000000000000000001B /* OrbitControlUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OrbitControlUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 10000000000000000000001C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 10000000000000000000001D /* host-native */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "host-native"; sourceTree = ""; }; + 210000000000000000000001 /* OrbitResourceURLResolver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OrbitResourceURLResolver.h; sourceTree = ""; }; + 210000000000000000000002 /* OrbitResourceURLResolver.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OrbitResourceURLResolver.m; sourceTree = ""; }; + 210000000000000000000003 /* OrbitResourceStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OrbitResourceStore.h; sourceTree = ""; }; + 210000000000000000000004 /* OrbitResourceStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OrbitResourceStore.m; sourceTree = ""; }; + 210000000000000000000005 /* OrbitResourceDownloader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OrbitResourceDownloader.h; sourceTree = ""; }; + 210000000000000000000006 /* OrbitResourceDownloader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OrbitResourceDownloader.m; sourceTree = ""; }; + 210000000000000000000007 /* OrbitResourceTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OrbitResourceTests.m; sourceTree = ""; }; + 210000000000000000000008 /* OrbitControlTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OrbitControlTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 210000000000000000000009 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = System/Library/Frameworks/XCTest.framework; sourceTree = SDKROOT; }; + 2A53B1B55000D34C4A561687 /* libPods-OrbitControl.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OrbitControl.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 4CA6D515EC78DF0FF9866EA0 /* Pods-OrbitControl.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OrbitControl.debug.xcconfig"; path = "Target Support Files/Pods-OrbitControl/Pods-OrbitControl.debug.xcconfig"; sourceTree = ""; }; + 89120448DFA86FDA0491C6AB /* Pods-OrbitControl.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OrbitControl.release.xcconfig"; path = "Target Support Files/Pods-OrbitControl/Pods-OrbitControl.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 100000000000000000000050 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0D3C69BC3F0039D57FBCE3E2 /* libPods-OrbitControl.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 100000000000000000000051 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 250000000000000000000002 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 200000000000000000000008 /* XCTest.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 100000000000000000000060 = { + isa = PBXGroup; + children = ( + 100000000000000000000061 /* OrbitControl */, + 100000000000000000000062 /* Resources */, + 100000000000000000000063 /* OrbitControlUITests */, + 260000000000000000000005 /* OrbitControlTests */, + 100000000000000000000064 /* Products */, + E38B361A9360FB49B8107C77 /* Pods */, + 637A738AE6E27E006103F4CC /* Frameworks */, + ); + sourceTree = ""; + }; + 100000000000000000000061 /* OrbitControl */ = { + isa = PBXGroup; + children = ( + 100000000000000000000011 /* AppDelegate.swift */, + 100000000000000000000012 /* ViewController.swift */, + 100000000000000000000013 /* OrbitResourceFetcher.h */, + 100000000000000000000014 /* OrbitResourceFetcher.m */, + 210000000000000000000001 /* OrbitResourceURLResolver.h */, + 210000000000000000000002 /* OrbitResourceURLResolver.m */, + 210000000000000000000003 /* OrbitResourceStore.h */, + 210000000000000000000004 /* OrbitResourceStore.m */, + 210000000000000000000005 /* OrbitResourceDownloader.h */, + 210000000000000000000006 /* OrbitResourceDownloader.m */, + 100000000000000000000015 /* OrbitControl-Bridging-Header.h */, + 100000000000000000000016 /* Info.plist */, + 100000000000000000000017 /* Info.Debug.plist */, + 100000000000000000000018 /* LaunchScreen.storyboard */, + 10000000000000000000001C /* Assets.xcassets */, + ); + path = OrbitControl; + sourceTree = ""; + }; + 100000000000000000000062 /* Resources */ = { + isa = PBXGroup; + children = ( + 100000000000000000000019 /* main.lynx.bundle */, + 10000000000000000000001D /* host-native */, + ); + path = Resources; + sourceTree = ""; + }; + 100000000000000000000063 /* OrbitControlUITests */ = { + isa = PBXGroup; + children = ( + 10000000000000000000001A /* OrbitControlUITests.swift */, + ); + path = OrbitControlUITests; + sourceTree = ""; + }; + 100000000000000000000064 /* Products */ = { + isa = PBXGroup; + children = ( + 100000000000000000000010 /* OrbitControl.app */, + 10000000000000000000001B /* OrbitControlUITests.xctest */, + 210000000000000000000008 /* OrbitControlTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 260000000000000000000005 /* OrbitControlTests */ = { + isa = PBXGroup; + children = ( + 210000000000000000000007 /* OrbitResourceTests.m */, + ); + path = OrbitControlTests; + sourceTree = ""; + }; + 637A738AE6E27E006103F4CC /* Frameworks */ = { + isa = PBXGroup; + children = ( + 210000000000000000000009 /* XCTest.framework */, + 2A53B1B55000D34C4A561687 /* libPods-OrbitControl.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + E38B361A9360FB49B8107C77 /* Pods */ = { + isa = PBXGroup; + children = ( + 4CA6D515EC78DF0FF9866EA0 /* Pods-OrbitControl.debug.xcconfig */, + 89120448DFA86FDA0491C6AB /* Pods-OrbitControl.release.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 100000000000000000000040 /* OrbitControl */ = { + isa = PBXNativeTarget; + buildConfigurationList = 100000000000000000000070 /* Build configuration list for PBXNativeTarget "OrbitControl" */; + buildPhases = ( + 28AC290F0830F258FCA83BC2 /* [CP] Check Pods Manifest.lock */, + 100000000000000000000080 /* Sources */, + 100000000000000000000050 /* Frameworks */, + 100000000000000000000081 /* Resources */, + 99866C7BB9F0FB220A1EC64C /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = OrbitControl; + productName = OrbitControl; + productReference = 100000000000000000000010 /* OrbitControl.app */; + productType = "com.apple.product-type.application"; + }; + 100000000000000000000041 /* OrbitControlUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 100000000000000000000071 /* Build configuration list for PBXNativeTarget "OrbitControlUITests" */; + buildPhases = ( + 100000000000000000000082 /* Sources */, + 100000000000000000000051 /* Frameworks */, + 100000000000000000000083 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 100000000000000000000090 /* PBXTargetDependency */, + ); + name = OrbitControlUITests; + productName = OrbitControlUITests; + productReference = 10000000000000000000001B /* OrbitControlUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; + 240000000000000000000002 /* OrbitControlTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 270000000000000000000003 /* Build configuration list for PBXNativeTarget "OrbitControlTests" */; + buildPhases = ( + 280000000000000000000004 /* Sources */, + 250000000000000000000002 /* Frameworks */, + 280000000000000000000005 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = OrbitControlTests; + productName = OrbitControlTests; + productReference = 210000000000000000000008 /* OrbitControlTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 100000000000000000000030 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1640; + LastUpgradeCheck = 1640; + TargetAttributes = { + 100000000000000000000040 = { + CreatedOnToolsVersion = 16.4; + }; + 100000000000000000000041 = { + CreatedOnToolsVersion = 16.4; + TestTargetID = 100000000000000000000040; + }; + 240000000000000000000002 = { + CreatedOnToolsVersion = 16.4; + }; + }; + }; + buildConfigurationList = 100000000000000000000072 /* Build configuration list for PBXProject "OrbitControl" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 100000000000000000000060; + productRefGroup = 100000000000000000000064 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 100000000000000000000040 /* OrbitControl */, + 100000000000000000000041 /* OrbitControlUITests */, + 240000000000000000000002 /* OrbitControlTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 100000000000000000000081 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 100000000000000000000004 /* LaunchScreen.storyboard in Resources */, + 100000000000000000000005 /* main.lynx.bundle in Resources */, + 100000000000000000000007 /* Assets.xcassets in Resources */, + 100000000000000000000008 /* host-native in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 100000000000000000000083 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 280000000000000000000005 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 28AC290F0830F258FCA83BC2 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-OrbitControl-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 99866C7BB9F0FB220A1EC64C /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-OrbitControl/Pods-OrbitControl-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-OrbitControl/Pods-OrbitControl-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OrbitControl/Pods-OrbitControl-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 100000000000000000000080 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 100000000000000000000001 /* AppDelegate.swift in Sources */, + 100000000000000000000002 /* ViewController.swift in Sources */, + 100000000000000000000003 /* OrbitResourceFetcher.m in Sources */, + 200000000000000000000001 /* OrbitResourceURLResolver.m in Sources */, + 200000000000000000000002 /* OrbitResourceStore.m in Sources */, + 200000000000000000000003 /* OrbitResourceDownloader.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 100000000000000000000082 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 100000000000000000000006 /* OrbitControlUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 280000000000000000000004 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 200000000000000000000004 /* OrbitResourceTests.m in Sources */, + 200000000000000000000005 /* OrbitResourceURLResolver.m in Sources */, + 200000000000000000000006 /* OrbitResourceStore.m in Sources */, + 200000000000000000000007 /* OrbitResourceDownloader.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 100000000000000000000090 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 100000000000000000000040 /* OrbitControl */; + targetProxy = 100000000000000000000020 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 1000000000000000000000A0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 1000000000000000000000A1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 1000000000000000000000A2 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4CA6D515EC78DF0FF9866EA0 /* Pods-OrbitControl.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + INFOPLIST_FILE = OrbitControl/Info.Debug.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.modulefederation.lynx.demo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "OrbitControl/OrbitControl-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1000000000000000000000A3 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89120448DFA86FDA0491C6AB /* Pods-OrbitControl.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + INFOPLIST_FILE = OrbitControl/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.modulefederation.lynx.demo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "OrbitControl/OrbitControl-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 1000000000000000000000A4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + PRODUCT_BUNDLE_IDENTIFIER = org.modulefederation.lynx.demo.uitests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = OrbitControl; + }; + name = Debug; + }; + 1000000000000000000000A5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + PRODUCT_BUNDLE_IDENTIFIER = org.modulefederation.lynx.demo.uitests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = OrbitControl; + }; + name = Release; + }; + 2200000000000000000000A6 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.modulefederation.lynx.demo.tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 2200000000000000000000A7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.modulefederation.lynx.demo.tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 100000000000000000000070 /* Build configuration list for PBXNativeTarget "OrbitControl" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1000000000000000000000A2 /* Debug */, + 1000000000000000000000A3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 100000000000000000000071 /* Build configuration list for PBXNativeTarget "OrbitControlUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1000000000000000000000A4 /* Debug */, + 1000000000000000000000A5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 100000000000000000000072 /* Build configuration list for PBXProject "OrbitControl" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1000000000000000000000A0 /* Debug */, + 1000000000000000000000A1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 270000000000000000000003 /* Build configuration list for PBXNativeTarget "OrbitControlTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2200000000000000000000A6 /* Debug */, + 2200000000000000000000A7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 100000000000000000000030 /* Project object */; +} diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl.xcodeproj/xcshareddata/xcschemes/OrbitControl.xcscheme b/apps/lynx-module-federation-demo/ios/OrbitControl.xcodeproj/xcshareddata/xcschemes/OrbitControl.xcscheme new file mode 100644 index 00000000000..371cf4755af --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl.xcodeproj/xcshareddata/xcschemes/OrbitControl.xcscheme @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/AppDelegate.swift b/apps/lynx-module-federation-demo/ios/OrbitControl/AppDelegate.swift new file mode 100644 index 00000000000..6624bad4921 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl/AppDelegate.swift @@ -0,0 +1,21 @@ +// Derived from the official Lynx HelloLynxSwift starter (Apache-2.0). + +import UIKit + +@main +final class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + LynxEnv.sharedInstance() + + let window = UIWindow(frame: UIScreen.main.bounds) + window.rootViewController = ViewController() + window.makeKeyAndVisible() + self.window = window + return true + } +} diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/lynx-module-federation-demo/ios/OrbitControl/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..3eae7b620aa --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images": [ + { + "filename": "OrbitControl.png", + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/Assets.xcassets/AppIcon.appiconset/OrbitControl.png b/apps/lynx-module-federation-demo/ios/OrbitControl/Assets.xcassets/AppIcon.appiconset/OrbitControl.png new file mode 100644 index 00000000000..b2789f9df53 Binary files /dev/null and b/apps/lynx-module-federation-demo/ios/OrbitControl/Assets.xcassets/AppIcon.appiconset/OrbitControl.png differ diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/Assets.xcassets/Contents.json b/apps/lynx-module-federation-demo/ios/OrbitControl/Assets.xcassets/Contents.json new file mode 100644 index 00000000000..74d6a722cf3 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/Base.lproj/LaunchScreen.storyboard b/apps/lynx-module-federation-demo/ios/OrbitControl/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000000..b4262ae4136 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/Info.Debug.plist b/apps/lynx-module-federation-demo/ios/OrbitControl/Info.Debug.plist new file mode 100644 index 00000000000..4706904f567 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl/Info.Debug.plist @@ -0,0 +1,52 @@ + + + + + CFBundleDisplayName + Orbit Control + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsLocalNetworking + + NSExceptionDomains + + 127.0.0.1 + + NSExceptionAllowsInsecureHTTPLoads + + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + + + NSLocalNetworkUsageDescription + Orbit Control connects to a developer-hosted Lynx federation server on the local network. + UILaunchStoryboardName + LaunchScreen + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/Info.plist b/apps/lynx-module-federation-demo/ios/OrbitControl/Info.plist new file mode 100644 index 00000000000..626bc5e11db --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl/Info.plist @@ -0,0 +1,39 @@ + + + + + CFBundleDisplayName + Orbit Control + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + NSLocalNetworkUsageDescription + Orbit Control connects to a developer-hosted Lynx federation server on the local network. + UILaunchStoryboardName + LaunchScreen + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitControl-Bridging-Header.h b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitControl-Bridging-Header.h new file mode 100644 index 00000000000..d565305c114 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitControl-Bridging-Header.h @@ -0,0 +1,4 @@ +#import +#import +#import +#import "OrbitResourceFetcher.h" diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceDownloader.h b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceDownloader.h new file mode 100644 index 00000000000..812f2fdda6c --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceDownloader.h @@ -0,0 +1,21 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +FOUNDATION_EXPORT NSUInteger const OrbitResourceDownloadByteLimit; + +typedef void (^OrbitResourceDownloadCompletion)(NSData *_Nullable, + NSURLResponse *_Nullable, + NSError *_Nullable); + +@interface OrbitResourceDownloader : NSObject + +- (instancetype)init; +- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration + NS_DESIGNATED_INITIALIZER; +- (dispatch_block_t)downloadURL:(NSURL *)url + completion:(OrbitResourceDownloadCompletion)completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceDownloader.m b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceDownloader.m new file mode 100644 index 00000000000..a892e914d27 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceDownloader.m @@ -0,0 +1,175 @@ +#import "OrbitResourceDownloader.h" + +NSUInteger const OrbitResourceDownloadByteLimit = 64 * 1024 * 1024; + +static NSString *const OrbitResourceDownloadErrorDomain = + @"org.modulefederation.lynx.resources"; + +@interface OrbitResourceDownloader () + +@property(nonatomic, strong) NSMutableDictionary *completions; +@property(nonatomic, strong) NSMutableSet *oversizedTasks; +@property(nonatomic, strong) NSURLSession *session; + +@end + +@implementation OrbitResourceDownloader + +- (instancetype)init { + NSURLSessionConfiguration *configuration = + NSURLSessionConfiguration.ephemeralSessionConfiguration; + configuration.timeoutIntervalForRequest = 30; + configuration.timeoutIntervalForResource = 60; + configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; + return [self initWithSessionConfiguration:configuration]; +} + +- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration { + self = [super init]; + if (self) { + _completions = [NSMutableDictionary dictionary]; + _oversizedTasks = [NSMutableSet set]; + _session = [NSURLSession sessionWithConfiguration:configuration + delegate:self + delegateQueue:nil]; + } + return self; +} + +- (void)dealloc { + [self.session invalidateAndCancel]; +} + +- (dispatch_block_t)downloadURL:(NSURL *)url + completion:(OrbitResourceDownloadCompletion)completion { + NSURLSessionDownloadTask *task = [self.session downloadTaskWithURL:url]; + @synchronized(self) { + self.completions[@(task.taskIdentifier)] = [completion copy]; + } + [task resume]; + return ^{ [task cancel]; }; +} + +- (OrbitResourceDownloadCompletion)takeCompletionForTask:(NSURLSessionTask *)task { + @synchronized(self) { + NSNumber *key = @(task.taskIdentifier); + OrbitResourceDownloadCompletion completion = self.completions[key]; + [self.completions removeObjectForKey:key]; + return completion; + } +} + +- (NSError *)errorForURL:(NSURL *)url prefix:(NSString *)prefix { + return [NSError errorWithDomain:OrbitResourceDownloadErrorDomain + code:1 + userInfo:@{ + NSLocalizedDescriptionKey: + [NSString stringWithFormat:@"%@: %@", + prefix, + url.absoluteString] + }]; +} + +- (BOOL)takeOversizedFlagForTask:(NSURLSessionTask *)task { + @synchronized(self) { + NSNumber *key = @(task.taskIdentifier); + BOOL oversized = [self.oversizedTasks containsObject:key]; + [self.oversizedTasks removeObject:key]; + return oversized; + } +} + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask + didWriteData:(int64_t)bytesWritten + totalBytesWritten:(int64_t)totalBytesWritten +totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { + (void)session; + (void)bytesWritten; + if (totalBytesWritten > (int64_t)OrbitResourceDownloadByteLimit || + totalBytesExpectedToWrite > (int64_t)OrbitResourceDownloadByteLimit) { + @synchronized(self) { + [self.oversizedTasks addObject:@(downloadTask.taskIdentifier)]; + } + [downloadTask cancel]; + } +} + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask +didFinishDownloadingToURL:(NSURL *)location { + (void)session; + BOOL oversized = [self takeOversizedFlagForTask:downloadTask]; + OrbitResourceDownloadCompletion completion = + [self takeCompletionForTask:downloadTask]; + if (!completion) { + [NSFileManager.defaultManager removeItemAtURL:location error:nil]; + return; + } + + NSError *resultError = nil; + NSData *data = nil; + NSURLResponse *response = downloadTask.response; + if (oversized) { + resultError = [self errorForURL:downloadTask.originalRequest.URL + prefix:@"Lynx resource exceeds 64 MiB"]; + } + if (!resultError && [response isKindOfClass:NSHTTPURLResponse.class]) { + NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; + if (statusCode < 200 || statusCode >= 300) { + resultError = [NSError errorWithDomain:OrbitResourceDownloadErrorDomain + code:1 + userInfo:@{ + NSLocalizedDescriptionKey: + [NSString stringWithFormat: + @"Lynx resource request failed with HTTP %ld: %@", + (long)statusCode, + downloadTask.originalRequest.URL.absoluteString] + }]; + } + } + + NSNumber *fileSize = nil; + if (!resultError && + ![location getResourceValue:&fileSize + forKey:NSURLFileSizeKey + error:&resultError]) { + fileSize = nil; + } + if (!resultError && + (response.expectedContentLength > (long long)OrbitResourceDownloadByteLimit || + fileSize.unsignedLongLongValue > OrbitResourceDownloadByteLimit)) { + resultError = [self errorForURL:downloadTask.originalRequest.URL + prefix:@"Lynx resource exceeds 64 MiB"]; + } + if (!resultError) { + data = [NSData dataWithContentsOfURL:location + options:NSDataReadingMappedIfSafe + error:&resultError]; + if (!data && !resultError) { + resultError = [self errorForURL:downloadTask.originalRequest.URL + prefix:@"Lynx resource request returned no data"]; + } + } + [NSFileManager.defaultManager removeItemAtURL:location error:nil]; + completion(data, response, resultError); +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didCompleteWithError:(NSError *)error { + (void)session; + if (!error) return; + + BOOL oversized = [self takeOversizedFlagForTask:task]; + OrbitResourceDownloadCompletion completion = [self takeCompletionForTask:task]; + if (!completion) return; + completion(nil, + task.response, + oversized + ? [self errorForURL:task.originalRequest.URL + prefix:@"Lynx resource exceeds 64 MiB"] + : error); +} + +@end diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceFetcher.h b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceFetcher.h new file mode 100644 index 00000000000..05dca786315 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceFetcher.h @@ -0,0 +1,23 @@ +// Derived from official Lynx iOS resource fetcher examples (Apache-2.0). + +#import +#import +#import +#import + +@class LynxViewBuilder; + +NS_ASSUME_NONNULL_BEGIN + +@interface OrbitResourceFetcher : NSObject + +- (instancetype)initWithRootBundleURL:(NSString *)rootBundleURL + NS_DESIGNATED_INITIALIZER NS_SWIFT_NAME(init(rootBundleURL:)); +- (instancetype)init NS_UNAVAILABLE; +- (void)configureBuilder:(LynxViewBuilder *)builder NS_SWIFT_NAME(configure(_:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceFetcher.m b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceFetcher.m new file mode 100644 index 00000000000..04b99f3b6b2 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceFetcher.m @@ -0,0 +1,141 @@ +// Derived from official Lynx iOS resource fetcher examples (Apache-2.0). + +#import "OrbitResourceFetcher.h" + +#import "OrbitResourceDownloader.h" +#import "OrbitResourceStore.h" +#import "OrbitResourceURLResolver.h" + +#import +#import +#import + +static NSString *const OrbitResourceErrorDomain = + @"org.modulefederation.lynx.resources"; + +@interface OrbitResourceFetcher () + +@property(nonatomic, strong) OrbitResourceDownloader *downloader; +@property(nonatomic, strong) OrbitResourceURLResolver *resolver; +@property(nonatomic, strong) OrbitResourceStore *store; + +@end + + +@implementation OrbitResourceFetcher + +- (instancetype)initWithRootBundleURL:(NSString *)rootBundleURL { + self = [super init]; + if (self) { + _downloader = [[OrbitResourceDownloader alloc] init]; + _resolver = [[OrbitResourceURLResolver alloc] + initWithRootBundleURL:rootBundleURL]; + _store = [[OrbitResourceStore alloc] init]; + } + return self; +} + +- (NSError *)errorWithMessage:(NSString *)message { + return [NSError errorWithDomain:OrbitResourceErrorDomain + code:1 + userInfo:@{NSLocalizedDescriptionKey: message}]; +} + +- (dispatch_block_t)loadDataForURLString:(NSString *)urlString + completion:(void (^)(NSData *_Nullable, + NSError *_Nullable))completion { + NSURL *url = [self.resolver resolvedURLForString:urlString]; + if (!url) { + completion(nil, [self errorWithMessage:[NSString stringWithFormat: + @"Unsupported Lynx resource URL: %@", urlString]]); + return ^{}; + } + + if (url.isFileURL) { + NSError *error = nil; + NSData *data = [NSData dataWithContentsOfURL:url options:0 error:&error]; + completion(data, error); + return ^{}; + } + + return [self.downloader + downloadURL:url + completion:^(NSData *data, NSURLResponse *response, NSError *error) { + (void)response; + completion(data, error); + }]; +} + +- (void)configureBuilder:(LynxViewBuilder *)builder { + builder.enableGenericResourceFetcher = LynxBooleanOptionTrue; + builder.genericResourceFetcher = self; + builder.templateResourceFetcher = self; +} + +- (void)loadTemplateWithUrl:(NSString *)url + onComplete:(LynxTemplateLoadBlock)callback { + [self loadDataForURLString:url + completion:^(NSData *data, NSError *error) { + callback(data, error); + }]; +} + +- (void)fetchTemplate:(LynxResourceRequest *)request + onComplete:(LynxTemplateResourceCompletionBlock)callback { + [self loadDataForURLString:request.url + completion:^(NSData *data, NSError *error) { + callback(data + ? [[LynxTemplateResource alloc] initWithNSData:data] + : nil, + error); + }]; +} + +- (void)fetchSSRData:(LynxResourceRequest *)request + onComplete:(LynxSSRResourceCompletionBlock)callback { + [self loadDataForURLString:request.url completion:callback]; +} + +- (dispatch_block_t)fetchResource:(LynxResourceRequest *)request + onComplete:(LynxGenericResourceCompletionBlock)callback { + return [self loadDataForURLString:request.url completion:callback]; +} + +- (dispatch_block_t)fetchResourcePath:(LynxResourceRequest *)request + onComplete:(LynxGenericResourcePathCompletionBlock)callback { + NSURL *url = [self.resolver resolvedURLForString:request.url]; + if (!url) { + callback(nil, [self errorWithMessage:[NSString stringWithFormat: + @"Unsupported Lynx resource URL: %@", request.url]]); + return ^{}; + } + if (url.isFileURL) { + callback(url.path, nil); + return ^{}; + } + + NSString *urlString = url.absoluteString; + NSString *cachedPath = [self.store pathForURLString:urlString]; + if (cachedPath) { + callback(cachedPath, nil); + return ^{}; + } + + return [self.downloader + downloadURL:url + completion:^(NSData *data, NSURLResponse *response, NSError *error) { + (void)response; + if (!data) { + callback(nil, error); + return; + } + + NSError *writeError = nil; + NSString *path = [self.store storeData:data + forURLString:urlString + error:&writeError]; + callback(path, writeError); + }]; +} + +@end diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceStore.h b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceStore.h new file mode 100644 index 00000000000..9ad54740fdd --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceStore.h @@ -0,0 +1,21 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +FOUNDATION_EXPORT NSUInteger const OrbitResourceStoreByteLimit; + +@interface OrbitResourceStore : NSObject + +@property(nonatomic, strong, readonly) NSURL *cacheDirectoryURL; + +- (instancetype)init; +- (instancetype)initWithCacheDirectoryURL:(NSURL *)cacheDirectoryURL + NS_DESIGNATED_INITIALIZER; +- (nullable NSString *)pathForURLString:(NSString *)urlString; +- (nullable NSString *)storeData:(NSData *)data + forURLString:(NSString *)urlString + error:(NSError **)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceStore.m b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceStore.m new file mode 100644 index 00000000000..4f9613ec48a --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceStore.m @@ -0,0 +1,94 @@ +#import "OrbitResourceStore.h" + +NSUInteger const OrbitResourceStoreByteLimit = 64 * 1024 * 1024; + +static NSString *const OrbitResourceStoreErrorDomain = + @"org.modulefederation.lynx.resources"; + +@interface OrbitResourceStore () + +@property(nonatomic, strong, readwrite) NSURL *cacheDirectoryURL; +@property(nonatomic, strong) NSMutableDictionary *paths; +@property(nonatomic, assign) NSUInteger storedBytes; + +@end + +@implementation OrbitResourceStore + +- (instancetype)init { + NSURL *directory = [[[NSURL fileURLWithPath:NSTemporaryDirectory() + isDirectory:YES] + URLByAppendingPathComponent:@"OrbitResources" + isDirectory:YES] + URLByAppendingPathComponent:NSUUID.UUID.UUIDString + isDirectory:YES]; + return [self initWithCacheDirectoryURL:directory]; +} + +- (instancetype)initWithCacheDirectoryURL:(NSURL *)cacheDirectoryURL { + self = [super init]; + if (self) { + _cacheDirectoryURL = cacheDirectoryURL; + _paths = [NSMutableDictionary dictionary]; + } + return self; +} + +- (void)dealloc { + [NSFileManager.defaultManager removeItemAtURL:self.cacheDirectoryURL error:nil]; +} + +- (nullable NSString *)pathForURLString:(NSString *)urlString { + @synchronized(self) { + return self.paths[urlString]; + } +} + +- (nullable NSString *)storeData:(NSData *)data + forURLString:(NSString *)urlString + error:(NSError **)error { + @synchronized(self) { + if (data.length > OrbitResourceStoreByteLimit - self.storedBytes) { + if (error) { + *error = [NSError errorWithDomain:OrbitResourceStoreErrorDomain + code:1 + userInfo:@{ + NSLocalizedDescriptionKey: + @"Lynx resource path cache exceeded 64 MiB" + }]; + } + return nil; + } + NSUInteger nextSize = self.storedBytes + data.length; + + NSError *writeError = nil; + [NSFileManager.defaultManager + createDirectoryAtURL:self.cacheDirectoryURL + withIntermediateDirectories:YES + attributes:nil + error:&writeError]; + if (writeError) { + if (error) *error = writeError; + return nil; + } + + NSString *extension = [NSURL URLWithString:urlString].pathExtension; + NSString *filename = NSUUID.UUID.UUIDString; + if (extension.length > 0) { + filename = [filename stringByAppendingPathExtension:extension]; + } + NSURL *fileURL = [self.cacheDirectoryURL + URLByAppendingPathComponent:filename]; + if (![data writeToURL:fileURL options:NSDataWritingAtomic error:&writeError]) { + if (error) *error = writeError; + return nil; + } + + // Lynx may still be reading an earlier path for this URL. + self.paths[urlString] = fileURL.path; + self.storedBytes = nextSize; + return fileURL.path; + } +} + +@end diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceURLResolver.h b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceURLResolver.h new file mode 100644 index 00000000000..71d05dd3bca --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceURLResolver.h @@ -0,0 +1,17 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface OrbitResourceURLResolver : NSObject + +- (instancetype)initWithRootBundleURL:(NSString *)rootBundleURL; +- (instancetype)initWithRootBundleURL:(NSString *)rootBundleURL + allowedLocalDirectories:(NSArray *)directories + NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; +- (nullable NSURL *)resolvedURLForString:(NSString *)urlString; +- (BOOL)isAllowedLocalURL:(NSURL *)url; + +@end + +NS_ASSUME_NONNULL_END diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceURLResolver.m b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceURLResolver.m new file mode 100644 index 00000000000..da89c966021 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl/OrbitResourceURLResolver.m @@ -0,0 +1,98 @@ +#import "OrbitResourceURLResolver.h" + +@interface OrbitResourceURLResolver () + +@property(nonatomic, copy) NSArray *allowedLocalDirectories; +@property(nonatomic, strong, nullable) NSURL *rootBundleURL; + +@end + +static BOOL IsNetworkBundleAssetPath(NSString *urlString) { + return [urlString hasPrefix:@"/host-native/"] || + [urlString hasPrefix:@"/catalog-native/"]; +} + +@implementation OrbitResourceURLResolver + +- (instancetype)initWithRootBundleURL:(NSString *)rootBundleURL { + return [self initWithRootBundleURL:rootBundleURL + allowedLocalDirectories:@[NSBundle.mainBundle.bundleURL]]; +} + +- (instancetype)initWithRootBundleURL:(NSString *)rootBundleURL + allowedLocalDirectories:(NSArray *)directories { + self = [super init]; + if (self) { + NSURL *rootURL = [NSURL URLWithString:rootBundleURL]; + if ([rootURL.scheme isEqualToString:@"http"] || + [rootURL.scheme isEqualToString:@"https"]) { + _rootBundleURL = rootURL; + } + _allowedLocalDirectories = [directories copy]; + } + return self; +} + +- (nullable NSURL *)resolvedURLForString:(NSString *)urlString { + NSURL *url = [NSURL URLWithString:urlString]; + NSString *unresolvedPath = url.path.length > 0 ? url.path : urlString; + if ([unresolvedPath.pathComponents containsObject:@".."]) return nil; + + if (self.rootBundleURL && IsNetworkBundleAssetPath(urlString)) { + return [NSURL URLWithString:urlString relativeToURL:self.rootBundleURL].absoluteURL; + } + + if ([url.scheme isEqualToString:@"http"] || + [url.scheme isEqualToString:@"https"]) { + return url; + } + if ([url.scheme isEqualToString:@"file"]) { + return [self isAllowedLocalURL:url] ? url : nil; + } + + NSString *relativePath; + if (urlString.isAbsolutePath) { + NSURL *fileURL = [NSURL fileURLWithPath:urlString]; + if ([self isAllowedLocalURL:fileURL]) return fileURL; + if (![urlString hasPrefix:@"/host-native/"]) return nil; + relativePath = [urlString substringFromIndex:1]; + } else { + relativePath = unresolvedPath; + } + + relativePath = relativePath.stringByStandardizingPath; + if (relativePath.length == 0 || [relativePath isEqualToString:@"."] || + [relativePath isEqualToString:@".."] || + [relativePath hasPrefix:@"../"]) { + return nil; + } + + NSString *filename = relativePath.lastPathComponent; + NSString *subdirectory = relativePath.stringByDeletingLastPathComponent; + NSString *extension = filename.pathExtension; + NSString *name = extension.length > 0 + ? filename.stringByDeletingPathExtension + : filename; + return [NSBundle.mainBundle URLForResource:name + withExtension:extension.length > 0 ? extension : nil + subdirectory:[subdirectory isEqualToString:@"."] + ? nil + : subdirectory]; +} + +- (BOOL)isAllowedLocalURL:(NSURL *)url { + if (!url.isFileURL) return NO; + NSString *resolvedPath = + url.URLByStandardizingPath.URLByResolvingSymlinksInPath.path; + for (NSURL *directory in self.allowedLocalDirectories) { + NSString *directoryPath = + directory.URLByStandardizingPath.URLByResolvingSymlinksInPath.path; + if ([resolvedPath isEqualToString:directoryPath] || + [resolvedPath hasPrefix:[directoryPath stringByAppendingString:@"/"]]) { + return YES; + } + } + return NO; +} + +@end diff --git a/apps/lynx-module-federation-demo/ios/OrbitControl/ViewController.swift b/apps/lynx-module-federation-demo/ios/OrbitControl/ViewController.swift new file mode 100644 index 00000000000..8bf65c0a688 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControl/ViewController.swift @@ -0,0 +1,88 @@ +// Derived from the official Lynx HelloLynxSwift starter (Apache-2.0). + +import UIKit + +final class ViewController: UIViewController { + private lazy var resourceFetcher = OrbitResourceFetcher( + rootBundleURL: rootBundleURL + ) + private var lynxView: LynxView? + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .black + + let lynxView = LynxView { builder in + builder.config = LynxConfig(provider: self.resourceFetcher) + builder.screenSize = self.contentFrame.size + builder.fontScale = 1.0 + self.resourceFetcher.configure(builder) + } + + lynxView.frame = contentFrame + lynxView.preferredLayoutWidth = contentFrame.width + lynxView.preferredLayoutHeight = contentFrame.height + lynxView.layoutWidthMode = .exact + lynxView.layoutHeightMode = .exact + view.addSubview(lynxView) + self.lynxView = lynxView + + NotificationCenter.default.addObserver( + self, + selector: #selector(enterForeground), + name: UIApplication.willEnterForegroundNotification, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(enterBackground), + name: UIApplication.didEnterBackgroundNotification, + object: nil + ) + + lynxView.loadTemplate(fromURL: rootBundleURL, initData: nil) + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + guard let lynxView else { return } + let frame = contentFrame + lynxView.frame = frame + lynxView.updateScreenMetrics(withWidth: frame.width, height: frame.height) + lynxView.updateViewport( + withPreferredLayoutWidth: frame.width, + preferredLayoutHeight: frame.height, + needLayout: true + ) + } + + deinit { + NotificationCenter.default.removeObserver(self) + lynxView?.clearForDestroy() + } + + @objc private func enterForeground() { + lynxView?.onEnterForeground() + } + + @objc private func enterBackground() { + lynxView?.onEnterBackground() + } + + private var rootBundleURL: String { + if let override = ProcessInfo.processInfo.environment["LYNX_BUNDLE_URL"], + !override.isEmpty { + return override + } +#if DEBUG + return "http://localhost:3000/main.lynx.bundle" +#else + return "main.lynx.bundle" +#endif + } + + private var contentFrame: CGRect { + let frame = view.safeAreaLayoutGuide.layoutFrame + return frame.isEmpty ? view.bounds : frame + } +} diff --git a/apps/lynx-module-federation-demo/ios/OrbitControlTests/OrbitResourceTests.m b/apps/lynx-module-federation-demo/ios/OrbitControlTests/OrbitResourceTests.m new file mode 100644 index 00000000000..4b785331472 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControlTests/OrbitResourceTests.m @@ -0,0 +1,137 @@ +#import + +#import "../OrbitControl/OrbitResourceDownloader.h" +#import "../OrbitControl/OrbitResourceStore.h" +#import "../OrbitControl/OrbitResourceURLResolver.h" + +@interface OrbitResourceDownloader (Testing) +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask + didWriteData:(int64_t)bytesWritten + totalBytesWritten:(int64_t)totalBytesWritten +totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite; +@end + +@interface OrbitResourceTests : XCTestCase +@end + +@implementation OrbitResourceTests + +- (void)testResolvesRootRelativeLoopbackURLs { + OrbitResourceURLResolver *resolver = [[OrbitResourceURLResolver alloc] + initWithRootBundleURL:@"http://127.0.0.1:3000/host-native/main.lynx.bundle"]; + + NSURL *resolved = [resolver + resolvedURLForString:@"/host-native/lazy-bundle/shared.bundle"]; + + XCTAssertEqualObjects(resolved.absoluteString, + @"http://127.0.0.1:3000/host-native/lazy-bundle/shared.bundle"); +} + +- (void)testResolvesStandaloneCatalogRootRelativeURLs { + OrbitResourceURLResolver *resolver = [[OrbitResourceURLResolver alloc] + initWithRootBundleURL:@"http://127.0.0.1:3000/catalog-native/main.lynx.bundle"]; + + NSURL *resolved = [resolver + resolvedURLForString:@"/catalog-native/lazy-bundle/activity-metadata.bundle"]; + + XCTAssertEqualObjects( + resolved.absoluteString, + @"http://127.0.0.1:3000/catalog-native/lazy-bundle/activity-metadata.bundle"); +} + +- (void)testRejectsTraversalAndSymlinkEscapes { + NSFileManager *files = NSFileManager.defaultManager; + NSURL *parent = [[NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES] + URLByAppendingPathComponent:NSUUID.UUID.UUIDString + isDirectory:YES]; + NSURL *root = [parent URLByAppendingPathComponent:@"root" isDirectory:YES]; + NSURL *outside = [parent URLByAppendingPathComponent:@"outside" isDirectory:YES]; + XCTAssertTrue([files createDirectoryAtURL:root + withIntermediateDirectories:YES + attributes:nil + error:nil]); + XCTAssertTrue([files createDirectoryAtURL:outside + withIntermediateDirectories:YES + attributes:nil + error:nil]); + NSURL *insideFile = [root URLByAppendingPathComponent:@"inside.bundle"]; + XCTAssertTrue([[@"inside" dataUsingEncoding:NSUTF8StringEncoding] + writeToURL:insideFile + atomically:YES]); + NSURL *escape = [root URLByAppendingPathComponent:@"escape"]; + XCTAssertTrue([files createSymbolicLinkAtURL:escape + withDestinationURL:outside + error:nil]); + NSURL *outsideFile = [outside URLByAppendingPathComponent:@"secret.bundle"]; + XCTAssertTrue([[@"secret" dataUsingEncoding:NSUTF8StringEncoding] + writeToURL:outsideFile + atomically:YES]); + OrbitResourceURLResolver *resolver = [[OrbitResourceURLResolver alloc] + initWithRootBundleURL:@"" + allowedLocalDirectories:@[root]]; + + XCTAssertTrue([resolver isAllowedLocalURL:insideFile]); + XCTAssertFalse([resolver isAllowedLocalURL: + [escape URLByAppendingPathComponent:@"secret.bundle"]]); + XCTAssertNil([resolver resolvedURLForString:@"../secret.bundle"]); + XCTAssertNil( + [resolver resolvedURLForString:@"/host-native/../secret.bundle"]); + [files removeItemAtURL:parent error:nil]; +} + +- (void)testRetainsReplacedCachedPathsUntilCleanup { + NSFileManager *files = NSFileManager.defaultManager; + NSURL *directory = [[NSURL fileURLWithPath:NSTemporaryDirectory() + isDirectory:YES] + URLByAppendingPathComponent:NSUUID.UUID.UUIDString + isDirectory:YES]; + @autoreleasepool { + OrbitResourceStore *store = [[OrbitResourceStore alloc] + initWithCacheDirectoryURL:directory]; + NSError *error = nil; + NSString *first = [store storeData:[@"first" dataUsingEncoding:NSUTF8StringEncoding] + forURLString:@"https://example.test/chunk.bundle" + error:&error]; + XCTAssertNotNil(first); + XCTAssertNil(error); + + NSString *replacement = [store + storeData:[@"replacement" dataUsingEncoding:NSUTF8StringEncoding] + forURLString:@"https://example.test/chunk.bundle" + error:&error]; + XCTAssertNotEqualObjects(first, replacement); + XCTAssertTrue([files fileExistsAtPath:first]); + XCTAssertEqualObjects([NSData dataWithContentsOfFile:first], + [@"first" dataUsingEncoding:NSUTF8StringEncoding]); + XCTAssertEqualObjects([NSData dataWithContentsOfFile:replacement], + [@"replacement" dataUsingEncoding:NSUTF8StringEncoding]); + + NSData *oversized = [NSMutableData dataWithLength:OrbitResourceStoreByteLimit + 1]; + XCTAssertNil([store storeData:oversized + forURLString:@"https://example.test/too-large.bundle" + error:&error]); + XCTAssertEqualObjects(error.localizedDescription, + @"Lynx resource path cache exceeded 64 MiB"); + } + XCTAssertFalse([files fileExistsAtPath:directory.path]); +} + +- (void)testCancelsOversizedDownloads { + OrbitResourceDownloader *downloader = [[OrbitResourceDownloader alloc] init]; + NSURLSession *session = [NSURLSession sessionWithConfiguration: + NSURLSessionConfiguration.ephemeralSessionConfiguration]; + NSURLSessionDownloadTask *task = [session + downloadTaskWithURL:[NSURL URLWithString:@"https://oversize.test/chunk.bundle"]]; + + [downloader URLSession:session + downloadTask:task + didWriteData:1 + totalBytesWritten:1 + totalBytesExpectedToWrite:(int64_t)OrbitResourceDownloadByteLimit + 1]; + + XCTAssertEqual(task.state, NSURLSessionTaskStateCanceling); + [session invalidateAndCancel]; +} + +@end diff --git a/apps/lynx-module-federation-demo/ios/OrbitControlUITests/OrbitControlUITests.swift b/apps/lynx-module-federation-demo/ios/OrbitControlUITests/OrbitControlUITests.swift new file mode 100644 index 00000000000..087e18ead40 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/OrbitControlUITests/OrbitControlUITests.swift @@ -0,0 +1,129 @@ +import XCTest + +final class OrbitControlUITests: XCTestCase { + private func launchNetworkBundle( + environmentKey: String, + fallback: String + ) -> XCUIApplication { + addUIInterruptionMonitor(withDescription: "Local network permission") { alert in + guard alert.buttons["Allow"].exists else { return false } + alert.buttons["Allow"].tap() + return true + } + + let app = XCUIApplication() + app.launchEnvironment["LYNX_BUNDLE_URL"] = + ProcessInfo.processInfo.environment[environmentKey] ?? fallback + app.launch() + app.tap() + return app + } + + private func attachScreenshot(named name: String) { + let attachment = XCTAttachment(screenshot: XCUIScreen.main.screenshot()) + attachment.name = name + attachment.lifetime = .keepAlways + add(attachment) + } + + private func tapUntilStateChanges(_ button: XCUIElement) -> Bool { + for _ in 0..<2 { + button.tap() + let stateChanged = XCTNSPredicateExpectation( + predicate: NSPredicate(format: "exists == false"), + object: button + ) + if XCTWaiter.wait(for: [stateChanged], timeout: 2) == .completed { + return true + } + } + XCTFail("The load gesture did not leave the idle state.") + return false + } + + func testEmbeddedReleaseHostLaunches() { + let app = XCUIApplication() + app.launchEnvironment["LYNX_BUNDLE_URL"] = "" + app.launch() + + let loadButton = app.descendants(matching: .any) + .matching(NSPredicate(format: "label == %@", "Load remote catalog")) + .firstMatch + XCTAssertTrue(loadButton.waitForExistence(timeout: 30)) + } + + func testFederatedImportsRuntimeLoadingAndSingleton() { + let app = launchNetworkBundle( + environmentKey: "LYNX_BUNDLE_URL", + fallback: "http://localhost:3000/host-native/main.lynx.bundle" + ) + + let loadButton = app.descendants(matching: .any) + .matching(NSPredicate(format: "label == %@", "Load remote catalog")) + .firstMatch + XCTAssertTrue(loadButton.waitForExistence(timeout: 30)) + guard tapUntilStateChanges(loadButton) else { return } + + let readiness = app.descendants(matching: .any) + .matching(identifier: "federation-ready") + .firstMatch + let isReady = readiness.waitForExistence(timeout: 60) + let error = app.descendants(matching: .any) + .matching(identifier: "federation-error") + .firstMatch + let failure = error.exists ? error.label : "No federation error was rendered." + XCTAssertTrue(isReady, failure) + + let metadata = app.descendants(matching: .any) + .matching(identifier: "activity-metadata") + .firstMatch + XCTAssertTrue(metadata.waitForExistence(timeout: 30)) + XCTAssertEqual(metadata.label, "Nested federated module ready") + + attachScreenshot(named: "Orbit Control federation loaded") + } + + func testStandaloneCatalogRemoteBuildLaunches() { + let app = launchNetworkBundle( + environmentKey: "CATALOG_BUNDLE_URL", + fallback: "http://localhost:3000/catalog-native/main.lynx.bundle" + ) + + let readiness = app.descendants(matching: .any) + .matching(identifier: "catalog-standalone-ready") + .firstMatch + XCTAssertTrue(readiness.waitForExistence(timeout: 30)) + + for label in ["REMOTE CARD", "REMOTE DETAILS", "Federated activity"] { + let component = app.descendants(matching: .any) + .matching(NSPredicate(format: "label == %@", label)) + .firstMatch + XCTAssertTrue(component.waitForExistence(timeout: 30), "Missing \(label)") + } + + let metadata = app.descendants(matching: .any) + .matching(identifier: "activity-metadata") + .firstMatch + XCTAssertTrue(metadata.waitForExistence(timeout: 30)) + XCTAssertEqual(metadata.label, "Nested federated module ready") + + let count = app.descendants(matching: .any) + .matching(identifier: "shared-card-count") + .firstMatch + XCTAssertTrue(count.waitForExistence(timeout: 30)) + let baseline = count.label + + let increment = app.descendants(matching: .any) + .matching(NSPredicate(format: "label == %@", "Increment from remote")) + .firstMatch + XCTAssertTrue(increment.waitForExistence(timeout: 30)) + increment.tap() + expectation( + for: NSPredicate(format: "label != %@", baseline), + evaluatedWith: count + ) + waitForExpectations(timeout: 10) + + attachScreenshot(named: "Standalone Orbit Catalog") + } +} diff --git a/apps/lynx-module-federation-demo/ios/Podfile b/apps/lynx-module-federation-demo/ios/Podfile new file mode 100644 index 00000000000..4c38945b082 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/Podfile @@ -0,0 +1,24 @@ +source 'https://cdn.cocoapods.org/' + +platform :ios, '13.0' + +target 'OrbitControl' do + pod 'Lynx', '3.9.0', :subspecs => ['Framework'] + # Lynx 3.9.0 pins this PrimJS engine build in its published podspec. + pod 'PrimJS', '3.8.0-alpha.6', :subspecs => ['quickjs', 'napi'] + pod 'LynxService', '3.9.0', :subspecs => ['Image', 'Log', 'Http'] + pod 'SDWebImage', '5.15.5' + pod 'SDWebImageWebPCoder', '0.11.0' + pod 'XElement', '3.9.0' +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['GCC_TREAT_WARNINGS_AS_ERRORS'] = 'NO' + config.build_settings['OTHER_CFLAGS'] = '$(inherited) -Wno-error -Wno-c99-designator' + config.build_settings['OTHER_CPLUSPLUSFLAGS'] = '$(inherited) -Wno-error -Wno-c99-designator' + config.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited) -Xcc -Wno-deprecated-declarations -Xcc -Wno-deprecated-implementations' + end + end +end diff --git a/apps/lynx-module-federation-demo/ios/Podfile.lock b/apps/lynx-module-federation-demo/ios/Podfile.lock new file mode 100644 index 00000000000..8ec0e011122 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/Podfile.lock @@ -0,0 +1,158 @@ +PODS: + - libwebp (1.5.0): + - libwebp/demux (= 1.5.0) + - libwebp/mux (= 1.5.0) + - libwebp/sharpyuv (= 1.5.0) + - libwebp/webp (= 1.5.0) + - libwebp/demux (1.5.0): + - libwebp/webp + - libwebp/mux (1.5.0): + - libwebp/demux + - libwebp/sharpyuv (1.5.0) + - libwebp/webp (1.5.0): + - libwebp/sharpyuv + - Lynx (3.9.0): + - Lynx/Framework (= 3.9.0) + - Lynx/Framework (3.9.0): + - Lynx/ReleaseResource + - LynxBase/Framework + - LynxServiceAPI + - PrimJS/napi/env + - PrimJS/napi/jsc + - PrimJS/napi/quickjs + - PrimJS/quickjs (= 3.8.0-alpha.6) + - Lynx/ReleaseResource (3.9.0) + - LynxBase (3.9.0): + - LynxBase/Framework (= 3.9.0) + - LynxBase/Framework (3.9.0): + - LynxServiceAPI/Core + - LynxService/Http (3.9.0): + - Lynx (= 3.9.0) + - LynxServiceAPI (= 3.9.0) + - LynxService/Image (3.9.0): + - Lynx (= 3.9.0) + - LynxServiceAPI (= 3.9.0) + - SDWebImage (= 5.15.5) + - SDWebImageWebPCoder (= 0.11.0) + - LynxService/Log (3.9.0): + - Lynx (= 3.9.0) + - LynxBase (= 3.9.0) + - LynxServiceAPI (= 3.9.0) + - LynxServiceAPI (3.9.0): + - LynxServiceAPI/Core (= 3.9.0) + - LynxServiceAPI/Core (3.9.0) + - LynxTextra (0.1.6) + - MJRefresh (3.7.9) + - PrimJS/log (3.8.0-alpha.6) + - PrimJS/napi (3.8.0-alpha.6): + - PrimJS/napi/adapter (= 3.8.0-alpha.6) + - PrimJS/napi/core (= 3.8.0-alpha.6) + - PrimJS/napi/env (= 3.8.0-alpha.6) + - PrimJS/napi/jsc (= 3.8.0-alpha.6) + - PrimJS/napi/quickjs (= 3.8.0-alpha.6) + - PrimJS/napi/adapter (3.8.0-alpha.6): + - PrimJS/napi/core + - PrimJS/napi/env + - PrimJS/napi/core (3.8.0-alpha.6) + - PrimJS/napi/env (3.8.0-alpha.6): + - PrimJS/napi/core + - PrimJS/napi/jsc (3.8.0-alpha.6): + - PrimJS/log + - PrimJS/napi/core + - PrimJS/quickjs + - PrimJS/napi/quickjs (3.8.0-alpha.6): + - PrimJS/napi/core + - PrimJS/quickjs + - PrimJS/quickjs (3.8.0-alpha.6): + - PrimJS/log + - SDWebImage (5.15.5): + - SDWebImage/Core (= 5.15.5) + - SDWebImage/Core (5.15.5) + - SDWebImageWebPCoder (0.11.0): + - libwebp (~> 1.0) + - SDWebImage/Core (~> 5.15) + - ServalMarkdown (0.0.28-alpha.4): + - LynxTextra + - ServalSVG (0.1.1) + - XElement (3.9.0): + - XElement/Behavior (= 3.9.0) + - XElement/Input (= 3.9.0) + - XElement/Markdown (= 3.9.0) + - XElement/Overlay (= 3.9.0) + - XElement/Refresh (= 3.9.0) + - XElement/ScrollCoordinator (= 3.9.0) + - XElement/SVG (= 3.9.0) + - XElement/ViewPager (= 3.9.0) + - XElement/Behavior (3.9.0): + - Lynx (= 3.9.0) + - XElement/Input + - XElement/Markdown + - XElement/Overlay + - XElement/Refresh + - XElement/ScrollCoordinator + - XElement/SVG + - XElement/ViewPager + - XElement/Input (3.9.0): + - Lynx (= 3.9.0) + - XElement/Markdown (3.9.0): + - Lynx (= 3.9.0) + - ServalMarkdown (= 0.0.28-alpha.4) + - XElement/Overlay (3.9.0): + - Lynx (= 3.9.0) + - XElement/Refresh (3.9.0): + - Lynx (= 3.9.0) + - MJRefresh (>= 3.6.1) + - XElement/ScrollCoordinator (3.9.0): + - Lynx (= 3.9.0) + - XElement/ViewPager + - XElement/SVG (3.9.0): + - Lynx (= 3.9.0) + - ServalSVG (>= 0.0.17) + - XElement/ViewPager (3.9.0): + - Lynx (= 3.9.0) + +DEPENDENCIES: + - Lynx/Framework (= 3.9.0) + - LynxService/Http (= 3.9.0) + - LynxService/Image (= 3.9.0) + - LynxService/Log (= 3.9.0) + - PrimJS/napi (= 3.8.0-alpha.6) + - PrimJS/quickjs (= 3.8.0-alpha.6) + - SDWebImage (= 5.15.5) + - SDWebImageWebPCoder (= 0.11.0) + - XElement (= 3.9.0) + +SPEC REPOS: + trunk: + - libwebp + - Lynx + - LynxBase + - LynxService + - LynxServiceAPI + - LynxTextra + - MJRefresh + - PrimJS + - SDWebImage + - SDWebImageWebPCoder + - ServalMarkdown + - ServalSVG + - XElement + +SPEC CHECKSUMS: + libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 + Lynx: ad5ed649ccf1d761e6697bc5d6d4fa6f30ad13e3 + LynxBase: 046fe3d218a88ff0b1a3f058ee8d9a07aa9ae387 + LynxService: a1728eea1ecb9d4bd9a37430b14d431ae000fce0 + LynxServiceAPI: d7fe4bc006716867e5d7cf2d3a4bcaa5207cfa0f + LynxTextra: e1a22775e880e000605091aad8dec614ca2ca79a + MJRefresh: ff9e531227924c84ce459338414550a05d2aea78 + PrimJS: a917134ed564e22594bcda526b940bc9a1de4e4d + SDWebImage: fd7e1a22f00303e058058278639bf6196ee431fe + SDWebImageWebPCoder: 295a6573c512f54ad2dd58098e64e17dcf008499 + ServalMarkdown: 60f8b7502cecce269275d38363740cd4b0deeff6 + ServalSVG: 6990830fdd5c1e7d545fc8c842a98bea6007dd86 + XElement: 49b95dcd304c4a3bd4af335962c45a65173033b3 + +PODFILE CHECKSUM: d603a00b878c420aad0fb9115986e2c845592bea + +COCOAPODS: 1.16.2 diff --git a/apps/lynx-module-federation-demo/ios/UPSTREAM.md b/apps/lynx-module-federation-demo/ios/UPSTREAM.md new file mode 100644 index 00000000000..c1396f5e269 --- /dev/null +++ b/apps/lynx-module-federation-demo/ios/UPSTREAM.md @@ -0,0 +1,20 @@ +# Official Lynx iOS starter provenance + +This application is derived from the official Lynx `HelloLynxSwift` UIKit +starter: + +- repository: +- source path: `ios/HelloLynxSwift` +- source commit: `f8230ca6aa1c9e629e30272971d0c03450b13e8e` +- upstream license: Apache-2.0 + +The standalone shell retains the starter's `LynxEnv`, `LynxConfig`, +`LynxView`, CocoaPods, and embedded `main.lynx.bundle` integration. It upgrades +the official Lynx pods from 3.8.0 to 3.9.0 (including Lynx 3.9.0's published +PrimJS 3.8.0-alpha.6 engine pin), removes the starter's personal signing team +and bundle identifier, uses a programmatic UIKit root view, and adds the +resource fetchers required for HTTP Bundle, Lazy Bundle, and generic resource +loading. + +The generated host bundle is intentionally not committed. Run +`pnpm ios:prepare` before opening the workspace. diff --git a/apps/lynx-module-federation-demo/lynx.catalog.native.config.mjs b/apps/lynx-module-federation-demo/lynx.catalog.native.config.mjs new file mode 100644 index 00000000000..e97860e26ef --- /dev/null +++ b/apps/lynx-module-federation-demo/lynx.catalog.native.config.mjs @@ -0,0 +1,42 @@ +import { pluginQRCode } from '@lynx-js/qrcode-rsbuild-plugin'; +import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin'; +import { defineConfig } from '@lynx-js/rspeedy'; + +import { resolveAliases, resolveOutputRoot } from './federation.config.mjs'; + +const catalogDevHost = process.env.CATALOG_DEV_HOST ?? '127.0.0.1'; +const catalogDevPort = Number(process.env.CATALOG_DEV_PORT ?? 3001); + +export default defineConfig({ + plugins: [ + pluginReactLynx({ + defaultDisplayLinear: false, + engineVersion: '3.9', + }), + pluginQRCode({ fullscreen: true }), + ], + source: { + entry: { + main: './src/catalog-app/index.tsx', + }, + }, + environments: { + lynx: {}, + }, + output: { + assetPrefix: '/catalog-native/', + cleanDistPath: false, + distPath: { + root: resolveOutputRoot('catalog-native'), + }, + minify: true, + }, + server: { + host: catalogDevHost, + port: catalogDevPort, + }, + resolve: { + alias: resolveAliases, + }, + splitChunks: false, +}); diff --git a/apps/lynx-module-federation-demo/lynx.catalog.web.config.mjs b/apps/lynx-module-federation-demo/lynx.catalog.web.config.mjs new file mode 100644 index 00000000000..c9f49654450 --- /dev/null +++ b/apps/lynx-module-federation-demo/lynx.catalog.web.config.mjs @@ -0,0 +1,36 @@ +import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin'; +import { defineConfig } from '@lynx-js/rspeedy'; + +import { resolveAliases, resolveOutputRoot } from './federation.config.mjs'; + +export default defineConfig({ + plugins: [ + pluginReactLynx({ + defaultDisplayLinear: false, + engineVersion: '3.9', + }), + ], + source: { + entry: { + main: './src/catalog-app/index.tsx', + }, + }, + environments: { + web: {}, + }, + output: { + assetPrefix: 'auto', + cleanDistPath: false, + distPath: { + root: resolveOutputRoot('catalog-web'), + }, + minify: false, + }, + server: { + port: Number(process.env.CATALOG_DEV_PORT ?? 3001), + }, + resolve: { + alias: resolveAliases, + }, + splitChunks: false, +}); diff --git a/apps/lynx-module-federation-demo/lynx.config.mjs b/apps/lynx-module-federation-demo/lynx.config.mjs new file mode 100644 index 00000000000..016760cd2fc --- /dev/null +++ b/apps/lynx-module-federation-demo/lynx.config.mjs @@ -0,0 +1,74 @@ +import { pluginQRCode } from '@lynx-js/qrcode-rsbuild-plugin'; +import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin'; +import { defineConfig } from '@lynx-js/rspeedy'; + +import { + createNativeHostFederationPlugin, + resolveAliases, + resolveOutputRoot, +} from './federation.config.mjs'; + +const nativeRemoteOrigin = + process.env.LYNX_REMOTE_ORIGIN?.replace(/\/+$/, '') ?? + 'http://127.0.0.1:3000'; +const nativeHostOrigin = process.env.LYNX_HOST_ORIGIN?.replace(/\/+$/, ''); +const devHost = process.env.LYNX_DEV_HOST ?? '127.0.0.1'; +const devPort = Number(process.env.LYNX_DEV_PORT ?? 3000); +const nativeManifestUrl = + process.env.CATALOG_NATIVE_MANIFEST_URL ?? + `${nativeRemoteOrigin}/remote-native/mf-manifest.json`; +const pluginNativeRemoteAssets = { + name: 'demo:native-remote-assets', + setup(api) { + api.modifyRsbuildConfig((config, { mergeRsbuildConfig }) => + mergeRsbuildConfig(config, { + server: { + publicDir: { + copyOnBuild: false, + name: process.env.LYNX_OUTPUT_ROOT ?? 'dist', + watch: false, + }, + }, + }), + ); + }, +}; + +export default defineConfig({ + plugins: [ + pluginReactLynx({ + defaultDisplayLinear: false, + engineVersion: '3.9', + firstScreenSyncTiming: 'jsReady', + }), + createNativeHostFederationPlugin(nativeManifestUrl), + pluginNativeRemoteAssets, + pluginQRCode({ fullscreen: true }), + ], + source: { + entry: { + main: './src/app/index.tsx', + }, + }, + environments: { + lynx: {}, + }, + output: { + assetPrefix: nativeHostOrigin + ? `${nativeHostOrigin}/host-native/` + : '/host-native/', + cleanDistPath: false, + distPath: { + root: resolveOutputRoot('host-native'), + }, + minify: true, + }, + server: { + host: devHost, + port: devPort, + }, + resolve: { + alias: resolveAliases, + }, + splitChunks: false, +}); diff --git a/apps/lynx-module-federation-demo/lynx.remote.native.config.mjs b/apps/lynx-module-federation-demo/lynx.remote.native.config.mjs new file mode 100644 index 00000000000..600fa2ecfd8 --- /dev/null +++ b/apps/lynx-module-federation-demo/lynx.remote.native.config.mjs @@ -0,0 +1,43 @@ +import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin'; +import { defineConfig } from '@lynx-js/rspeedy'; + +import { + createNativeRemoteFederationPlugin, + resolveAliases, + resolveOutputRoot, +} from './federation.config.mjs'; + +const nativeRemoteOrigin = + process.env.LYNX_REMOTE_ORIGIN?.replace(/\/+$/, '') ?? + 'http://127.0.0.1:3000'; + +export default defineConfig({ + plugins: [ + pluginReactLynx({ + defaultDisplayLinear: false, + engineVersion: '3.9', + experimental_isLazyBundle: true, + }), + createNativeRemoteFederationPlugin(), + ], + source: { + entry: { + bootstrap: './src/remote-ui/bootstrap.ts', + }, + }, + environments: { + lynx: {}, + }, + output: { + assetPrefix: `${nativeRemoteOrigin}/remote-native/`, + cleanDistPath: false, + distPath: { + root: resolveOutputRoot('remote-native'), + }, + minify: true, + }, + resolve: { + alias: resolveAliases, + }, + splitChunks: false, +}); diff --git a/apps/lynx-module-federation-demo/lynx.remote.web.config.mjs b/apps/lynx-module-federation-demo/lynx.remote.web.config.mjs new file mode 100644 index 00000000000..52362ffda44 --- /dev/null +++ b/apps/lynx-module-federation-demo/lynx.remote.web.config.mjs @@ -0,0 +1,39 @@ +import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin'; +import { defineConfig } from '@lynx-js/rspeedy'; + +import { + createWebRemoteFederationPlugin, + resolveAliases, + resolveOutputRoot, +} from './federation.config.mjs'; + +export default defineConfig({ + plugins: [ + pluginReactLynx({ + defaultDisplayLinear: false, + engineVersion: '3.9', + experimental_isLazyBundle: true, + }), + createWebRemoteFederationPlugin(), + ], + source: { + entry: { + bootstrap: './src/remote-ui/bootstrap.ts', + }, + }, + environments: { + web: {}, + }, + output: { + assetPrefix: 'auto', + cleanDistPath: false, + distPath: { + root: resolveOutputRoot('remote-web'), + }, + minify: false, + }, + resolve: { + alias: resolveAliases, + }, + splitChunks: false, +}); diff --git a/apps/lynx-module-federation-demo/lynx.web.config.mjs b/apps/lynx-module-federation-demo/lynx.web.config.mjs new file mode 100644 index 00000000000..75c6795a4f4 --- /dev/null +++ b/apps/lynx-module-federation-demo/lynx.web.config.mjs @@ -0,0 +1,45 @@ +import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin'; +import { defineConfig } from '@lynx-js/rspeedy'; + +import { + createWebHostFederationPlugin, + resolveAliases, + resolveOutputRoot, +} from './federation.config.mjs'; + +const manifestUrl = + process.env.CATALOG_WEB_MANIFEST_URL ?? '/remote-web/mf-manifest.json'; + +export default defineConfig({ + plugins: [ + pluginReactLynx({ + defaultDisplayLinear: false, + engineVersion: '3.9', + firstScreenSyncTiming: 'jsReady', + }), + createWebHostFederationPlugin(manifestUrl), + ], + source: { + entry: { + main: './src/app/index.tsx', + }, + }, + environments: { + web: {}, + }, + output: { + assetPrefix: 'auto', + cleanDistPath: false, + distPath: { + root: resolveOutputRoot('host-web'), + }, + minify: false, + }, + server: { + port: 3000, + }, + resolve: { + alias: resolveAliases, + }, + splitChunks: false, +}); diff --git a/apps/lynx-module-federation-demo/package.json b/apps/lynx-module-federation-demo/package.json new file mode 100644 index 00000000000..112f7629ef5 --- /dev/null +++ b/apps/lynx-module-federation-demo/package.json @@ -0,0 +1,67 @@ +{ + "name": "lynx-module-federation-demo", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "build:adapter": "pnpm --dir ../../packages/lynx run build", + "build:catalog:native": "node rspack-canary-rspeedy.mjs build -c lynx.catalog.native.config.mjs", + "build:catalog:web": "node rspack-canary-rspeedy.mjs build -c lynx.catalog.web.config.mjs", + "build:host:native": "node rspack-canary-rspeedy.mjs build -c lynx.config.mjs", + "build:host:web": "node rspack-canary-rspeedy.mjs build -c lynx.web.config.mjs", + "build:remote:native": "node rspack-canary-rspeedy.mjs build -c lynx.remote.native.config.mjs", + "build:remote:web": "node rspack-canary-rspeedy.mjs build -c lynx.remote.web.config.mjs", + "clean:dist": "node --eval \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"", + "build:native": "pnpm run clean:dist && pnpm run build:adapter && pnpm run build:catalog:native && pnpm run build:remote:native && pnpm run build:host:native", + "build:native:ci": "pnpm run clean:dist && pnpm run build:catalog:native && pnpm run build:remote:native && pnpm run build:host:native", + "build:web": "pnpm run clean:dist && pnpm run build:adapter && pnpm run build:catalog:web && pnpm run build:remote:web && pnpm run build:host:web", + "build:web:ci": "pnpm run clean:dist && pnpm run build:catalog:web && pnpm run build:remote:web && pnpm run build:host:web", + "build": "pnpm run clean:dist && pnpm run build:adapter && pnpm run build:catalog:native && pnpm run build:catalog:web && pnpm run build:remote:native && pnpm run build:remote:web && pnpm run build:host:native && pnpm run build:host:web", + "dev": "pnpm run clean:dist && pnpm run build:adapter && pnpm run build:remote:native && node rspack-canary-rspeedy.mjs dev -c lynx.config.mjs", + "dev:catalog:native": "pnpm run clean:dist && pnpm run build:adapter && node rspack-canary-rspeedy.mjs dev -c lynx.catalog.native.config.mjs", + "dev:catalog:web": "pnpm run clean:dist && pnpm run build:adapter && node rspack-canary-rspeedy.mjs dev -c lynx.catalog.web.config.mjs", + "dev:web": "pnpm run clean:dist && pnpm run build:adapter && pnpm run build:remote:web && node rspack-canary-rspeedy.mjs dev -c lynx.web.config.mjs", + "e2e:native": "pnpm run build:native && pnpm run test:native-artifacts && pnpm run test:native-dev-server", + "e2e:native:ci": "pnpm run build:native:ci && pnpm run test:native-artifacts && pnpm run test:native-dev-server", + "e2e:ios": "node test/ios/run.mjs", + "e2e:web": "pnpm run build:web && node test/real-web/run.mjs", + "e2e:web:ci": "pnpm run build:web:ci && node test/real-web/run.mjs", + "ios:open": "open ios/OrbitControl.xcworkspace", + "ios:pods": "cd ios && bundle install && bundle exec pod install --deployment --repo-update", + "ios:device": "node scripts/dev-ios-device.mjs", + "ios:prepare": "pnpm run build:native && pnpm run ios:sync", + "ios:sync": "node scripts/sync-ios-bundle.mjs", + "preview": "node rspack-canary-rspeedy.mjs preview -c lynx.config.mjs", + "preview:catalog:native": "node rspack-canary-rspeedy.mjs preview -c lynx.catalog.native.config.mjs", + "preview:catalog:web": "node rspack-canary-rspeedy.mjs preview -c lynx.catalog.web.config.mjs", + "preview:web": "node rspack-canary-rspeedy.mjs preview -c lynx.web.config.mjs", + "test": "pnpm run test:ios-project && pnpm run e2e:native && pnpm run e2e:web", + "test:ci-policy": "node test/ci-policy.mjs", + "test:ios-project": "node test/ios-project.mjs", + "test:native-artifacts": "node test/native-artifacts.mjs", + "test:native-dev-server": "node test/native-dev-server.mjs" + }, + "dependencies": { + "@lynx-js/react": "https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09", + "@module-federation/runtime": "workspace:*" + }, + "devDependencies": { + "@lynx-js/lynx-bundle-rslib-config": "https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/lynx-bundle-rslib-config@e22ca09", + "@lynx-js/qrcode-rsbuild-plugin": "0.6.0", + "@lynx-js/react-rsbuild-plugin": "https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react-rsbuild-plugin@e22ca09", + "@lynx-js/rspeedy": "https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/rspeedy@e22ca09", + "@lynx-js/types": "4.0.0", + "@lynx-js/web-core": "https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-core@e22ca09", + "@lynx-js/web-elements": "0.12.6", + "@module-federation/lynx": "workspace:*", + "@module-federation/runtime-tools": "workspace:*", + "@playwright/test": "1.57.0", + "@rsbuild/core": "2.1.4", + "@rspack-canary/core": "2.1.5-canary-54a0d8f3-20260715194831", + "@rspack/core": "npm:@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831", + "@types/react": "18.3.28" + }, + "engines": { + "node": ">=24" + } +} diff --git a/apps/lynx-module-federation-demo/rspack-canary-rspeedy.mjs b/apps/lynx-module-federation-demo/rspack-canary-rspeedy.mjs new file mode 100644 index 00000000000..9f96c96cbe6 --- /dev/null +++ b/apps/lynx-module-federation-demo/rspack-canary-rspeedy.mjs @@ -0,0 +1,25 @@ +import { registerHooks } from 'node:module'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +const canaryUrl = import.meta.resolve('@rspack-canary/core'); +const rsbuildUrl = import.meta.resolve('@rsbuild/core'); + +registerHooks({ + resolve(specifier, context, nextResolve) { + if (specifier === '@rspack/core') { + return { shortCircuit: true, url: canaryUrl }; + } + if (specifier === '@rsbuild/core') { + return { shortCircuit: true, url: rsbuildUrl }; + } + return nextResolve(specifier, context); + }, +}); + +const rspeedyPackage = fileURLToPath( + import.meta.resolve('@lynx-js/rspeedy/package.json'), +); +const rspeedyBin = resolve(dirname(rspeedyPackage), 'bin/rspeedy.js'); +process.argv = [process.execPath, rspeedyBin, ...process.argv.slice(2)]; +await import(pathToFileURL(rspeedyBin).href); diff --git a/apps/lynx-module-federation-demo/scripts/dev-ios-device.mjs b/apps/lynx-module-federation-demo/scripts/dev-ios-device.mjs new file mode 100644 index 00000000000..5f8b39139a4 --- /dev/null +++ b/apps/lynx-module-federation-demo/scripts/dev-ios-device.mjs @@ -0,0 +1,65 @@ +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import { isIP } from 'node:net'; + +const isPhoneReachableHostname = (hostname) => { + const normalized = hostname.replace(/^\[|\]$/g, '').toLowerCase(); + if (normalized === 'localhost' || normalized.endsWith('.localhost')) { + return false; + } + if (isIP(normalized) === 4) { + const firstOctet = Number(normalized.split('.')[0]); + return firstOctet !== 0 && firstOctet !== 127; + } + if (isIP(normalized) === 6) { + if (normalized.startsWith('::ffff:')) { + const mapped = normalized.slice('::ffff:'.length); + const firstOctet = mapped.includes('.') + ? Number(mapped.split('.')[0]) + : Number.parseInt(mapped.split(':')[0], 16) >> 8; + if (firstOctet === 0 || firstOctet === 127) return false; + } + return normalized !== '::' && normalized !== '::1'; + } + return true; +}; + +const origin = process.env.LYNX_REMOTE_ORIGIN; +assert.ok( + origin, + 'Set LYNX_REMOTE_ORIGIN to the phone-reachable LAN origin, for example http://192.168.1.10:3000.', +); +const url = new URL(origin); +assert.equal( + url.protocol, + 'http:', + 'LYNX_REMOTE_ORIGIN must use HTTP because the Rspeedy development server does not terminate TLS.', +); +assert.ok( + url.pathname === '/' && + !url.search && + !url.hash && + !url.username && + !url.password, + 'LYNX_REMOTE_ORIGIN must be an HTTP origin without a path, credentials, query, or fragment.', +); +assert.ok( + isPhoneReachableHostname(url.hostname), + 'LYNX_REMOTE_ORIGIN must be reachable from the phone, not a loopback or unspecified address.', +); + +if (process.argv.includes('--check-origin')) process.exit(0); + +const child = spawn('pnpm', ['run', 'dev'], { + env: { + ...process.env, + LYNX_DEV_HOST: '0.0.0.0', + }, + stdio: 'inherit', +}); +child.once('error', (error) => { + throw error; +}); +child.once('exit', (code, signal) => { + process.exitCode = code ?? (signal ? 1 : 0); +}); diff --git a/apps/lynx-module-federation-demo/scripts/sync-ios-bundle.mjs b/apps/lynx-module-federation-demo/scripts/sync-ios-bundle.mjs new file mode 100644 index 00000000000..90d041171ab --- /dev/null +++ b/apps/lynx-module-federation-demo/scripts/sync-ios-bundle.mjs @@ -0,0 +1,37 @@ +import assert from 'node:assert/strict'; +import { cp, copyFile, mkdir, rm, stat } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const appRoot = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '..', +); +const source = path.resolve( + appRoot, + process.env.LYNX_IOS_HOST_BUNDLE ?? 'dist/host-native/main.lynx.bundle', +); +const destination = path.join(appRoot, 'ios/Resources/main.lynx.bundle'); +const sourceLazyBundles = path.join(path.dirname(source), 'lazy-bundle'); +const destinationHost = path.join(appRoot, 'ios/Resources/host-native'); +const destinationLazyBundles = path.join(destinationHost, 'lazy-bundle'); + +const [sourceStat, sourceLazyBundlesStat] = await Promise.all([ + stat(source), + stat(sourceLazyBundles), +]); +assert.ok(sourceStat.size > 0, `Native host bundle is empty: ${source}`); +assert.ok( + sourceLazyBundlesStat.isDirectory(), + `Native host lazy bundles are missing: ${sourceLazyBundles}`, +); +await mkdir(path.dirname(destination), { recursive: true }); +await rm(destinationHost, { force: true, recursive: true }); +await mkdir(destinationHost, { recursive: true }); +await Promise.all([ + copyFile(source, destination), + cp(sourceLazyBundles, destinationLazyBundles, { recursive: true }), +]); +process.stdout.write( + `Copied ${source} and ${sourceLazyBundles} to iOS Resources.\n`, +); diff --git a/apps/lynx-module-federation-demo/src/app/App.css b/apps/lynx-module-federation-demo/src/app/App.css new file mode 100644 index 00000000000..313eecb66bd --- /dev/null +++ b/apps/lynx-module-federation-demo/src/app/App.css @@ -0,0 +1,447 @@ +:root { + background-color: #f4f7f8; +} + +.Page { + width: 100vw; + height: 100vh; + display: flex; + flex-direction: column; + background-color: #f4f7f8; +} + +.TopBar { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 20px 20px 12px; + background-color: #f4f7f8; +} + +.Brand { + display: flex; + flex-direction: row; + align-items: center; +} + +.BrandMark { + width: 34px; + height: 34px; + border-radius: 12px; + background-color: #17212b; + align-items: center; + justify-content: center; + margin-right: 10px; +} + +.BrandMarkText { + color: #60e2a4; + font-size: 17px; + font-weight: 800; +} + +.BrandName { + color: #17212b; + font-size: 17px; + font-weight: 750; +} + +.Connection { + display: flex; + flex-direction: row; + align-items: center; +} + +.ConnectionDot { + width: 8px; + height: 8px; + border-radius: 4px; + background-color: #30bd78; + margin-right: 6px; +} + +.ConnectionText { + color: #50606d; + font-size: 11px; + font-weight: 650; +} + +.Content { + flex: 1; + padding-left: 20px; + padding-right: 20px; +} + +.Screen { + display: flex; + flex-direction: column; + padding-bottom: 24px; +} + +.Hero { + display: flex; + flex-direction: column; + padding-top: 16px; + padding-bottom: 18px; +} + +.HeroTitle { + color: #17212b; + font-size: 31px; + line-height: 36px; + font-weight: 800; + letter-spacing: -0.6px; + margin-bottom: 8px; +} + +.HeroCopy { + color: #64717d; + font-size: 13px; + line-height: 19px; + max-width: 330px; +} + +.PrimaryAction { + height: 48px; + border-radius: 15px; + background-color: #17212b; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding-left: 16px; + padding-right: 16px; + margin-bottom: 14px; +} + +.PrimaryActionText { + color: #ffffff; + font-size: 13px; + font-weight: 700; +} + +.PrimaryActionMeta { + color: #8da0af; + font-size: 10px; +} + +.PrimaryActionLoading { + background-color: #33404c; +} + +.PrimaryActionReady { + background-color: #1d6144; +} + +.PrimaryActionError { + background-color: #9d392d; +} + +.LoadEvidence { + display: flex; + flex-direction: row; + justify-content: space-between; + margin-top: -4px; + margin-bottom: 14px; +} + +.LoadEvidenceText { + color: #788590; + font-size: 9px; + font-weight: 650; +} + +.LoadEvidenceReady { + color: #258a5b; +} + +.LoadError { + color: #9d392d; + font-size: 11px; + margin-bottom: 14px; +} + +.SingletonProof { + display: flex; + flex-direction: row; + align-items: stretch; + margin-bottom: 16px; + border: 1px solid #2e3d50; + border-radius: 16px; + background: #111b29; + overflow: hidden; +} + +.SingletonProofCopy { + display: flex; + width: 112px; + padding: 14px 16px; + border-right: 1px solid #2e3d50; +} + +.SingletonProofCopyWide { + flex: 1; + width: auto; + border-right: 0; +} + +.SingletonProofLabel { + color: #7f93aa; + font-size: 10px; + font-weight: 700; + letter-spacing: 1px; +} + +.SingletonProofValue { + margin-top: 6px; + color: #ffffff; + font-size: 28px; + font-weight: 800; +} + +.SingletonProofStatus { + margin-top: 8px; + color: #9fb0c2; + font-size: 13px; + font-weight: 600; +} + +.SingletonProofStatusReady { + color: #61e5a1; +} + +.HealthPanel { + background-color: #ffffff; + border-radius: 20px; + padding: 18px; + margin-bottom: 12px; + border-width: 1px; + border-color: #e5eaed; +} + +.PanelHeader { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + margin-bottom: 15px; +} + +.PanelTitle { + color: #17212b; + font-size: 18px; + font-weight: 750; +} + +.PanelMeta { + color: #30a66e; + font-size: 10px; + font-weight: 700; +} + +.HealthGrid { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.HealthCell { + width: 31%; + display: flex; + flex-direction: column; + background-color: #f4f7f8; + border-radius: 14px; + padding: 12px; +} + +.HealthCellValue { + color: #17212b; + font-size: 16px; + font-weight: 750; + margin-bottom: 5px; +} + +.HealthCellLabel { + color: #788590; + font-size: 9px; + line-height: 13px; +} + +.SectionHeader { + display: flex; + flex-direction: row; + align-items: flex-end; + justify-content: space-between; + margin-top: 14px; + margin-bottom: 10px; +} + +.SectionTitle { + color: #17212b; + font-size: 19px; + font-weight: 750; +} + +.SectionMeta { + color: #788590; + font-size: 10px; +} + +.RemoteFallback { + min-height: 148px; + background-color: #e8edf1; + border-radius: 20px; + align-items: center; + justify-content: center; + padding: 28px; + margin-bottom: 12px; +} + +.RemoteFallbackText { + color: #64717d; + font-size: 13px; + line-height: 19px; + text-align: center; +} + +.QuickActions { + display: flex; + flex-direction: row; + justify-content: space-between; + margin-top: 14px; +} + +.SecondaryAction { + width: 48%; + height: 44px; + border-radius: 14px; + border-width: 1px; + border-color: #d7dfe4; + background-color: #ffffff; + align-items: center; + justify-content: center; +} + +.SecondaryActionText { + color: #33404c; + font-size: 12px; + font-weight: 650; +} + +.ModuleList { + display: flex; + flex-direction: column; + background-color: #ffffff; + border-radius: 20px; + padding-left: 16px; + padding-right: 16px; + border-width: 1px; + border-color: #e5eaed; +} + +.ModuleRow { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + min-height: 68px; + border-bottom-width: 1px; + border-bottom-color: #e8edf1; +} + +.ModuleRowLast { + border-bottom-width: 0; +} + +.ModuleCopy { + display: flex; + flex-direction: column; +} + +.ModuleName { + color: #17212b; + font-size: 13px; + font-weight: 700; + margin-bottom: 4px; +} + +.ModulePath { + color: #788590; + font-size: 10px; +} + +.ModuleStatus { + color: #30a66e; + font-size: 10px; + font-weight: 700; +} + +.SettingsPanel { + background-color: #17212b; + border-radius: 20px; + padding: 18px; +} + +.SettingsTitle { + color: #ffffff; + font-size: 18px; + font-weight: 750; + margin-bottom: 14px; +} + +.SettingsLabel { + color: #8da0af; + font-size: 10px; + margin-bottom: 4px; +} + +.SettingsValue { + color: #f7fbff; + font-size: 12px; + line-height: 18px; + margin-bottom: 14px; +} + +.BottomNav { + height: 76px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-around; + background-color: #ffffff; + border-top-width: 1px; + border-top-color: #e0e6ea; + padding-left: 8px; + padding-right: 8px; +} + +.NavItem { + width: 23%; + height: 54px; + align-items: center; + justify-content: center; +} + +.NavIndicator { + width: 18px; + height: 4px; + border-radius: 2px; + background-color: transparent; + margin-bottom: 8px; +} + +.NavIndicatorActive { + background-color: #ff6c47; +} + +.NavLabel { + color: #8b969f; + font-size: 10px; + font-weight: 600; +} + +.NavLabelActive { + color: #17212b; + font-weight: 750; +} diff --git a/apps/lynx-module-federation-demo/src/app/App.tsx b/apps/lynx-module-federation-demo/src/app/App.tsx new file mode 100644 index 00000000000..e1c88e09a8d --- /dev/null +++ b/apps/lynx-module-federation-demo/src/app/App.tsx @@ -0,0 +1,404 @@ +import { useCallback, useEffect, useState } from '@lynx-js/react'; + +import type { SharedStateView } from '../remote-ui/contracts'; +import { type LoadState, useFederatedCatalog } from './useFederatedCatalog'; +import './App.css'; + +type Screen = 'overview' | 'activity' | 'modules' | 'settings'; + +const NAVIGATION: Array<{ id: Screen; label: string }> = [ + { id: 'overview', label: 'Overview' }, + { id: 'activity', label: 'Activity' }, + { id: 'modules', label: 'Modules' }, + { id: 'settings', label: 'Settings' }, +]; + +const MODULES = [ + { name: 'Card', path: "import * as card from 'catalog/Card'" }, + { name: 'Details', path: "import('catalog/Details')" }, + { name: 'ActivityFeed', path: "loadRemote('catalog/ActivityFeed')" }, +]; + +function LoadButton({ + interactive, + state, + onTap, +}: { + interactive: boolean; + state: LoadState; + onTap: () => void; +}) { + const disabled = !interactive || state === 'loading' || state === 'ready'; + const label = !interactive + ? 'Starting host…' + : state === 'loading' + ? 'Loading catalog…' + : state === 'ready' + ? 'Catalog connected' + : state === 'error' + ? 'Retry catalog' + : 'Load remote catalog'; + const stateClass = + !interactive || state === 'loading' + ? 'PrimaryAction PrimaryActionLoading' + : state === 'ready' + ? 'PrimaryAction PrimaryActionReady' + : state === 'error' + ? 'PrimaryAction PrimaryActionError' + : 'PrimaryAction'; + + return ( + + {label} + MF MANIFEST · HTTP + + ); +} + +function DeliveryHealth({ ready }: { ready: boolean }) { + return ( + + + Delivery health + + {ready ? '3 MODULES ONLINE' : 'CONNECTING'} + + + + + {ready ? 'Ready' : 'Wait'} + Catalog remote + + + JSON + MF manifest + + + HTTP + Lynx bundle + + + + ); +} + +function ModuleList({ ready }: { ready: boolean }) { + return ( + + {MODULES.map((module, index) => ( + + + {module.name} + {module.path} + + {ready ? 'READY' : 'PENDING'} + + ))} + + ); +} + +function SingletonProof({ + ready, + shared, + state, +}: { + ready: boolean; + shared: boolean; + state: SharedStateView; +}) { + const status = !ready + ? { + accessibilityId: undefined, + accessibilityLabel: 'Waiting for remote observers', + className: 'SingletonProofStatus', + key: 'waiting', + text: 'Waiting for remote observers', + } + : shared + ? { + accessibilityId: 'federation-ready', + accessibilityLabel: + 'Federation ready: compiled imports, runtime API, shared singleton', + className: 'SingletonProofStatus SingletonProofStatusReady', + key: 'ready', + text: 'Shared singleton verified', + } + : { + accessibilityId: undefined, + accessibilityLabel: 'Singleton identity mismatch', + className: 'SingletonProofStatus', + key: 'mismatch', + text: 'Singleton identity mismatch', + }; + + return ( + + + HOST OBSERVER + + {state.count} + + + + REALM-LOCAL IDENTITY + + {status.text} + + + + ); +} + +export function App() { + const [screen, setScreen] = useState('overview'); + const [backgroundReady, setBackgroundReady] = useState(false); + const { + ActivityFeedComponent, + CardComponent, + DetailsComponent, + activity, + filter, + handleHostIncrement, + handleRemoteStateChange, + handleReset, + loadError, + loadFederatedSurface, + loadState, + selectFilter, + sharedState, + singletonShared, + } = useFederatedCatalog(); + + useEffect(() => { + 'background-only'; + setBackgroundReady(true); + }, []); + + const selectScreen = useCallback((nextScreen: Screen) => { + 'background-only'; + setScreen(nextScreen); + }, []); + + const renderActivity = () => + ActivityFeedComponent ? ( + + ) : ( + + + The runtime-loaded activity feed will appear after the catalog + manifest resolves. + + + ); + + return ( + + + + + O + + Orbit Control + + + + Connected + + + + + + + Federated workspace + + Live modules, shared state, and delivery health in one native Lynx + surface. + + + + + + + Compiled imports {loadState === 'ready' ? 'ready' : loadState} + + + Runtime API {loadState === 'ready' ? 'ready' : loadState} + + + {loadError ? ( + + {loadError} + + ) : null} + + + {screen === 'overview' ? ( + <> + + {CardComponent ? ( + + ) : ( + + + Loading the shared state card through a standard federated + import. + + + )} + {DetailsComponent ? : null} + + Live activity + RUNTIME API + + {renderActivity()} + + ) : null} + + {screen === 'activity' ? ( + <> + + Activity timeline + {activity.length} EVENTS + + {renderActivity()} + + + Add state event + + + Reset feed + + + + ) : null} + + {screen === 'modules' ? ( + <> + + Remote modules + CATALOG + + + + ) : null} + + {screen === 'settings' ? ( + <> + + Runtime settings + READ ONLY + + + Lynx federation + REMOTE ENTRY + + catalog@mf-manifest.json · HTTP + + REMOTE BUNDLE + + External .lynx.bundle container + + LAYERS + + Background and main thread, isolated per realm + + LAST ERROR + {loadError || 'None'} + + + ) : null} + + + + + {NAVIGATION.map((item) => ( + selectScreen(item.id)} + > + + + {item.label} + + + ))} + + + ); +} diff --git a/apps/lynx-module-federation-demo/src/app/catalogLoadController.test.ts b/apps/lynx-module-federation-demo/src/app/catalogLoadController.test.ts new file mode 100644 index 00000000000..3b741e54396 --- /dev/null +++ b/apps/lynx-module-federation-demo/src/app/catalogLoadController.test.ts @@ -0,0 +1,161 @@ +import type { ComponentType } from '@lynx-js/react'; +import { describe, expect, it, rs } from '@rstest/core'; + +import type { + ActivityFeedProps, + RemoteCardProps, + RemoteDetailsProps, + SharedStateView, +} from '../remote-ui/contracts'; +import type { + ActivityFeedRemoteModule, + CardRemoteModule, + DetailsRemoteModule, +} from './federation'; +import { createCatalogLoadController } from './catalogLoadController'; + +const component = (): ComponentType => + (() => null) as ComponentType; + +const deferred = () => { + let resolve!: (value: Value) => void; + let reject!: (error: unknown) => void; + const promise = new Promise((resolvePromise, rejectPromise) => { + resolve = resolvePromise; + reject = rejectPromise; + }); + return { promise, reject, resolve }; +}; + +const sharedState: SharedStateView = { + count: 3, + instanceId: 'shared-instance', + lastSource: 'catalog/Details', + revision: 2, +}; + +const createModules = (sharedToken: object) => { + const observer = { + sharedInstance: () => sharedState.instanceId, + sharedSnapshot: () => sharedState, + sharedToken: () => sharedToken, + }; + const card: CardRemoteModule = { + ...observer, + default: component(), + touchSharedState: () => sharedState, + }; + const details: DetailsRemoteModule = { + ...observer, + default: component(), + touchSharedState: () => sharedState, + }; + const activityFeed: ActivityFeedRemoteModule = { + ...observer, + default: component(), + }; + return { activityFeed, card, details }; +}; + +describe('catalog load controller', () => { + it('shares one in-flight transaction across repeated calls', async () => { + const sharedToken = {}; + const modules = createModules(sharedToken); + const compiled = deferred<{ + card: CardRemoteModule; + details: DetailsRemoteModule; + }>(); + const runtime = deferred(); + const loadCompiledImportRemotes = rs.fn(() => compiled.promise); + const loadRuntimeActivityFeed = rs.fn(() => runtime.promise); + const controller = createCatalogLoadController({ + instanceId: sharedState.instanceId, + loadCompiledImportRemotes, + loadRuntimeActivityFeed, + snapshot: () => sharedState, + token: sharedToken, + }); + + const first = controller.load(); + const second = controller.load(); + + expect(second).toBe(first); + expect(loadCompiledImportRemotes).toHaveBeenCalledTimes(1); + expect(loadRuntimeActivityFeed).toHaveBeenCalledTimes(1); + + compiled.resolve({ card: modules.card, details: modules.details }); + runtime.resolve(modules.activityFeed); + await expect(first).resolves.toMatchObject({ + activityFeed: modules.activityFeed.default, + card: modules.card.default, + details: modules.details.default, + sharedState, + singletonShared: true, + }); + }); + + it('discards partial results and retries with fresh modules', async () => { + const sharedToken = {}; + const firstModules = createModules(sharedToken); + const retryModules = createModules(sharedToken); + const firstRuntime = deferred(); + const failure = new Error('activity feed unavailable'); + const loadCompiledImportRemotes = rs + .fn() + .mockResolvedValueOnce({ + card: firstModules.card, + details: firstModules.details, + }) + .mockResolvedValueOnce({ + card: retryModules.card, + details: retryModules.details, + }); + const loadRuntimeActivityFeed = rs + .fn<() => Promise>() + .mockImplementationOnce(() => firstRuntime.promise) + .mockResolvedValueOnce(retryModules.activityFeed); + const controller = createCatalogLoadController({ + instanceId: sharedState.instanceId, + loadCompiledImportRemotes, + loadRuntimeActivityFeed, + snapshot: () => sharedState, + token: sharedToken, + }); + + const first = controller.load(); + firstRuntime.reject(failure); + await expect(first).rejects.toBe(failure); + + const retry = await controller.load(); + expect(retry.card).toBe(retryModules.card.default); + expect(retry.details).toBe(retryModules.details.default); + expect(retry.activityFeed).toBe(retryModules.activityFeed.default); + expect(loadCompiledImportRemotes).toHaveBeenCalledTimes(2); + expect(loadRuntimeActivityFeed).toHaveBeenCalledTimes(2); + }); + + it('validates singleton identity only on the first successful load', async () => { + const sharedToken = {}; + const modules = createModules(sharedToken); + const touchCard = rs.fn(() => sharedState); + const touchDetails = rs.fn(() => sharedState); + modules.card.touchSharedState = touchCard; + modules.details.touchSharedState = touchDetails; + const controller = createCatalogLoadController({ + instanceId: sharedState.instanceId, + loadCompiledImportRemotes: async () => ({ + card: modules.card, + details: modules.details, + }), + loadRuntimeActivityFeed: async () => modules.activityFeed, + snapshot: () => sharedState, + token: sharedToken, + }); + + await controller.load(); + await controller.load(); + + expect(touchCard).toHaveBeenCalledTimes(1); + expect(touchDetails).toHaveBeenCalledTimes(1); + }); +}); diff --git a/apps/lynx-module-federation-demo/src/app/catalogLoadController.ts b/apps/lynx-module-federation-demo/src/app/catalogLoadController.ts new file mode 100644 index 00000000000..016d6e21d64 --- /dev/null +++ b/apps/lynx-module-federation-demo/src/app/catalogLoadController.ts @@ -0,0 +1,113 @@ +import type { ComponentType } from '@lynx-js/react'; + +import type { + ActivityFeedProps, + RemoteCardProps, + RemoteDetailsProps, + SharedStateView, +} from '../remote-ui/contracts'; +import type { + ActivityFeedRemoteModule, + CardRemoteModule, + DetailsRemoteModule, +} from './federation'; + +export interface CatalogLoadResult { + activityFeed: ComponentType; + card: ComponentType; + details: ComponentType; + sharedState: SharedStateView; + singletonShared: boolean; +} + +interface CatalogLoadControllerDependencies { + instanceId: string; + loadCompiledImportRemotes(): Promise<{ + card: CardRemoteModule; + details: DetailsRemoteModule; + }>; + loadRuntimeActivityFeed(): Promise; + snapshot(): SharedStateView; + token: object; +} + +const sharesSingleton = ( + modules: { + activityFeed: ActivityFeedRemoteModule; + card: CardRemoteModule; + details: DetailsRemoteModule; + }, + sharedState: SharedStateView, + instanceId: string, + token: object, +): boolean => + modules.card.sharedToken() === token && + modules.details.sharedToken() === token && + modules.activityFeed.sharedToken() === token && + modules.card.sharedInstance() === instanceId && + modules.details.sharedInstance() === instanceId && + modules.activityFeed.sharedInstance() === instanceId && + [ + modules.card.sharedSnapshot(), + modules.details.sharedSnapshot(), + modules.activityFeed.sharedSnapshot(), + ].every( + (remoteState) => + remoteState.count === sharedState.count && + remoteState.instanceId === sharedState.instanceId, + ); + +export const createCatalogLoadController = ({ + instanceId, + loadCompiledImportRemotes, + loadRuntimeActivityFeed, + snapshot, + token, +}: CatalogLoadControllerDependencies) => { + let inFlight: Promise | undefined; + let validatedSingleton: boolean | undefined; + + const runTransaction = async (): Promise => { + const [compiled, activityFeed] = await Promise.all([ + loadCompiledImportRemotes(), + loadRuntimeActivityFeed(), + ]); + + let sharedState = snapshot(); + if (validatedSingleton === undefined) { + compiled.card.touchSharedState(); + sharedState = compiled.details.touchSharedState(); + validatedSingleton = sharesSingleton( + { activityFeed, ...compiled }, + sharedState, + instanceId, + token, + ); + } + + return { + activityFeed: activityFeed.default, + card: compiled.card.default, + details: compiled.details.default, + sharedState, + singletonShared: validatedSingleton, + }; + }; + + return { + load(): Promise { + if (inFlight) { + return inFlight; + } + + const transaction = runTransaction(); + const tracked = transaction.finally(() => { + if (inFlight === tracked) { + inFlight = undefined; + } + }); + inFlight = tracked; + return tracked; + }, + }; +}; diff --git a/apps/lynx-module-federation-demo/src/app/federation.d.ts b/apps/lynx-module-federation-demo/src/app/federation.d.ts new file mode 100644 index 00000000000..8f1ca3cfc5a --- /dev/null +++ b/apps/lynx-module-federation-demo/src/app/federation.d.ts @@ -0,0 +1,52 @@ +declare module 'orbit-shared-state' { + export type FederationStateSource = + | 'host' + | 'host/action' + | 'catalog/Card' + | 'catalog/Details'; + + export interface FederationStateSnapshot { + count: number; + instanceId: string; + lastSource: FederationStateSource; + revision: number; + } + + export const instanceId: string; + export const token: object; + export function increment(source?: FederationStateSource): number; + export function reset(): FederationStateSnapshot; + export function snapshot(): FederationStateSnapshot; +} + +declare module 'catalog/Card' { + const Card: import('@lynx-js/react').ComponentType< + import('../remote-ui/contracts.js').RemoteCardProps + >; + export const sharedInstance: () => string; + export const sharedSnapshot: typeof import('../shared-app/federationState.js').snapshot; + export const sharedToken: () => object; + export const touchSharedState: typeof import('../shared-app/federationState.js').snapshot; + export default Card; +} + +declare module 'catalog/Details' { + const Details: import('@lynx-js/react').ComponentType< + import('../remote-ui/contracts.js').RemoteDetailsProps + >; + export const sharedInstance: () => string; + export const sharedSnapshot: typeof import('../shared-app/federationState.js').snapshot; + export const sharedToken: () => object; + export const touchSharedState: typeof import('../shared-app/federationState.js').snapshot; + export default Details; +} + +declare module 'catalog/ActivityFeed' { + const ActivityFeed: import('@lynx-js/react').ComponentType< + import('../remote-ui/contracts.js').ActivityFeedProps + >; + export const sharedInstance: () => string; + export const sharedSnapshot: typeof import('../shared-app/federationState.js').snapshot; + export const sharedToken: () => object; + export default ActivityFeed; +} diff --git a/apps/lynx-module-federation-demo/src/app/federation.ts b/apps/lynx-module-federation-demo/src/app/federation.ts new file mode 100644 index 00000000000..6c63eaea59e --- /dev/null +++ b/apps/lynx-module-federation-demo/src/app/federation.ts @@ -0,0 +1,60 @@ +import type { ComponentType } from '@lynx-js/react'; +import { getInstance } from '@module-federation/runtime-tools'; + +import type { + ActivityFeedProps, + RemoteCardProps, + RemoteDetailsProps, + SharedStateView, +} from '../remote-ui/contracts'; + +export interface SharedObserverExports { + sharedInstance: () => string; + sharedSnapshot: () => SharedStateView; + sharedToken: () => object; +} + +export interface SharedRemoteExports extends SharedObserverExports { + touchSharedState: () => SharedStateView; +} + +export interface CardRemoteModule extends SharedRemoteExports { + default: ComponentType; +} + +export interface DetailsRemoteModule extends SharedRemoteExports { + default: ComponentType; +} + +export interface ActivityFeedRemoteModule extends SharedObserverExports { + default: ComponentType; +} + +export async function loadCompiledImportRemotes() { + 'background-only'; + const [{ card }, details] = await Promise.all([ + import('./staticCard'), + import('catalog/Details'), + ]); + + return { card, details }; +} + +export async function loadRuntimeActivityFeed() { + const instance = getInstance( + (candidate) => candidate.name === 'orbit_control', + ); + if (!instance) { + throw new Error('The orbit_control federation instance is unavailable.'); + } + + const activityFeed = await instance.loadRemote( + 'catalog/ActivityFeed', + ); + + if (!activityFeed) { + throw new Error('catalog/ActivityFeed returned no module.'); + } + + return activityFeed; +} diff --git a/apps/lynx-module-federation-demo/src/app/index.tsx b/apps/lynx-module-federation-demo/src/app/index.tsx new file mode 100644 index 00000000000..86c1e378589 --- /dev/null +++ b/apps/lynx-module-federation-demo/src/app/index.tsx @@ -0,0 +1,10 @@ +import '@lynx-js/react/experimental/lazy/import'; +import { root } from '@lynx-js/react'; + +import { App } from './App'; + +root.render(); + +if (import.meta.webpackHot) { + import.meta.webpackHot.accept(); +} diff --git a/apps/lynx-module-federation-demo/src/app/staticCard.ts b/apps/lynx-module-federation-demo/src/app/staticCard.ts new file mode 100644 index 00000000000..44aba54d297 --- /dev/null +++ b/apps/lynx-module-federation-demo/src/app/staticCard.ts @@ -0,0 +1,4 @@ +import 'background-only'; +import * as card from 'catalog/Card'; + +export { card }; diff --git a/apps/lynx-module-federation-demo/src/app/useFederatedCatalog.ts b/apps/lynx-module-federation-demo/src/app/useFederatedCatalog.ts new file mode 100644 index 00000000000..e36054023c4 --- /dev/null +++ b/apps/lynx-module-federation-demo/src/app/useFederatedCatalog.ts @@ -0,0 +1,224 @@ +import { useCallback, useRef, useState } from '@lynx-js/react'; + +import { + increment, + instanceId, + reset, + snapshot, + token, +} from 'orbit-shared-state'; +import type { + ActivityEntry, + ActivityFilter, + SharedStateView, +} from '../remote-ui/contracts'; +import { + type CatalogLoadResult, + createCatalogLoadController, +} from './catalogLoadController'; +import { + loadCompiledImportRemotes, + loadRuntimeActivityFeed, +} from './federation'; + +export type LoadState = 'idle' | 'loading' | 'ready' | 'error'; + +type CatalogState = + | { + status: 'idle' | 'loading'; + sharedState: SharedStateView; + } + | { + error: string; + sharedState: SharedStateView; + status: 'error'; + } + | (CatalogLoadResult & { status: 'ready' }); + +const INITIAL_ACTIVITY: ActivityEntry[] = [ + { + id: 'boot-host', + category: 'runtime', + detail: 'Rspeedy started the official ReactLynx host bundle.', + time: 'NOW', + title: 'Host launched', + }, +]; + +export function useFederatedCatalog() { + const [catalogState, setCatalogState] = useState({ + sharedState: snapshot(), + status: 'idle', + }); + const [activity, setActivity] = useState(INITIAL_ACTIVITY); + const [filter, setFilter] = useState('all'); + const controllerRef = useRef( + createCatalogLoadController({ + instanceId, + loadCompiledImportRemotes, + loadRuntimeActivityFeed, + snapshot, + token, + }), + ); + const inFlightRef = useRef | null>(null); + + const loadFederatedSurface = useCallback(() => { + 'background-only'; + if (inFlightRef.current) { + return inFlightRef.current; + } + if (catalogState.status === 'ready') { + return; + } + + setCatalogState((current) => ({ + sharedState: current.sharedState, + status: 'loading', + })); + const transaction = controllerRef.current.load(); + inFlightRef.current = transaction; + + void transaction + .then( + (result) => { + setCatalogState({ ...result, status: 'ready' }); + const nextSnapshot = result.sharedState; + setActivity((entries) => [ + { + id: `state-${nextSnapshot.revision}`, + category: 'state', + detail: `Host, Card, and Details share instance ${instanceId}.`, + time: 'NOW', + title: 'Shared counter updated', + }, + { + id: 'runtime-feed', + category: 'runtime', + detail: + "Runtime API resolved loadRemote('catalog/ActivityFeed').", + time: 'NOW', + title: 'Activity feed mounted', + }, + { + id: 'compiled-imports', + category: 'runtime', + detail: + 'Compiled imports resolved catalog/Card and catalog/Details.', + time: 'NOW', + title: 'Catalog modules mounted', + }, + { + id: 'manifest-resolved', + category: 'runtime', + detail: + 'mf-manifest.json resolved the remote Lynx bundle over HTTP.', + time: 'NOW', + title: 'Manifest resolved', + }, + ...entries.filter((entry) => entry.id === 'boot-host'), + ]); + }, + (error) => { + setCatalogState((current) => ({ + error: error instanceof Error ? error.message : String(error), + sharedState: current.sharedState, + status: 'error', + })); + }, + ) + .finally(() => { + if (inFlightRef.current === transaction) { + inFlightRef.current = null; + } + }); + return transaction; + }, [catalogState.status]); + + const handleHostIncrement = useCallback(() => { + 'background-only'; + increment('host/action'); + const nextSnapshot = snapshot(); + setCatalogState((current) => ({ + ...current, + sharedState: nextSnapshot, + })); + setActivity((entries) => [ + { + id: `increment-${nextSnapshot.revision}`, + category: 'state', + detail: `All singleton consumers now observe count ${nextSnapshot.count}.`, + time: 'NOW', + title: 'Shared counter incremented', + }, + ...entries, + ]); + }, []); + + const handleRemoteStateChange = useCallback( + (nextSnapshot: SharedStateView) => { + 'background-only'; + setCatalogState((current) => ({ + ...current, + sharedState: nextSnapshot, + })); + setActivity((entries) => [ + { + id: `increment-${nextSnapshot.revision}`, + category: 'state', + detail: `catalog/Card mutated the shared singleton to ${nextSnapshot.count}; every observer re-read it.`, + time: 'NOW', + title: 'Remote changed shared state', + }, + ...entries, + ]); + }, + [], + ); + + const handleReset = useCallback(() => { + 'background-only'; + const nextSnapshot = reset(); + setCatalogState((current) => ({ + ...current, + sharedState: nextSnapshot, + })); + setFilter('all'); + setActivity([ + { + id: `reset-${nextSnapshot.revision}`, + category: 'state', + detail: + 'Host reset the shared singleton and cleared the activity feed.', + time: 'NOW', + title: 'Workspace reset', + }, + ...INITIAL_ACTIVITY, + ]); + }, []); + + const selectFilter = useCallback((nextFilter: ActivityFilter) => { + 'background-only'; + setFilter(nextFilter); + }, []); + + return { + ActivityFeedComponent: + catalogState.status === 'ready' ? catalogState.activityFeed : null, + CardComponent: catalogState.status === 'ready' ? catalogState.card : null, + DetailsComponent: + catalogState.status === 'ready' ? catalogState.details : null, + activity, + filter, + handleHostIncrement, + handleRemoteStateChange, + handleReset, + loadError: catalogState.status === 'error' ? catalogState.error : '', + loadFederatedSurface, + loadState: catalogState.status, + selectFilter, + sharedState: catalogState.sharedState, + singletonShared: + catalogState.status === 'ready' && catalogState.singletonShared, + }; +} diff --git a/apps/lynx-module-federation-demo/src/catalog-app/CatalogApp.css b/apps/lynx-module-federation-demo/src/catalog-app/CatalogApp.css new file mode 100644 index 00000000000..df52d807094 --- /dev/null +++ b/apps/lynx-module-federation-demo/src/catalog-app/CatalogApp.css @@ -0,0 +1,285 @@ +:root { + background-color: #f2f5f7; +} + +.CatalogPage { + width: 100vw; + height: 100vh; + display: flex; + flex-direction: column; + background-color: #f2f5f7; +} + +.CatalogTopBar { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 20px 20px 12px; +} + +.CatalogBrand { + display: flex; + flex-direction: row; + align-items: center; +} + +.CatalogBrandMark { + width: 36px; + height: 36px; + border-radius: 12px; + background-color: #ff6c47; + align-items: center; + justify-content: center; + margin-right: 10px; +} + +.CatalogBrandMarkText { + color: #ffffff; + font-size: 17px; + font-weight: 800; +} + +.CatalogBrandCopy { + display: flex; + flex-direction: column; +} + +.CatalogBrandName { + color: #17212b; + font-size: 17px; + font-weight: 750; +} + +.CatalogBrandMeta { + color: #89949e; + font-size: 9px; + font-weight: 700; + margin-top: 2px; +} + +.CatalogLiveBadge { + display: flex; + flex-direction: row; + align-items: center; + padding: 8px 10px; + border-radius: 14px; + background-color: #e0f8eb; +} + +.CatalogLiveDot { + width: 7px; + height: 7px; + border-radius: 4px; + background-color: #30bd78; + margin-right: 6px; +} + +.CatalogLiveText { + color: #247b53; + font-size: 10px; + font-weight: 750; +} + +.CatalogContent { + flex: 1; + padding-left: 20px; + padding-right: 20px; +} + +.CatalogScreen { + display: flex; + flex-direction: column; + padding-bottom: 28px; +} + +.CatalogHero { + display: flex; + flex-direction: column; + padding-top: 18px; + padding-bottom: 20px; +} + +.CatalogEyebrow { + color: #e45331; + font-size: 10px; + font-weight: 750; + letter-spacing: 1.2px; + margin-bottom: 9px; +} + +.CatalogHeroTitle { + color: #17212b; + font-size: 32px; + line-height: 37px; + font-weight: 800; + letter-spacing: -0.7px; + margin-bottom: 9px; +} + +.CatalogHeroCopy { + color: #64717d; + font-size: 13px; + line-height: 19px; + max-width: 350px; +} + +.CatalogProof { + display: flex; + flex-direction: row; + align-items: center; + min-height: 96px; + padding: 16px; + border-radius: 20px; + background-color: #17212b; + margin-bottom: 22px; +} + +.CatalogProofMetric { + display: flex; + flex-direction: column; + flex: 1; + align-items: center; +} + +.CatalogProofMetricWide { + flex: 1.25; +} + +.CatalogProofValue { + color: #ffffff; + font-size: 27px; + font-weight: 800; + margin-bottom: 5px; +} + +.CatalogProofValueSmall { + color: #60e2a4; + font-size: 13px; + margin-top: 8px; + margin-bottom: 10px; +} + +.CatalogProofLabel { + color: #8799a9; + font-size: 8px; + font-weight: 700; +} + +.CatalogProofDivider { + width: 1px; + height: 48px; + background-color: #34424f; +} + +.CatalogSectionHeader { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + margin-top: 4px; + margin-bottom: 12px; +} + +.CatalogActivityHeader { + margin-top: 14px; +} + +.CatalogSectionTitle { + color: #17212b; + font-size: 18px; + font-weight: 750; +} + +.CatalogSectionMeta { + color: #89949e; + font-size: 9px; + font-weight: 700; +} + +.CatalogExposeList { + display: flex; + flex-direction: column; + background-color: #ffffff; + border-width: 1px; + border-color: #e2e8eb; + border-radius: 20px; + padding-left: 16px; + padding-right: 16px; + margin-bottom: 22px; +} + +.CatalogExposeRow { + display: flex; + flex-direction: row; + align-items: center; + min-height: 68px; + border-bottom-width: 1px; + border-bottom-color: #e8edf0; +} + +.CatalogExposeRowLast { + border-bottom-width: 0; +} + +.CatalogExposeIcon { + width: 34px; + height: 34px; + border-radius: 11px; + align-items: center; + justify-content: center; + background-color: #effcf6; + margin-right: 11px; +} + +.CatalogExposeIconText { + color: #218559; + font-size: 13px; + font-weight: 800; +} + +.CatalogExposeCopy { + display: flex; + flex: 1; + flex-direction: column; +} + +.CatalogExposeName { + color: #17212b; + font-size: 13px; + font-weight: 700; +} + +.CatalogExposePath { + color: #7b8791; + font-size: 10px; + margin-top: 3px; +} + +.CatalogExposeStatus { + color: #2a9665; + font-size: 9px; + font-weight: 750; +} + +.CatalogFooter { + display: flex; + flex-direction: column; + padding: 18px; + border-radius: 18px; + background-color: #ffece6; + margin-top: 18px; +} + +.CatalogFooterTitle { + color: #8e3523; + font-size: 14px; + font-weight: 750; + margin-bottom: 5px; +} + +.CatalogFooterCopy { + color: #a04b37; + font-size: 11px; + line-height: 16px; +} diff --git a/apps/lynx-module-federation-demo/src/catalog-app/CatalogApp.tsx b/apps/lynx-module-federation-demo/src/catalog-app/CatalogApp.tsx new file mode 100644 index 00000000000..5f2dbbf86a9 --- /dev/null +++ b/apps/lynx-module-federation-demo/src/catalog-app/CatalogApp.tsx @@ -0,0 +1,184 @@ +import { useCallback, useState } from '@lynx-js/react'; + +import { snapshot } from 'orbit-shared-state'; +import { ActivityFeed } from '../remote-ui/ActivityFeed'; +import { Card } from '../remote-ui/Card'; +import type { + ActivityEntry, + ActivityFilter, + SharedStateView, +} from '../remote-ui/contracts'; +import { Details } from '../remote-ui/Details'; +import './CatalogApp.css'; + +const INITIAL_ACTIVITY: ActivityEntry[] = [ + { + id: 'catalog-boot', + category: 'runtime', + detail: 'Rspeedy launched the Catalog as its own ReactLynx product.', + time: 'NOW', + title: 'Catalog app launched', + }, + { + id: 'catalog-exposes', + category: 'runtime', + detail: 'Card, Details, and ActivityFeed are also published as exposes.', + time: 'NOW', + title: 'Federation surface ready', + }, +]; + +const EXPOSES = [ + { name: 'Card', path: 'catalog/Card' }, + { name: 'Details', path: 'catalog/Details' }, + { name: 'ActivityFeed', path: 'catalog/ActivityFeed' }, +]; + +export function CatalogApp() { + const [activity, setActivity] = useState(INITIAL_ACTIVITY); + const [filter, setFilter] = useState('all'); + const [sharedState, setSharedState] = useState(snapshot()); + + const handleStateChange = useCallback((nextState: SharedStateView) => { + 'background-only'; + setSharedState(nextState); + setActivity((entries) => [ + { + id: `catalog-state-${nextState.revision}`, + category: 'state', + detail: `The standalone Card updated the local singleton to ${nextState.count}.`, + time: 'NOW', + title: 'Shared state changed', + }, + ...entries, + ]); + }, []); + + const selectFilter = useCallback((nextFilter: ActivityFilter) => { + 'background-only'; + setFilter(nextFilter); + }, []); + + return ( + + + + + C + + + Orbit Catalog + REMOTE PRODUCT + + + + + + LIVE + + + + + + + + STANDALONE + FEDERATED + + One product, three exports. + + + This app runs on its own. Orbit Control imports the exact same + components from its manifest over HTTP. + + + + + + 3 + EXPOSED MODULES + + + + + {sharedState.count} + + LOCAL SHARED COUNT + + + + + NATIVE + + LYNX + WEB + + + + + Published surface + MF MANIFEST + + + {EXPOSES.map((expose, index) => ( + + + + {expose.name.slice(0, 1)} + + + + {expose.name} + {expose.path} + + EXPOSED + + ))} + + + + Local composition + DIRECT IMPORTS + + +
+ + + Product activity + SHARED FUNCTIONALITY + + + + + Built once with Rspeedy + + Launch main.lynx.bundle as this app, or consume the three lazy + exposes through mf-manifest.json. + + + + + + ); +} diff --git a/apps/lynx-module-federation-demo/src/catalog-app/index.tsx b/apps/lynx-module-federation-demo/src/catalog-app/index.tsx new file mode 100644 index 00000000000..2472b2e6845 --- /dev/null +++ b/apps/lynx-module-federation-demo/src/catalog-app/index.tsx @@ -0,0 +1,10 @@ +import '@lynx-js/react/experimental/lazy/import'; +import { root } from '@lynx-js/react'; + +import { CatalogApp } from './CatalogApp'; + +root.render(); + +if (import.meta.webpackHot) { + import.meta.webpackHot.accept(); +} diff --git a/apps/lynx-module-federation-demo/src/remote-ui/ActivityFeed.tsx b/apps/lynx-module-federation-demo/src/remote-ui/ActivityFeed.tsx new file mode 100644 index 00000000000..d51faae6d8a --- /dev/null +++ b/apps/lynx-module-federation-demo/src/remote-ui/ActivityFeed.tsx @@ -0,0 +1,136 @@ +import { useCallback, useEffect, useState } from '@lynx-js/react'; +import { instanceId, snapshot, token } from 'orbit-shared-state'; + +import type { ActivityFeedProps, ActivityFilter } from './contracts'; +import './Remote.css'; + +const FILTERS: Array<{ id: ActivityFilter; label: string }> = [ + { id: 'all', label: 'All' }, + { id: 'runtime', label: 'Runtime' }, + { id: 'state', label: 'Shared state' }, +]; + +const loadActivityMetadata = () => { + 'background-only'; + return import( + /* webpackChunkName: 'activity-metadata' */ './activityMetadata' + ); +}; + +export function ActivityFeed({ + entries, + filter, + onFilterChange, +}: ActivityFeedProps) { + const sharedState = snapshot(); + const [metadata, setMetadata] = useState('Loading nested module'); + const selectFilter = useCallback( + (nextFilter: ActivityFilter) => { + 'background-only'; + onFilterChange(nextFilter); + }, + [onFilterChange], + ); + useEffect(() => { + 'background-only'; + let mounted = true; + void loadActivityMetadata().then( + (module) => { + if (mounted) setMetadata(module.activityMetadata); + }, + () => { + if (mounted) setMetadata('Nested federated module failed'); + }, + ); + return () => { + mounted = false; + }; + }, []); + + const visibleEntries = + filter === 'all' + ? entries + : entries.filter((entry) => entry.category === filter); + + return ( + + + Federated activity + + SHARED COUNT {sharedState.count} + + + {metadata} + + + + {FILTERS.map((item) => ( + selectFilter(item.id)} + > + + {item.label} + + + ))} + + + + {visibleEntries.map((entry) => ( + + + + + {entry.title} + {entry.time} + + {entry.detail} + + + ))} + + + {visibleEntries.length === 0 ? ( + + No activity in this filter. + + ) : null} + + ); +} + +export const sharedInstance = () => instanceId; +export const sharedSnapshot = snapshot; +export const sharedToken = () => token; + +export default ActivityFeed; diff --git a/apps/lynx-module-federation-demo/src/remote-ui/Card.tsx b/apps/lynx-module-federation-demo/src/remote-ui/Card.tsx new file mode 100644 index 00000000000..855c197cfd6 --- /dev/null +++ b/apps/lynx-module-federation-demo/src/remote-ui/Card.tsx @@ -0,0 +1,77 @@ +import { useCallback } from '@lynx-js/react'; +import { increment, instanceId, snapshot, token } from 'orbit-shared-state'; + +import type { RemoteCardProps } from './contracts'; +import './Remote.css'; + +export function Card({ loadPath, onStateChange }: RemoteCardProps) { + const sharedState = snapshot(); + const incrementFromRemote = useCallback(() => { + 'background-only'; + increment('catalog/Card'); + onStateChange(snapshot()); + }, [onStateChange]); + + return ( + + + + Shared state + + + + + REMOTE CARD + + + + + + + {sharedState.count} + + read by catalog/Card + + + + + Last writer + + {sharedState.lastSource} + + + + Revision + {sharedState.revision} + + + + + Direct singleton read · {sharedState.instanceId} · {loadPath} + + + + Increment from remote + + + ); +} + +export function touchSharedState() { + increment('catalog/Card'); + return snapshot(); +} + +export const sharedInstance = () => instanceId; +export const sharedSnapshot = snapshot; +export const sharedToken = () => token; + +export default Card; diff --git a/apps/lynx-module-federation-demo/src/remote-ui/Details.tsx b/apps/lynx-module-federation-demo/src/remote-ui/Details.tsx new file mode 100644 index 00000000000..d037f3e16cd --- /dev/null +++ b/apps/lynx-module-federation-demo/src/remote-ui/Details.tsx @@ -0,0 +1,48 @@ +import { increment, instanceId, snapshot, token } from 'orbit-shared-state'; + +import './Remote.css'; + +export function Details() { + const sharedState = snapshot(); + + return ( + + + Realm status + + + REMOTE DETAILS + + + + + + Background + + READY · COUNT {sharedState.count} + + + + Main thread + READY · ISOLATED + + + + + Direct singleton read · {sharedState.instanceId} · revision{' '} + {sharedState.revision} + + + ); +} + +export const sharedInstance = () => instanceId; +export const sharedSnapshot = snapshot; +export const sharedToken = () => token; + +export function touchSharedState() { + increment('catalog/Details'); + return snapshot(); +} + +export default Details; diff --git a/apps/lynx-module-federation-demo/src/remote-ui/Remote.css b/apps/lynx-module-federation-demo/src/remote-ui/Remote.css new file mode 100644 index 00000000000..a9826ebe166 --- /dev/null +++ b/apps/lynx-module-federation-demo/src/remote-ui/Remote.css @@ -0,0 +1,287 @@ +.RemotePanel { + display: flex; + flex-direction: column; + background-color: #17212b; + border-width: 1px; + border-color: #2c3946; + border-radius: 20px; + padding: 18px; + margin-bottom: 12px; +} + +.RemotePanelAccent { + background-color: #effcf6; + border-color: #b7ecd1; +} + +.RemotePanelHeader { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.RemotePanelTitle { + color: #f7fbff; + font-size: 18px; + font-weight: 700; +} + +.RemotePanelTitleDark { + color: #13221a; +} + +.RemoteStatus { + display: flex; + flex-direction: row; + align-items: center; +} + +.RemoteStatusDot { + width: 8px; + height: 8px; + border-radius: 4px; + background-color: #60e2a4; + margin-right: 7px; +} + +.RemoteStatusText { + color: #93a3b3; + font-size: 11px; + font-weight: 600; +} + +.RemoteStatusTextDark { + color: #35654d; +} + +.HealthRow { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding-top: 10px; + padding-bottom: 10px; + border-bottom-width: 1px; + border-bottom-color: #2c3946; +} + +.HealthRowLast { + border-bottom-width: 0; +} + +.HealthLabel { + color: #93a3b3; + font-size: 13px; +} + +.HealthValue { + color: #f7fbff; + font-size: 13px; + font-weight: 650; +} + +.CountHero { + display: flex; + flex-direction: row; + align-items: flex-end; + margin-bottom: 14px; +} + +.CountValue { + color: #13221a; + font-size: 44px; + font-weight: 750; + line-height: 48px; +} + +.CountLabel { + color: #52705f; + font-size: 12px; + margin-left: 10px; + margin-bottom: 6px; +} + +.CountGrid { + display: flex; + flex-direction: row; + justify-content: space-between; + background-color: #dff7e9; + border-radius: 14px; + padding: 12px; + margin-bottom: 12px; +} + +.CountCell { + display: flex; + flex-direction: column; + align-items: center; + width: 31%; +} + +.CountCellLabel { + color: #52705f; + font-size: 10px; + margin-bottom: 4px; +} + +.CountCellValue { + color: #13221a; + font-size: 18px; + font-weight: 700; +} + +.RemoteAction { + height: 42px; + border-radius: 13px; + background-color: #ff6c47; + align-items: center; + justify-content: center; +} + +.RemoteActionText { + color: #ffffff; + font-size: 13px; + font-weight: 700; +} + +.IdentityLine { + color: #35654d; + font-size: 11px; + line-height: 16px; + margin-bottom: 12px; +} + +.RealmGrid { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.RealmCell { + width: 48%; + background-color: #22303d; + border-radius: 14px; + padding: 13px; +} + +.RealmName { + color: #f7fbff; + font-size: 12px; + font-weight: 650; + margin-bottom: 6px; +} + +.RealmMeta { + color: #60e2a4; + font-size: 10px; +} + +.ActivityPanel { + display: flex; + flex-direction: column; + margin-top: 8px; +} + +.FilterRow { + display: flex; + flex-direction: row; + margin-bottom: 12px; +} + +.FilterButton { + height: 34px; + padding-left: 14px; + padding-right: 14px; + border-radius: 17px; + background-color: #e8edf1; + align-items: center; + justify-content: center; + margin-right: 8px; +} + +.FilterButtonActive { + background-color: #17212b; +} + +.FilterText { + color: #64717d; + font-size: 11px; + font-weight: 650; +} + +.FilterTextActive { + color: #ffffff; +} + +.ActivityList { + width: 100%; + height: 324px; + background-color: #ffffff; + border-radius: 18px; +} + +.ActivityRow { + width: 100%; + min-height: 76px; + display: flex; + flex-direction: row; + padding: 14px; + border-bottom-width: 1px; + border-bottom-color: #e8edf1; +} + +.ActivityMarker { + width: 10px; + height: 10px; + border-radius: 5px; + background-color: #4a7cfa; + margin-top: 4px; + margin-right: 12px; +} + +.ActivityMarkerState { + background-color: #ff6c47; +} + +.ActivityCopy { + display: flex; + flex-direction: column; + flex: 1; +} + +.ActivityTopline { + display: flex; + flex-direction: row; + justify-content: space-between; + margin-bottom: 4px; +} + +.ActivityTitle { + color: #17212b; + font-size: 13px; + font-weight: 700; +} + +.ActivityTime { + color: #89949e; + font-size: 10px; +} + +.ActivityDetail { + color: #64717d; + font-size: 11px; + line-height: 16px; +} + +.EmptyActivity { + height: 140px; + align-items: center; + justify-content: center; +} + +.EmptyActivityText { + color: #64717d; + font-size: 13px; +} diff --git a/apps/lynx-module-federation-demo/src/remote-ui/activityMetadata.ts b/apps/lynx-module-federation-demo/src/remote-ui/activityMetadata.ts new file mode 100644 index 00000000000..a4e154d8bd7 --- /dev/null +++ b/apps/lynx-module-federation-demo/src/remote-ui/activityMetadata.ts @@ -0,0 +1,3 @@ +import 'background-only'; + +export const activityMetadata = 'Nested federated module ready'; diff --git a/apps/lynx-module-federation-demo/src/remote-ui/bootstrap.ts b/apps/lynx-module-federation-demo/src/remote-ui/bootstrap.ts new file mode 100644 index 00000000000..cb0ff5c3b54 --- /dev/null +++ b/apps/lynx-module-federation-demo/src/remote-ui/bootstrap.ts @@ -0,0 +1 @@ +export {}; diff --git a/apps/lynx-module-federation-demo/src/remote-ui/contracts.ts b/apps/lynx-module-federation-demo/src/remote-ui/contracts.ts new file mode 100644 index 00000000000..bff4168cc06 --- /dev/null +++ b/apps/lynx-module-federation-demo/src/remote-ui/contracts.ts @@ -0,0 +1,29 @@ +export interface RemoteCardProps { + loadPath: string; + onStateChange: (state: SharedStateView) => void; +} + +export interface SharedStateView { + count: number; + instanceId: string; + lastSource: string; + revision: number; +} + +export type RemoteDetailsProps = Record; + +export type ActivityFilter = 'all' | 'runtime' | 'state'; + +export interface ActivityEntry { + id: string; + category: Exclude; + detail: string; + time: string; + title: string; +} + +export interface ActivityFeedProps { + entries: ActivityEntry[]; + filter: ActivityFilter; + onFilterChange: (filter: ActivityFilter) => void; +} diff --git a/apps/lynx-module-federation-demo/src/shared-app/federationState.ts b/apps/lynx-module-federation-demo/src/shared-app/federationState.ts new file mode 100644 index 00000000000..bb67da54a3b --- /dev/null +++ b/apps/lynx-module-federation-demo/src/shared-app/federationState.ts @@ -0,0 +1,40 @@ +export type FederationStateSource = + | 'host' + | 'host/action' + | 'catalog/Card' + | 'catalog/Details'; + +export interface FederationStateSnapshot { + count: number; + instanceId: string; + lastSource: FederationStateSource; + revision: number; +} + +export const instanceId = `orbit-${Date.now().toString(36)}-${Math.random() + .toString(36) + .slice(2, 8)}`; + +export const token = {}; + +let count = 1; +let lastSource: FederationStateSource = 'host'; +let revision = 0; + +export function increment(source: FederationStateSource = 'host'): number { + count += 1; + revision += 1; + lastSource = source; + return count; +} + +export function reset(): FederationStateSnapshot { + count = 0; + revision += 1; + lastSource = 'host'; + return snapshot(); +} + +export function snapshot(): FederationStateSnapshot { + return { count, instanceId, lastSource, revision }; +} diff --git a/apps/lynx-module-federation-demo/test/ci-policy.mjs b/apps/lynx-module-federation-demo/test/ci-policy.mjs new file mode 100644 index 00000000000..840fd5bbcae --- /dev/null +++ b/apps/lynx-module-federation-demo/test/ci-policy.mjs @@ -0,0 +1,86 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const appRoot = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '..', +); +const repoRoot = path.resolve(appRoot, '../..'); +const readRepo = (file) => readFile(path.join(repoRoot, file), 'utf8'); + +const [metroWorkflow, lynxWorkflow, localCi, readme, packageSource] = + await Promise.all([ + readRepo('.github/workflows/e2e-metro.yml'), + readRepo('.github/workflows/e2e-lynx.yml'), + readRepo('tools/scripts/ci-local.mjs'), + readFile(path.join(appRoot, 'README.md'), 'utf8'), + readFile(path.join(appRoot, 'package.json'), 'utf8'), + ]); + +assert.match(metroWorkflow, /ANDROID_EMULATOR_PARTITION_SIZE_MB: 1024/); +assert.match( + metroWorkflow, + /disk-size: \$\{\{ env\.ANDROID_EMULATOR_PARTITION_SIZE_MB \}\}M/, +); +assert.match( + metroWorkflow, + /-partition-size \$\{\{ env\.ANDROID_EMULATOR_PARTITION_SIZE_MB \}\}/, +); +assert.doesNotMatch(metroWorkflow, /ANDROID_EMULATOR_DISK_SPACE/); +assert.doesNotMatch(metroWorkflow, /-partition-size\s+\d+/); +assert.match(lynxWorkflow, /run test:ci-policy/); +const lynxIosJob = lynxWorkflow.slice(lynxWorkflow.indexOf(' e2e-lynx-ios:')); +assert.match( + lynxIosJob, + /name: Restore iOS build cache\s+id: ios-build-cache\s+uses: actions\/cache\/restore@/, +); +assert.match( + lynxIosJob, + /name: Install iOS dependencies\s+if: steps\.ios-build-cache\.outputs\.cache-hit != 'true'/, +); +assert.match( + lynxIosJob, + /name: Validate cached iOS dependencies\s+if: steps\.ios-build-cache\.outputs\.cache-hit == 'true'/, +); +assert.match( + lynxIosJob, + /name: Save iOS build cache[\s\S]*?uses: actions\/cache\/save@/, +); +assert.match(lynxIosJob, /lynx-ios-build-v3-/); +for (const action of ['Restore', 'Save']) { + assert.match( + lynxIosJob, + new RegExp( + `name: ${action} iOS build cache[\\s\\S]*?path: \\|[\\s\\S]*?ios/Pods[\\s\\S]*?ios/OrbitControl\\.xcworkspace[\\s\\S]*?ios/build/DerivedData/Build`, + ), + ); +} +assert.match( + lynxIosJob, + /cmp Podfile\.lock Pods\/Manifest\.lock\s+test -f OrbitControl\.xcworkspace\/contents\.xcworkspacedata/, +); +assert.doesNotMatch(lynxIosJob, /restore-keys:/); +for (const script of ['e2e:native:ci', 'e2e:web:ci']) { + assert.match(lynxWorkflow, new RegExp(`run ${script}`)); + assert.match(localCi, new RegExp(`'${script}'`)); +} + +assert.match(readme, /rspack-canary-rspeedy\.mjs/); +assert.match(readme, /@rspack-canary\/core/); +assert.match( + readme, + /remove.*Rspeedy supports the repository's\s+Rspack package directly/is, +); + +const { scripts } = JSON.parse(packageSource); +const rspeedyCommands = Object.entries(scripts).filter(([, command]) => + command.includes('rspeedy'), +); +assert.ok(rspeedyCommands.length > 0); +for (const [name, command] of rspeedyCommands) { + assert.match(command, /node rspack-canary-rspeedy\.mjs/, name); +} + +process.stdout.write('Lynx and Android CI policy validated.\n'); diff --git a/apps/lynx-module-federation-demo/test/ios-project.mjs b/apps/lynx-module-federation-demo/test/ios-project.mjs new file mode 100644 index 00000000000..bdaefab9893 --- /dev/null +++ b/apps/lynx-module-federation-demo/test/ios-project.mjs @@ -0,0 +1,194 @@ +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import { access, readFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const appRoot = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '..', +); +const read = (relativePath) => + readFile(path.join(appRoot, relativePath), 'utf8'); + +await Promise.all([ + access(path.join(appRoot, 'ios/OrbitControl.xcodeproj/project.pbxproj')), + access(path.join(appRoot, 'ios/OrbitControl/AppDelegate.swift')), + access( + path.join(appRoot, 'ios/OrbitControlUITests/OrbitControlUITests.swift'), + ), + ...[ + 'OrbitResourceDownloader.h', + 'OrbitResourceDownloader.m', + 'OrbitResourceStore.h', + 'OrbitResourceStore.m', + 'OrbitResourceURLResolver.h', + 'OrbitResourceURLResolver.m', + ].map((file) => access(path.join(appRoot, 'ios/OrbitControl', file))), + access(path.join(appRoot, 'ios/OrbitControlTests/OrbitResourceTests.m')), +]); + +const [ + podfile, + podfileLock, + provenance, + viewController, + releaseInfo, + debugInfo, + project, + uiTest, + appSource, + packageJson, + deviceServer, + syncScript, + iosRunner, + scheme, + urlResolver, + nativeHostConfig, + webHostConfig, + nativeCatalogConfig, + webCatalogConfig, + nativeRemoteConfig, + webRemoteConfig, +] = await Promise.all([ + read('ios/Podfile'), + read('ios/Podfile.lock'), + read('ios/UPSTREAM.md'), + read('ios/OrbitControl/ViewController.swift'), + read('ios/OrbitControl/Info.plist'), + read('ios/OrbitControl/Info.Debug.plist'), + read('ios/OrbitControl.xcodeproj/project.pbxproj'), + read('ios/OrbitControlUITests/OrbitControlUITests.swift'), + read('src/app/App.tsx'), + read('package.json'), + read('scripts/dev-ios-device.mjs'), + read('scripts/sync-ios-bundle.mjs'), + read('test/ios/run.mjs'), + read( + 'ios/OrbitControl.xcodeproj/xcshareddata/xcschemes/OrbitControl.xcscheme', + ), + read('ios/OrbitControl/OrbitResourceURLResolver.m'), + read('lynx.config.mjs'), + read('lynx.web.config.mjs'), + read('lynx.catalog.native.config.mjs'), + read('lynx.catalog.web.config.mjs'), + read('lynx.remote.native.config.mjs'), + read('lynx.remote.web.config.mjs'), +]); + +for (const pod of ['Lynx', 'LynxService', 'XElement']) { + assert.match(podfile, new RegExp(`pod '${pod}', '3\\.9\\.0'`)); +} +assert.match(podfile, /pod 'PrimJS', '3\.8\.0-alpha\.6'/); +assert.match(podfileLock, /Lynx \(3\.9\.0\)/); +assert.match(podfileLock, /PrimJS\/quickjs \(3\.8\.0-alpha\.6\)/); +assert.match(provenance, /integrating-lynx-demo-projects/); +assert.match(provenance, /f8230ca6aa1c9e629e30272971d0c03450b13e8e/); +assert.doesNotMatch(releaseInfo, /NSAllowsArbitraryLoads/); +assert.doesNotMatch(releaseInfo, /NSExceptionAllowsInsecureHTTPLoads/); +assert.match(releaseInfo, /NSAllowsLocalNetworking/); +assert.match(debugInfo, /NSAllowsLocalNetworking/); +assert.match(debugInfo, /127\.0\.0\.1<\/key>/); +assert.match(debugInfo, /localhost<\/key>/); +assert.doesNotMatch(debugInfo, /NSAllowsArbitraryLoads/); +assert.doesNotMatch(project, /DEVELOPMENT_TEAM/); +assert.match(project, /OrbitControlUITests/); +assert.match(project, /OrbitControlTests\.xctest/); +for (const file of [ + 'OrbitResourceDownloader.m', + 'OrbitResourceStore.m', + 'OrbitResourceURLResolver.m', + 'OrbitResourceTests.m', +]) { + assert.match(project, new RegExp(`${file.replace('.', '\\.')} in Sources`)); +} +assert.match(scheme, /BlueprintName="OrbitControlTests"/); +assert.match(project, /host-native in Resources/); +assert.match(uiTest, /matching\(identifier: "federation-ready"\)/); +assert.match(appSource, /accessibilityId: 'federation-ready'/); +assert.match( + appSource, + /const \[backgroundReady, setBackgroundReady\] = useState\(false\)/, +); +assert.match( + appSource, + /useEffect\(\(\) => \{\s*'background-only';\s*setBackgroundReady\(true\);/, +); +assert.match( + appSource, + /useEffect\(\(\) => \{\s*'background-only';\s*setBackgroundReady\(true\);\s*\}, \[\]\);/, +); +assert.doesNotMatch( + appSource, + /markFirstScreenSyncReady|setTimeout|queueMicrotask/, +); +for (const hostConfig of [nativeHostConfig, webHostConfig]) { + assert.match(hostConfig, /firstScreenSyncTiming: 'jsReady'/); +} +for (const config of [ + nativeHostConfig, + webHostConfig, + nativeCatalogConfig, + webCatalogConfig, + nativeRemoteConfig, + webRemoteConfig, +]) { + assert.match(config, /engineVersion: '3\.9'/); +} +assert.match(appSource, /interactive=\{backgroundReady\}/); +assert.match( + viewController, + /OrbitResourceFetcher\(\s*rootBundleURL: rootBundleURL\s*\)/, +); +assert.match( + appSource, + /ios-platform-accessibility-id=\{status\.accessibilityId\}/, +); +assert.match(uiTest, /testEmbeddedReleaseHostLaunches/); +assert.match(packageJson, /"ios:device": "node scripts\/dev-ios-device\.mjs"/); +assert.match(deviceServer, /LYNX_DEV_HOST: '0\.0\.0\.0'/); +assert.match(deviceServer, /not a loopback or unspecified address/); +assert.match(syncScript, /dist\/host-native\/main\.lynx\.bundle/); +assert.match(syncScript, /sourceLazyBundles/); +assert.match(syncScript, /cp\(sourceLazyBundles, destinationLazyBundles/); +assert.match(urlResolver, /hasPrefix:@"\/host-native\/"/); +assert.match(urlResolver, /hasPrefix:@"\/catalog-native\/"/); +assert.equal(iosRunner.match(/await run\(\s*'xcodebuild'/g)?.length, 1); +assert.match(iosRunner, /'-configuration',\s*'Release'/); +assert.match(iosRunner, /build\/OrbitControl-Release\.xcresult/); +assert.match(iosRunner, /simulator\.log/); +assert.match(iosRunner, /'-test-iterations',\s*'2'/); +assert.match(iosRunner, /'-retry-tests-on-failure'/); +assert.match(iosRunner, /-only-testing:OrbitControlTests/); +for (const testName of [ + 'testFederatedImportsRuntimeLoadingAndSingleton', + 'testStandaloneCatalogRemoteBuildLaunches', + 'testEmbeddedReleaseHostLaunches', +]) { + assert.match(iosRunner, new RegExp(`-only-testing:.*${testName}`)); +} +const deviceServerPath = path.join(appRoot, 'scripts/dev-ios-device.mjs'); +const validateDeviceOrigin = (origin) => + spawnSync(process.execPath, [deviceServerPath, '--check-origin'], { + encoding: 'utf8', + env: { ...process.env, LYNX_REMOTE_ORIGIN: origin }, + }).status; +assert.equal(validateDeviceOrigin('http://192.168.1.20:3000'), 0); +for (const origin of [ + 'http://localhost:3000', + 'http://dev.localhost:3000', + 'http://127.42.0.1:3000', + 'http://0.0.0.0:3000', + 'http://[::1]:3000', + 'http://[::]:3000', + 'http://[::ffff:7f00:1]:3000', + 'https://192.168.1.20:3000', + 'http://192.168.1.20:3000/prefix', + 'http://192.168.1.20:3000?query=yes', + 'http://user:pass@192.168.1.20:3000', +]) { + assert.notEqual(validateDeviceOrigin(origin), 0, origin); +} +process.stdout.write( + 'Standalone official Lynx iOS project policy validated.\n', +); diff --git a/apps/lynx-module-federation-demo/test/ios/run.mjs b/apps/lynx-module-federation-demo/test/ios/run.mjs new file mode 100644 index 00000000000..069205eccbe --- /dev/null +++ b/apps/lynx-module-federation-demo/test/ios/run.mjs @@ -0,0 +1,249 @@ +import assert from 'node:assert/strict'; +import { spawn, spawnSync } from 'node:child_process'; +import { + mkdir, + readFile, + readdir, + rm, + stat, + writeFile, +} from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { createArtifactServer } from '../support/artifact-server.mjs'; + +const appRoot = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '../..', +); +const distRoot = path.join(appRoot, 'dist'); +const iosRoot = path.join(appRoot, 'ios'); +const artifactsRoot = path.join(iosRoot, 'build'); +const requestLogPath = path.join(artifactsRoot, 'requests.json'); +const screenshotPath = path.join(artifactsRoot, 'orbit-control.png'); +const simulatorLogPath = path.join(artifactsRoot, 'simulator.log'); +const resultBundlePath = path.join( + artifactsRoot, + 'OrbitControl-Release.xcresult', +); + +await mkdir(artifactsRoot, { recursive: true }); +await rm(resultBundlePath, { force: true, recursive: true }); +await stat(path.join(distRoot, 'host-native/main.lynx.bundle')); +await stat(path.join(distRoot, 'catalog-native/main.lynx.bundle')); +await stat(path.join(distRoot, 'remote-native/mf-manifest.json')); +const hostLazyFiles = ( + await readdir(path.join(distRoot, 'host-native/lazy-bundle'), { + recursive: true, + }) +) + .filter((file) => file.endsWith('.bundle')) + .map((file) => file.split(path.sep).join('/')); +assert.equal(hostLazyFiles.length, 2); +const catalogLazyFiles = ( + await readdir(path.join(distRoot, 'catalog-native/lazy-bundle')) +) + .filter((file) => file.endsWith('.bundle')) + .map((file) => `/catalog-native/lazy-bundle/${file}`); +assert.equal(catalogLazyFiles.length, 1); +assert.ok(catalogLazyFiles[0].includes('activity-metadata')); +const manifest = JSON.parse( + await readFile(path.join(distRoot, 'remote-native/mf-manifest.json'), 'utf8'), +); +const serverURL = new URL( + manifest.metaData.publicPath === 'auto' + ? 'http://127.0.0.1:3000/remote-native/' + : manifest.metaData.publicPath, +); +assert.ok( + serverURL.hostname === 'localhost' || serverURL.hostname === '127.0.0.1', + `iOS E2E only serves local artifacts, received ${serverURL.origin}`, +); +const serverPort = Number(serverURL.port || 80); + +const run = (command, args, options = {}) => + new Promise((resolve, reject) => { + const child = spawn(command, args, { + cwd: iosRoot, + env: process.env, + stdio: 'inherit', + ...options, + }); + child.once('error', reject); + child.once('exit', (code, signal) => { + if (code === 0) resolve(); + else reject(new Error(`${command} exited with ${code ?? signal}`)); + }); + }); + +const simulatorList = spawnSync( + 'xcrun', + ['simctl', 'list', 'devices', 'available', '-j'], + { encoding: 'utf8' }, +); +assert.equal(simulatorList.status, 0, simulatorList.stderr); +const devices = Object.entries(JSON.parse(simulatorList.stdout).devices) + .reverse() + .flatMap(([runtime, runtimeDevices]) => + runtimeDevices.map((device) => ({ ...device, runtime })), + ) + .filter((device) => device.isAvailable && device.name.startsWith('iPhone')); +assert.ok(devices.length > 0, 'No available iPhone simulator was found.'); +const templateDevice = + devices.find(({ name }) => name.includes('16 Pro')) ?? devices[0]; +assert.ok( + templateDevice.deviceTypeIdentifier, + `Simulator ${templateDevice.name} did not report a device type.`, +); + +const artifactServer = await createArtifactServer({ + port: serverPort, + root: distRoot, +}); +const { requests } = artifactServer; +const requestedPaths = () => + requests.map(({ path: requestPath }) => requestPath); + +let deviceUDID; +try { + const created = spawnSync( + 'xcrun', + [ + 'simctl', + 'create', + `OrbitControl E2E ${process.pid}`, + templateDevice.deviceTypeIdentifier, + templateDevice.runtime, + ], + { encoding: 'utf8' }, + ); + assert.equal(created.status, 0, created.stderr); + deviceUDID = created.stdout.trim(); + assert.ok(deviceUDID, 'simctl create did not return a device identifier.'); + + const boot = spawnSync('xcrun', ['simctl', 'boot', deviceUDID], { + encoding: 'utf8', + }); + assert.ok( + boot.status === 0 || /current state: Booted/.test(boot.stderr), + boot.stderr, + ); + await run('xcrun', ['simctl', 'bootstatus', deviceUDID, '-b']); + await run( + 'xcodebuild', + [ + 'test', + '-workspace', + 'OrbitControl.xcworkspace', + '-scheme', + 'OrbitControl', + '-configuration', + 'Release', + '-destination', + `platform=iOS Simulator,id=${deviceUDID}`, + '-derivedDataPath', + 'build/DerivedData', + '-resultBundlePath', + 'build/OrbitControl-Release.xcresult', + 'COMPILER_INDEX_STORE_ENABLE=NO', + '-showBuildTimingSummary', + '-parallel-testing-enabled', + 'NO', + '-test-iterations', + '2', + '-retry-tests-on-failure', + '-only-testing:OrbitControlTests', + '-only-testing:OrbitControlUITests/OrbitControlUITests/testFederatedImportsRuntimeLoadingAndSingleton', + '-only-testing:OrbitControlUITests/OrbitControlUITests/testStandaloneCatalogRemoteBuildLaunches', + '-only-testing:OrbitControlUITests/OrbitControlUITests/testEmbeddedReleaseHostLaunches', + ], + { + env: { + ...process.env, + CATALOG_BUNDLE_URL: `${serverURL.origin}/catalog-native/main.lynx.bundle`, + LYNX_BUNDLE_URL: `${serverURL.origin}/host-native/main.lynx.bundle`, + }, + }, + ); + + const remoteEntry = manifest.metaData.remoteEntry; + const expected = [ + '/host-native/main.lynx.bundle', + '/catalog-native/main.lynx.bundle', + ...catalogLazyFiles, + ...hostLazyFiles.map((file) => `/host-native/lazy-bundle/${file}`), + '/remote-native/mf-manifest.json', + new URL(`${remoteEntry.path}${remoteEntry.name}`, serverURL).pathname, + ]; + const lazyFiles = ( + await readdir(path.join(distRoot, 'remote-native/lazy-bundle')) + ) + .filter((file) => file.endsWith('.bundle')) + .map((file) => `/remote-native/lazy-bundle/${file}`); + assert.equal(lazyFiles.length, 4); + assert.ok(lazyFiles.some((file) => file.includes('activity-metadata'))); + expected.push(...lazyFiles); + for (const pathname of expected) { + assert.ok( + requestedPaths().includes(pathname), + `iOS app did not request ${pathname}`, + ); + } + process.stdout.write( + `Native iOS app launched Orbit and standalone Catalog, then loaded ${hostLazyFiles.length} host lazy bundles, the manifest, container, and ${lazyFiles.length} remote lazy bundles.\n`, + ); +} finally { + await writeFile(requestLogPath, JSON.stringify(requests, null, 2)); + if (deviceUDID) { + const simulatorLog = spawnSync( + 'xcrun', + [ + 'simctl', + 'spawn', + deviceUDID, + 'log', + 'show', + '--last', + '15m', + '--style', + 'compact', + '--predicate', + 'process == "OrbitControl" OR process == "Orbit Control"', + ], + { + encoding: 'utf8', + maxBuffer: 32 * 1024 * 1024, + }, + ); + const simulatorLogOutput = [simulatorLog.stdout, simulatorLog.stderr] + .filter(Boolean) + .join('\n'); + await writeFile(simulatorLogPath, simulatorLogOutput); + const diagnosticLines = simulatorLogOutput + .split('\n') + .filter((line) => + /bundle|error|exception|federation|lazy|lynx/i.test(line), + ) + .slice(-200); + if (diagnosticLines.length > 0) { + process.stderr.write( + `iOS simulator diagnostics:\n${diagnosticLines.join('\n')}\n`, + ); + } + spawnSync( + 'xcrun', + ['simctl', 'io', deviceUDID, 'screenshot', screenshotPath], + { + encoding: 'utf8', + }, + ); + spawnSync('xcrun', ['simctl', 'shutdown', deviceUDID], { + encoding: 'utf8', + }); + spawnSync('xcrun', ['simctl', 'delete', deviceUDID], { + encoding: 'utf8', + }); + } + await artifactServer.close(); +} diff --git a/apps/lynx-module-federation-demo/test/native-artifacts.mjs b/apps/lynx-module-federation-demo/test/native-artifacts.mjs new file mode 100644 index 00000000000..7268094c52b --- /dev/null +++ b/apps/lynx-module-federation-demo/test/native-artifacts.mjs @@ -0,0 +1,270 @@ +import assert from 'node:assert/strict'; +import { readFile, readdir, stat } from 'node:fs/promises'; +import { createRequire } from 'node:module'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { createArtifactServer } from './support/artifact-server.mjs'; + +const appRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url))); +const requireFromAdapter = createRequire( + path.join(appRoot, '../../packages/lynx/package.json'), +); +const nativeRemoteOrigin = + process.env.LYNX_REMOTE_ORIGIN?.replace(/\/+$/, '') ?? + 'http://127.0.0.1:3000'; +const nativeHostOrigin = process.env.LYNX_HOST_ORIGIN?.replace(/\/+$/, ''); +const nativeHostAssetPrefix = nativeHostOrigin + ? `${nativeHostOrigin}/host-native/` + : '/host-native/'; +const { decode_napi: decodeTemplate } = requireFromAdapter('@lynx-js/tasm'); +const hostBundlePath = path.join(appRoot, 'dist/host-native/main.lynx.bundle'); +const standaloneBundlePath = path.join( + appRoot, + 'dist/catalog-native/main.lynx.bundle', +); +const remoteBundlePath = path.join( + appRoot, + 'dist/remote-native/catalog.native.lynx.bundle', +); +const remoteManifestPath = path.join( + appRoot, + 'dist/remote-native/mf-manifest.json', +); +const remoteStatsPath = path.join(appRoot, 'dist/remote-native/mf-stats.json'); + +const [ + hostBundle, + standaloneBundle, + remoteBundle, + manifestSource, + statsSource, + remoteFiles, + lazyFiles, + catalogLazyFiles, + hostLazyFiles, +] = await Promise.all([ + stat(hostBundlePath), + stat(standaloneBundlePath), + stat(remoteBundlePath), + readFile(remoteManifestPath, 'utf8'), + readFile(remoteStatsPath, 'utf8'), + readdir(path.join(appRoot, 'dist/remote-native')), + readdir(path.join(appRoot, 'dist/remote-native/lazy-bundle')), + readdir(path.join(appRoot, 'dist/catalog-native/lazy-bundle')), + readdir(path.join(appRoot, 'dist/host-native/lazy-bundle'), { + recursive: true, + }), +]); + +assert.ok(hostBundle.isFile() && hostBundle.size > 1_000, hostBundlePath); +assert.ok( + standaloneBundle.isFile() && standaloneBundle.size > 1_000, + standaloneBundlePath, +); +assert.ok(remoteBundle.isFile() && remoteBundle.size > 1_000, remoteBundlePath); +assert.ok(!remoteFiles.includes('bootstrap.lynx.bundle')); +assert.ok(!remoteFiles.includes('main.lynx.bundle')); +assert.equal(catalogLazyFiles.length, 1); +assert.ok(catalogLazyFiles[0].includes('activity-metadata')); +const remoteLazyBundles = lazyFiles.filter((name) => name.endsWith('.bundle')); +assert.equal(remoteLazyBundles.length, 4); +const nestedLazyBundle = remoteLazyBundles.find((name) => + name.includes('activity-metadata'), +); +assert.ok(nestedLazyBundle, 'nested activity lazy bundle is missing'); +const hostLazyBundles = hostLazyFiles + .filter((name) => name.endsWith('.bundle')) + .map((name) => name.split(path.sep).join('/')); +assert.equal(hostLazyBundles.length, 2); +assert.ok(hostLazyBundles.some((name) => name.includes('staticCard.ts.'))); +assert.ok(hostLazyBundles.some((name) => name.includes('federationState.ts.'))); + +const [hostBundleSource, standaloneSource, remoteBundleSource] = + await Promise.all([ + readFile(hostBundlePath), + readFile(standaloneBundlePath), + readFile(remoteBundlePath), + ]); +const hostTemplate = decodeTemplate(hostBundleSource); +const standaloneTemplate = decodeTemplate(standaloneSource); +const remoteTemplate = decodeTemplate(remoteBundleSource); +assert.equal(hostTemplate['engine-version'], '3.9'); +assert.equal(standaloneTemplate['app-type'], 'card'); +assert.equal(standaloneTemplate['engine-version'], '3.9'); +assert.equal(remoteTemplate['app-type'], 'DynamicComponent'); +assert.equal(remoteTemplate['engine-version'], '3.9'); +assert.deepEqual(Object.keys(remoteTemplate['custom-sections']).sort(), [ + 'catalog', + 'catalog__main-thread', +]); +const remoteMainThreadEntry = Buffer.from( + remoteTemplate['custom-sections']['catalog__main-thread'], +); +assert.ok( + remoteMainThreadEntry.includes(Buffer.from('processEvalResultByHost')), + 'native remote has no main-thread chunk installer', +); +assert.ok( + !remoteMainThreadEntry.includes(Buffer.from('bundleSupportLoadScript')), + 'native main-thread container retained the background runtime wrapper', +); +const hostBackgroundSource = hostTemplate['background-thread-script'] + .map(({ content }) => content) + .join('\n'); +assert.ok(hostBackgroundSource.includes('mfAsyncStartup')); +assert.ok(hostBackgroundSource.includes('lynx_aci')); +assert.ok(hostBackgroundSource.includes('fetchBundle')); +assert.ok(hostBackgroundSource.includes(nativeHostAssetPrefix)); +const reactLazyLoaderIndex = hostBackgroundSource.indexOf( + 'react-lynx-lazy-bundle-runtime-plugin', +); +const asyncStartupIndex = hostBackgroundSource.indexOf('mfAsyncStartup'); +assert.ok( + reactLazyLoaderIndex >= 0, + 'ReactLynx lazy loader is not bootstrapped', +); +assert.ok( + reactLazyLoaderIndex < asyncStartupIndex, + 'ReactLynx lazy loader starts after federation async startup', +); + +for (const name of hostLazyBundles) { + const lazyTemplate = decodeTemplate( + await readFile(path.join(appRoot, 'dist/host-native/lazy-bundle', name)), + ); + assert.equal(lazyTemplate['app-type'], 'DynamicComponent', name); + assert.equal( + typeof lazyTemplate['custom-sections']?.background, + 'string', + name, + ); + assert.ok( + !lazyTemplate['custom-sections'].background.includes( + "tt.define('/app-service.js'", + ), + `${name} contains the app-service dispatcher instead of its executable chunk`, + ); +} + +const manifest = JSON.parse(manifestSource); +const stats = JSON.parse(statsSource); +assert.equal(manifest.metaData?.name, 'catalog'); +assert.equal( + manifest.metaData?.publicPath, + `${nativeRemoteOrigin}/remote-native/`, +); +assert.deepEqual(manifest.metaData?.remoteEntry, { + name: 'catalog.native.lynx.bundle', + path: '', + type: 'lynx', +}); + +assert.ok(Array.isArray(manifest.exposes)); +assert.deepEqual(manifest.exposes.map(({ name }) => name).sort(), [ + 'ActivityFeed', + 'Card', + 'Details', +]); +assert.deepEqual( + stats.exposes.map(({ name }) => name).sort(), + manifest.exposes.map(({ name }) => name).sort(), +); +for (const exposed of manifest.exposes) { + assert.equal(exposed.layer, 'react:background', exposed.name); + assert.equal(exposed.path, `./${exposed.name}`); + assert.deepEqual(exposed.assets?.js, { sync: [], async: [] }); + assert.deepEqual(exposed.assets?.css, { sync: [], async: [] }); + const lazyName = lazyFiles.find((name) => + name.startsWith(`catalog__background_${exposed.name}.`), + ); + assert.ok(lazyName, `${exposed.name} lazy bundle is missing`); + const lazyPath = path.join( + appRoot, + 'dist/remote-native/lazy-bundle', + lazyName, + ); + const lazyStat = await stat(lazyPath); + assert.ok(lazyStat.isFile() && lazyStat.size > 1_000, lazyPath); + const lazyTemplate = decodeTemplate(await readFile(lazyPath)); + assert.equal(lazyTemplate['app-type'], 'DynamicComponent', lazyName); + assert.equal( + typeof lazyTemplate['custom-sections']?.background, + 'string', + `${lazyName} has no background section`, + ); + assert.ok( + !lazyTemplate['custom-sections'].background.includes( + "tt.define('/app-service.js'", + ), + `${lazyName} contains the app-service dispatcher instead of its executable chunk`, + ); + assert.ok( + lazyTemplate['custom-sections']?.['main-thread']?.length > 100, + `${lazyName} has no main-thread snapshot section`, + ); + assert.ok( + !JSON.stringify(exposed).includes('__main_thread'), + `${exposed.name} contains a main-thread alias`, + ); +} + +const nestedTemplate = decodeTemplate( + await readFile( + path.join(appRoot, 'dist/remote-native/lazy-bundle', nestedLazyBundle), + ), +); +assert.equal(nestedTemplate['app-type'], 'DynamicComponent'); +const nestedBackgroundSource = + nestedTemplate['custom-sections']?.background ?? ''; +assert.ok( + nestedBackgroundSource.includes('Nested federated module ready'), + `${nestedLazyBundle} does not contain the nested module`, +); + +assert.ok(Array.isArray(manifest.shared)); +assert.deepEqual( + manifest.shared.map(({ name }) => name), + ['orbit-shared-state'], +); +for (const shared of manifest.shared) { + assert.equal(shared.layer, 'react:background', shared.name); + assert.deepEqual(shared.shareScope, ['default:react:background']); + assert.equal(shared.singleton, true, shared.name); + assert.notEqual(shared.eager, true, shared.name); +} +assert.deepEqual( + stats.shared.map(({ name }) => name), + ['orbit-shared-state'], +); +assert.ok( + manifest.exposes.some((exposed) => + exposed.requiredShared?.some( + (shared) => shared.name === 'orbit-shared-state', + ), + ), + 'no expose records its shared-state dependency', +); + +const server = await createArtifactServer({ root: path.join(appRoot, 'dist') }); +try { + await Promise.all([ + server.waitFor('/host-native/main.lynx.bundle'), + server.waitFor('/catalog-native/main.lynx.bundle'), + server.waitFor(`/catalog-native/lazy-bundle/${catalogLazyFiles[0]}`), + server.waitFor('/remote-native/mf-manifest.json'), + server.waitFor('/remote-native/catalog.native.lynx.bundle'), + ...hostLazyBundles.map((name) => + server.waitFor(`/host-native/lazy-bundle/${name}`), + ), + ...lazyFiles + .filter((name) => name.endsWith('.bundle')) + .map((name) => server.waitFor(`/remote-native/lazy-bundle/${name}`)), + ]); +} finally { + await server.close(); +} + +console.log( + 'Native Lynx host, standalone Catalog, and federation artifacts verified.', +); diff --git a/apps/lynx-module-federation-demo/test/native-dev-server.mjs b/apps/lynx-module-federation-demo/test/native-dev-server.mjs new file mode 100644 index 00000000000..2b6209ceb56 --- /dev/null +++ b/apps/lynx-module-federation-demo/test/native-dev-server.mjs @@ -0,0 +1,149 @@ +import assert from 'node:assert/strict'; +import { mkdtemp, readdir, rm } from 'node:fs/promises'; +import { createServer } from 'node:net'; +import { spawn, spawnSync } from 'node:child_process'; +import os from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const appRoot = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '..', +); + +const reservePort = () => + new Promise((resolve, reject) => { + const server = createServer(); + server.once('error', reject); + server.listen(0, '127.0.0.1', () => { + const address = server.address(); + assert.ok(address && typeof address === 'object'); + server.close((error) => (error ? reject(error) : resolve(address.port))); + }); + }); + +const port = await reservePort(); +const origin = `http://127.0.0.1:${port}`; +const outputRoot = await mkdtemp(path.join(os.tmpdir(), 'lynx-e2e-')); +const devEnvironment = { + ...process.env, + LYNX_DEV_HOST: '127.0.0.1', + LYNX_DEV_PORT: String(port), + LYNX_OUTPUT_ROOT: outputRoot, + LYNX_REMOTE_ORIGIN: origin, +}; +const output = []; +let child; + +const fetchReady = async (url, timeout = 30_000) => { + const deadline = Date.now() + timeout; + let lastError; + while (Date.now() < deadline) { + try { + const response = await fetch(url); + if (response.ok) return response; + lastError = new Error(`${response.status} ${response.statusText}`); + } catch (error) { + lastError = error; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + } + throw new Error( + `Native dev asset did not become available at ${url}: ${lastError}\n${output.join('')}`, + ); +}; + +const build = (config, label) => { + const result = spawnSync( + process.execPath, + ['rspack-canary-rspeedy.mjs', 'build', '-c', config], + { cwd: appRoot, encoding: 'utf8', env: devEnvironment }, + ); + assert.equal( + result.status, + 0, + `${label} failed:\n${result.stdout}\n${result.stderr}`, + ); +}; + +try { + build('lynx.remote.native.config.mjs', 'Native remote rebuild'); + build('lynx.catalog.native.config.mjs', 'Native Catalog rebuild'); + child = spawn( + process.execPath, + ['rspack-canary-rspeedy.mjs', 'dev', '-c', 'lynx.config.mjs'], + { + cwd: appRoot, + env: devEnvironment, + stdio: ['ignore', 'pipe', 'pipe'], + }, + ); + child.stdout.on('data', (chunk) => output.push(chunk.toString())); + child.stderr.on('data', (chunk) => output.push(chunk.toString())); + + await fetchReady(`${origin}/main.lynx.bundle`); + await fetchReady(`${origin}/catalog-native/main.lynx.bundle`); + const catalogLazyBundles = await readdir( + path.join(outputRoot, 'catalog-native/lazy-bundle'), + ); + assert.equal(catalogLazyBundles.length, 1); + assert.ok(catalogLazyBundles[0].includes('activity-metadata')); + await fetchReady( + `${origin}/catalog-native/lazy-bundle/${catalogLazyBundles[0]}`, + ); + const hostLazyBundles = ( + await readdir(path.join(outputRoot, 'host-native/lazy-bundle'), { + recursive: true, + }) + ) + .filter((name) => name.endsWith('.bundle')) + .map((name) => name.split(path.sep).join('/')); + assert.ok(hostLazyBundles.length >= 2, JSON.stringify(hostLazyBundles)); + assert.ok(hostLazyBundles.some((name) => name.includes('staticCard.ts.'))); + assert.ok( + hostLazyBundles.some((name) => name.includes('federationState.ts.')), + ); + await Promise.all( + hostLazyBundles.map((name) => + fetchReady(`${origin}/host-native/lazy-bundle/${name}`), + ), + ); + const manifestResponse = await fetchReady( + `${origin}/remote-native/mf-manifest.json`, + ); + const manifest = await manifestResponse.json(); + assert.equal(manifest.metaData.publicPath, `${origin}/remote-native/`); + const remoteEntry = manifest.metaData.remoteEntry; + const remoteBase = + manifest.metaData.publicPath === 'auto' + ? new URL('.', manifestResponse.url) + : new URL(manifest.metaData.publicPath, manifestResponse.url); + await fetchReady( + new URL(`${remoteEntry.path}${remoteEntry.name}`, remoteBase), + ); + + const lazyFiles = await readdir( + path.join(outputRoot, 'remote-native/lazy-bundle'), + ); + const lazyBundles = lazyFiles.filter((name) => name.endsWith('.bundle')); + assert.equal(lazyBundles.length, 4); + assert.ok(lazyBundles.some((name) => name.includes('activity-metadata'))); + await Promise.all( + lazyBundles.map((name) => + fetchReady(`${origin}/remote-native/lazy-bundle/${name}`), + ), + ); + process.stdout.write( + `Native Rspeedy dev server served host, ${hostLazyBundles.length} host lazy bundles, standalone Catalog with its nested bundle, manifest, container, and ${lazyBundles.length} remote lazy bundles.\n`, + ); +} finally { + if (child) { + child.kill('SIGTERM'); + await Promise.race([ + new Promise((resolve) => child.once('exit', resolve)), + new Promise((resolve) => setTimeout(resolve, 5_000)), + ]); + if (child.exitCode === null) child.kill('SIGKILL'); + } + await rm(outputRoot, { force: true, recursive: true }); +} diff --git a/apps/lynx-module-federation-demo/test/real-web/README.md b/apps/lynx-module-federation-demo/test/real-web/README.md new file mode 100644 index 00000000000..74b9a0d6f6f --- /dev/null +++ b/apps/lynx-module-federation-demo/test/real-web/README.md @@ -0,0 +1,18 @@ +# Real Lynx Web E2E + +`run.mjs` mounts the official Rspeedy `main.web.bundle` in the public +`@lynx-js/web-core` `` client at a mobile viewport. It serves the +federation manifest and remote Lynx bundle over HTTP, exercises both import +styles and shared singleton state, and terminates its browser and ephemeral +server on success or failure. + +Expected artifacts: + +- `dist/host-web/main.web.bundle` +- `dist/remote-web/mf-manifest.json` +- `dist/remote-web/catalog.web.lynx.bundle` + +Override them with `LYNX_HOST_WEB_BUNDLE`, `LYNX_REMOTE_MANIFEST`, and +`LYNX_REMOTE_WEB_BUNDLE`. A failed run writes +`test/real-web/artifacts/failure.png` (override with +`LYNX_WEB_E2E_SCREENSHOT`). diff --git a/apps/lynx-module-federation-demo/test/real-web/index.html b/apps/lynx-module-federation-demo/test/real-web/index.html new file mode 100644 index 00000000000..eca3dc2a438 --- /dev/null +++ b/apps/lynx-module-federation-demo/test/real-web/index.html @@ -0,0 +1,45 @@ + + + + + + + Orbit Control · Lynx Web E2E + + + + + + + + + diff --git a/apps/lynx-module-federation-demo/test/real-web/run.mjs b/apps/lynx-module-federation-demo/test/real-web/run.mjs new file mode 100644 index 00000000000..2efebb343ce --- /dev/null +++ b/apps/lynx-module-federation-demo/test/real-web/run.mjs @@ -0,0 +1,487 @@ +import assert from 'node:assert/strict'; +import { access, mkdir, readdir } from 'node:fs/promises'; +import { createRequire } from 'node:module'; +import path from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +import { createArtifactServer } from '../support/artifact-server.mjs'; + +const testRoot = path.dirname(fileURLToPath(import.meta.url)); +const appRoot = path.resolve(testRoot, '../..'); +const repoRoot = path.resolve(appRoot, '../..'); +const requireFromApp = createRequire(path.join(appRoot, 'package.json')); +const requireFromRepo = createRequire(path.join(repoRoot, 'package.json')); + +const resolveFromApp = (specifier) => + path.dirname(requireFromApp.resolve(`${specifier}/package.json`)); +const webCoreRoot = resolveFromApp('@lynx-js/web-core'); +const requireFromWebCore = createRequire( + path.join(webCoreRoot, 'package.json'), +); +const webElementsRoot = path.dirname( + requireFromWebCore.resolve('@lynx-js/web-elements/package.json'), +); + +const fromAppRoot = (value, fallback) => + path.resolve(appRoot, value ?? fallback); +const hostBundlePath = fromAppRoot( + process.env.LYNX_HOST_WEB_BUNDLE, + 'dist/host-web/main.web.bundle', +); +const standaloneBundlePath = fromAppRoot( + process.env.LYNX_CATALOG_WEB_BUNDLE, + 'dist/catalog-web/main.web.bundle', +); +const remoteManifestPath = fromAppRoot( + process.env.LYNX_REMOTE_MANIFEST, + 'dist/remote-web/mf-manifest.json', +); +const remoteBundlePath = fromAppRoot( + process.env.LYNX_REMOTE_WEB_BUNDLE, + 'dist/remote-web/catalog.web.lynx.bundle', +); +const hostOutputRoot = path.dirname(hostBundlePath); +const remoteOutputRoot = path.dirname(remoteManifestPath); +const standaloneOutputRoot = path.dirname(standaloneBundlePath); +const screenshotPath = fromAppRoot( + process.env.LYNX_WEB_E2E_SCREENSHOT, + 'test/real-web/artifacts/failure.png', +); +const readinessTimeout = Number(process.env.LYNX_WEB_E2E_TIMEOUT ?? 60_000); + +const requiredArtifacts = [ + ['Rspeedy host web bundle', hostBundlePath], + ['standalone Catalog web bundle', standaloneBundlePath], + ['federation manifest', remoteManifestPath], + ['federated Lynx web bundle', remoteBundlePath], +]; +for (const [name, file] of requiredArtifacts) { + await assert.doesNotReject( + access(file), + `${name} is missing at ${file}; build the official Rspeedy demo first`, + ); +} + +const artifactServer = await createArtifactServer({ + root: testRoot, + routes: { + '/dist/catalog-web/': standaloneOutputRoot, + '/dist/host-web/': hostOutputRoot, + '/dist/remote-web/': remoteOutputRoot, + '/node_modules/@lynx-js/web-core/': webCoreRoot, + '/node_modules/@lynx-js/web-elements/': webElementsRoot, + '/remote-web/': remoteOutputRoot, + '/test/real-web/': testRoot, + }, +}); +const { origin: baseUrl, requests } = artifactServer; +const requestedPaths = () => + requests.map(({ path: requestPath }) => requestPath); + +const playwrightEntry = requireFromRepo.resolve('@playwright/test'); +const playwrightModule = await import(pathToFileURL(playwrightEntry)); +const { chromium } = playwrightModule.default ?? playwrightModule; +let browser; +let failurePage; + +const close = async () => { + await browser?.close(); + await artifactServer.close(); +}; + +const onSignal = () => { + close().finally(() => process.exit(130)); +}; +process.once('SIGINT', onSignal); +process.once('SIGTERM', onSignal); + +const poll = async (read, accept, label, timeout = readinessTimeout) => { + const deadline = Date.now() + timeout; + let value; + let lastError; + while (Date.now() < deadline) { + try { + value = await read(); + if (accept(value)) return value; + } catch (error) { + lastError = error; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + } + assert.fail( + `${label} did not become ready; last value: ${JSON.stringify(value)}${ + lastError ? `; last error: ${lastError.message}` : '' + }`, + ); +}; + +const text = (locator) => locator.textContent({ timeout: 1_000 }); +const numberFrom = async (locator) => { + const value = await text(locator); + const match = value?.match(/-?\d+/); + assert.ok(match, `Expected a numeric value in ${JSON.stringify(value)}`); + return Number(match[0]); +}; +const readCounts = (locators) => Promise.all(locators.map(numberFrom)); +const allEqual = (values) => values.every((value) => value === values[0]); +const tap = async (locator) => { + await locator.scrollIntoViewIfNeeded(); + await locator.tap(); +}; + +const pageErrors = []; +const consoleErrors = []; +const describePageError = (error) => { + const details = { + name: error?.name, + message: error?.message, + stack: error?.stack, + cause: error?.cause, + }; + return JSON.stringify(details, (_key, value) => + value instanceof Error + ? { name: value.name, message: value.message, stack: value.stack } + : value, + ); +}; +const observePage = (candidate, label) => { + candidate.on('pageerror', (error) => + pageErrors.push(`${label}: ${describePageError(error)}`), + ); + candidate.on('console', (message) => { + if (message.type() === 'error') { + consoleErrors.push(`${label}: ${message.text()}`); + } + }); +}; + +try { + browser = await chromium.launch({ + headless: true, + args: ['--disable-gpu', '--no-sandbox'], + }); + const context = await browser.newContext({ + deviceScaleFactor: 1, + hasTouch: true, + isMobile: true, + viewport: { width: 430, height: 932 }, + }); + const hostPage = await context.newPage(); + failurePage = hostPage; + observePage(hostPage, 'Orbit'); + + await hostPage.goto(`${baseUrl}/`, { + timeout: 60_000, + waitUntil: 'domcontentloaded', + }); + + const activePage = hostPage.locator( + '#orbit-lynx-view [part="page"]:not([l-disposed])', + ); + await poll( + () => activePage.count(), + (count) => count === 1, + 'single active Lynx page', + ); + const app = activePage.getByTestId('orbit-control-app'); + await app.waitFor({ state: 'visible', timeout: readinessTimeout }); + assert.equal(await app.count(), 1, 'the app must render exactly once'); + + const initialRemoteLazyBundleRequests = requestedPaths().filter( + (value) => + value.includes('/remote-web/lazy-bundle/') && value.endsWith('.bundle'), + ); + assert.deepEqual( + initialRemoteLazyBundleRequests, + [], + `remote UI loaded before Lynx initialized its DynamicComponent runtime: ${JSON.stringify(initialRemoteLazyBundleRequests)}`, + ); + + await tap(activePage.getByTestId('load-remotes')); + const ready = /ready|loaded|passed|success/i; + for (const testId of ['import-status', 'runtime-status']) { + const locator = activePage.getByTestId(testId); + const status = await poll( + () => text(locator), + (value) => ready.test(value ?? '') || /error/i.test(value ?? ''), + `${testId} evidence`, + ); + if (/error/i.test(status ?? '')) { + assert.fail( + `${testId} failed: ${await text(activePage.getByTestId('load-error'))}`, + ); + } + } + + const remoteCard = activePage.getByTestId('remote-card'); + await remoteCard.waitFor({ state: 'visible', timeout: readinessTimeout }); + assert.equal(await remoteCard.count(), 1, 'remote Card must render once'); + assert.equal( + await remoteCard.evaluate( + (element) => getComputedStyle(element).backgroundColor, + ), + 'rgb(239, 252, 246)', + 'remote CSS must be applied through the external Lynx bundle', + ); + const remoteDetails = activePage.getByTestId('remote-details'); + await remoteDetails.waitFor({ state: 'visible', timeout: readinessTimeout }); + assert.equal( + await remoteDetails.evaluate( + (element) => getComputedStyle(element).backgroundColor, + ), + 'rgb(23, 33, 43)', + 'Details must retain its dark remote style', + ); + await poll( + () => text(activePage.getByTestId('activity-metadata')), + (value) => value?.trim() === 'Nested federated module ready', + 'nested federated module render', + ); + assert.equal( + await app.evaluate((element) => getComputedStyle(element).backgroundColor), + 'rgb(244, 247, 248)', + 'remote CSS must not overwrite the host surface', + ); + + const singletonStatus = activePage.getByTestId('singleton-status'); + await poll( + () => text(singletonStatus), + (value) => /shared singleton verified/i.test(value ?? ''), + 'singleton identity proof', + ); + + const counts = [ + activePage.getByTestId('shared-host-count'), + activePage.getByTestId('shared-card-count'), + activePage.getByTestId('shared-details-count'), + activePage.getByTestId('shared-activity-count'), + ]; + const baseline = await poll( + () => readCounts(counts), + allEqual, + 'singleton counts', + ); + + await tap(activePage.getByTestId('increment-shared')); + const incremented = await poll( + () => readCounts(counts), + (values) => allEqual(values) && values[0] > baseline[0], + 'shared singleton increment', + ); + assert.ok(incremented[0] > baseline[0]); + await poll( + () => text(activePage.getByTestId('shared-last-source')), + (value) => value?.trim() === 'catalog/Card', + 'remote singleton mutation source', + ); + + await tap(activePage.getByTestId('nav-activity')); + await tap(activePage.getByTestId('reset-feed')); + await tap(activePage.getByTestId('nav-overview')); + await poll( + () => readCounts(counts), + (values) => allEqual(values) && values[0] === 0, + 'shared singleton reset', + ); + + const modulesNav = activePage.getByTestId('nav-modules'); + await tap(modulesNav); + await poll( + async () => ({ + active: await modulesNav.getAttribute('data-active'), + current: await modulesNav.getAttribute('aria-current'), + selected: await modulesNav.getAttribute('aria-selected'), + }), + ({ active, current, selected }) => + active === 'true' || current === 'page' || selected === 'true', + 'modules navigation state', + ); + + await poll( + () => Promise.resolve(requestedPaths()), + (values) => + values.includes('/dist/remote-web/mf-manifest.json') || + values.includes('/remote-web/mf-manifest.json'), + 'manifest HTTP request', + ); + assert.ok( + requestedPaths().some((value) => + value.endsWith(`/${path.basename(remoteBundlePath)}`), + ), + `Remote bundle was not requested: ${JSON.stringify(requests)}`, + ); + const lazyBundleRequests = requestedPaths().filter( + (value) => + value.includes('/remote-web/lazy-bundle/') && value.endsWith('.bundle'), + ); + assert.equal( + lazyBundleRequests.length, + 4, + `each lazy exposure and its nested chunk should be fetched once: ${JSON.stringify(lazyBundleRequests)}`, + ); + assert.equal( + new Set(lazyBundleRequests).size, + 4, + `split mode must fetch each exposure and nested chunk once: ${JSON.stringify(lazyBundleRequests)}`, + ); + assert.ok( + lazyBundleRequests.some((value) => value.includes('activity-metadata')), + `nested activity bundle was not requested: ${JSON.stringify(lazyBundleRequests)}`, + ); + const hostLazyBundleRequests = requestedPaths().filter( + (value) => + value.includes('/dist/host-web/lazy-bundle/') && + value.endsWith('.bundle'), + ); + for (const moduleName of ['federationState', 'staticCard']) { + const moduleRequests = hostLazyBundleRequests.filter((value) => + value.includes(moduleName), + ); + assert.equal( + moduleRequests.length, + 1, + `${moduleName} lazy bundle must be requested exactly once: ${JSON.stringify(hostLazyBundleRequests)}`, + ); + } + + const hostLynxErrors = await hostPage.evaluate( + () => globalThis.__LYNX_WEB_E2E__?.errors ?? [], + ); + await hostPage.close(); + const standaloneRequestStart = requests.length; + const catalogBrowserPage = await context.newPage(); + failurePage = catalogBrowserPage; + observePage(catalogBrowserPage, 'Catalog'); + await catalogBrowserPage.goto( + `${baseUrl}/?bundle=${encodeURIComponent('/dist/catalog-web/main.web.bundle')}`, + { timeout: 60_000, waitUntil: 'domcontentloaded' }, + ); + + const catalogPage = catalogBrowserPage.locator( + '#orbit-lynx-view [part="page"]:not([l-disposed])', + ); + await poll( + () => catalogPage.count(), + (count) => count === 1, + 'single active standalone Catalog page', + ); + const catalog = catalogPage.getByTestId('catalog-standalone-app'); + await catalog.waitFor({ state: 'visible', timeout: readinessTimeout }); + await catalogPage + .getByTestId('catalog-standalone-ready') + .waitFor({ state: 'visible', timeout: readinessTimeout }); + for (const testId of [ + 'remote-card', + 'remote-details', + 'remote-activity-feed', + ]) { + await catalogPage + .getByTestId(testId) + .waitFor({ state: 'visible', timeout: readinessTimeout }); + } + await poll( + () => text(catalogPage.getByTestId('activity-metadata')), + (value) => value?.trim() === 'Nested federated module ready', + 'standalone nested module render', + ); + + const catalogCounts = [ + catalogPage.getByTestId('catalog-local-count'), + catalogPage.getByTestId('shared-card-count'), + catalogPage.getByTestId('shared-details-count'), + catalogPage.getByTestId('shared-activity-count'), + ]; + const catalogBaseline = await poll( + () => readCounts(catalogCounts), + allEqual, + 'standalone Catalog shared counts', + ); + await tap(catalogPage.getByTestId('increment-shared')); + await poll( + () => readCounts(catalogCounts), + (values) => allEqual(values) && values[0] > catalogBaseline[0], + 'standalone Catalog shared increment', + ); + await poll( + () => text(catalogPage.getByTestId('shared-last-source')), + (value) => value?.trim() === 'catalog/Card', + 'standalone Catalog mutation source', + ); + + const standaloneRequests = requestedPaths().slice(standaloneRequestStart); + assert.ok( + standaloneRequests.includes('/dist/catalog-web/main.web.bundle'), + `standalone Catalog entry was not requested: ${JSON.stringify(standaloneRequests)}`, + ); + assert.ok( + !standaloneRequests.some( + (value) => + value.includes('mf-manifest.json') || + value.includes('/remote-web/lazy-bundle/') || + value.endsWith(`/${path.basename(remoteBundlePath)}`), + ), + `standalone direct imports unexpectedly used federation transport: ${JSON.stringify(standaloneRequests)}`, + ); + + const catalogLynxErrors = await catalogBrowserPage.evaluate( + () => globalThis.__LYNX_WEB_E2E__?.errors ?? [], + ); + const lynxErrors = [...hostLynxErrors, ...catalogLynxErrors]; + assert.deepEqual( + lynxErrors, + [], + `lynx-view errors: ${lynxErrors.join('\n')}`, + ); + assert.deepEqual(pageErrors, [], `page errors: ${pageErrors.join('\n')}`); + assert.deepEqual( + consoleErrors, + [], + `console errors: ${consoleErrors.join('\n')}`, + ); + + process.stdout.write( + `Real Lynx Web E2E passed for Orbit and standalone Catalog at ${baseUrl} (${requests.length} requests)\n`, + ); +} catch (error) { + const frameDiagnostics = failurePage + ? await Promise.all( + failurePage.frames().map(async (frame) => { + try { + return await frame.evaluate(() => ({ + processEvalResultHosts: Object.keys( + globalThis.processEvalResultByHost ?? {}, + ), + url: location.href, + })); + } catch (frameError) { + return { + error: + frameError instanceof Error + ? frameError.message + : String(frameError), + url: frame.url(), + }; + } + }), + ) + : []; + if (failurePage) { + await mkdir(path.dirname(screenshotPath), { recursive: true }); + await failurePage.screenshot({ path: screenshotPath, fullPage: true }); + } + throw new Error( + [ + error instanceof Error ? error.message : String(error), + `Failure screenshot: ${screenshotPath}`, + `Page errors: ${JSON.stringify(pageErrors)}`, + `Console errors: ${JSON.stringify(consoleErrors)}`, + `Frame diagnostics: ${JSON.stringify(frameDiagnostics)}`, + `Requests: ${JSON.stringify(requests)}`, + ].join('\n'), + { cause: error }, + ); +} finally { + process.removeListener('SIGINT', onSignal); + process.removeListener('SIGTERM', onSignal); + await close(); +} diff --git a/apps/lynx-module-federation-demo/test/real-web/shell.js b/apps/lynx-module-federation-demo/test/real-web/shell.js new file mode 100644 index 00000000000..1dcefcf17f4 --- /dev/null +++ b/apps/lynx-module-federation-demo/test/real-web/shell.js @@ -0,0 +1,29 @@ +import '@lynx-js/web-core/client'; + +const state = { + errors: [], + timings: [], +}; + +globalThis.__LYNX_WEB_E2E__ = state; + +await customElements.whenDefined('lynx-view'); + +const lynxView = document.createElement('lynx-view'); +lynxView.id = 'orbit-lynx-view'; +lynxView.initData = { e2e: true, surface: 'lynx-web' }; +lynxView.globalProps = { e2e: true, platform: 'web' }; +lynxView.setAttribute('height', '100%'); +lynxView.setAttribute('width', '100%'); +lynxView.addEventListener('error', (event) => { + state.errors.push(String(event.detail?.message ?? event.detail ?? event)); +}); +lynxView.addEventListener('timing', (event) => { + state.timings.push(event.detail); +}); + +document.body.append(lynxView); +const bundleUrl = + new URL(location.href).searchParams.get('bundle') ?? + '/dist/host-web/main.web.bundle'; +lynxView.setAttribute('url', bundleUrl); diff --git a/apps/lynx-module-federation-demo/test/support/artifact-server.mjs b/apps/lynx-module-federation-demo/test/support/artifact-server.mjs new file mode 100644 index 00000000000..7eeb88d5896 --- /dev/null +++ b/apps/lynx-module-federation-demo/test/support/artifact-server.mjs @@ -0,0 +1,182 @@ +import { readFile, readdir, realpath, stat } from 'node:fs/promises'; +import { createServer } from 'node:http'; +import path from 'node:path'; + +const contentTypes = new Map([ + ['.bundle', 'application/octet-stream'], + ['.css', 'text/css; charset=utf-8'], + ['.html', 'text/html; charset=utf-8'], + ['.js', 'text/javascript; charset=utf-8'], + ['.json', 'application/json; charset=utf-8'], + ['.map', 'application/json; charset=utf-8'], + ['.wasm', 'application/wasm'], +]); + +const isInside = (root, file) => { + const relative = path.relative(root, file); + return ( + relative === '' || + (relative !== '..' && + !relative.startsWith(`..${path.sep}`) && + !path.isAbsolute(relative)) + ); +}; + +const indexFiles = async (root) => { + const resolvedRoot = await realpath(path.resolve(root)); + const files = new Map(); + const visited = new Set(); + + const visit = async (directory) => { + const resolvedDirectory = await realpath(directory); + if ( + visited.has(resolvedDirectory) || + !isInside(resolvedRoot, resolvedDirectory) + ) { + return; + } + visited.add(resolvedDirectory); + + for (const entry of await readdir(resolvedDirectory, { + withFileTypes: true, + })) { + const resolved = await realpath(path.join(resolvedDirectory, entry.name)); + if (!isInside(resolvedRoot, resolved)) continue; + + const fileStat = await stat(resolved); + if (fileStat.isDirectory()) { + await visit(resolved); + } else if (fileStat.isFile()) { + const relative = path.relative(resolvedRoot, resolved); + files.set(`/${relative.split(path.sep).join('/')}`, resolved); + } + } + }; + + await visit(resolvedRoot); + return files; +}; + +const delay = (duration) => + new Promise((resolve) => setTimeout(resolve, duration)); + +export const createArtifactServer = async ({ port = 0, root, routes = {} }) => { + const artifactFiles = await indexFiles(root); + const routeEntries = await Promise.all( + Object.entries(routes).map(async ([route, target]) => [ + route, + typeof target === 'string' + ? { files: await indexFiles(target) } + : { handler: target }, + ]), + ); + routeEntries.sort(([left], [right]) => right.length - left.length); + const requests = []; + + const server = createServer(async (request, response) => { + let pathname = '/'; + const trace = { + method: request.method ?? 'GET', + path: pathname, + status: undefined, + }; + requests.push(trace); + response.once('finish', () => { + trace.status = response.statusCode; + }); + + try { + const rawPath = (request.url ?? '/').split(/[?#]/, 1)[0]; + pathname = decodeURIComponent(rawPath); + trace.path = pathname; + if (pathname.split(/[\\/]/).includes('..')) { + response.writeHead(403).end('Forbidden'); + return; + } + + for (const [route, target] of routeEntries) { + if (target.handler && pathname === route) { + await target.handler(request, response); + return; + } + if (!target.files) continue; + + const matchesPrefix = route.endsWith('/') && pathname.startsWith(route); + if (matchesPrefix) { + const relativePath = `/${pathname.slice(route.length)}`; + await serveFile(request, response, target.files.get(relativePath)); + return; + } + } + + await serveFile( + request, + response, + artifactFiles.get(pathname === '/' ? '/index.html' : pathname), + ); + } catch { + if (!response.headersSent) response.writeHead(500); + response.end('Internal server error'); + } + }); + + await new Promise((resolve, reject) => { + server.once('error', reject); + server.listen(port, '127.0.0.1', resolve); + }); + const address = server.address(); + if (!address || typeof address === 'string') { + throw new Error('Artifact server did not expose a TCP address.'); + } + const origin = `http://127.0.0.1:${address.port}`; + let closePromise; + + return { + origin, + requests, + async waitFor(url, timeout = 30_000) { + const target = new URL(url, origin); + const deadline = Date.now() + timeout; + let lastError; + while (Date.now() < deadline) { + try { + const response = await fetch(target); + if (response.ok) return response; + lastError = new Error(`${response.status} ${response.statusText}`); + await response.body?.cancel(); + } catch (error) { + lastError = error; + } + await delay(100); + } + throw new Error( + `Artifact did not become available at ${target}: ${lastError}`, + ); + }, + close() { + closePromise ??= new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + }); + return closePromise; + }, + }; +}; + +const serveFile = async (request, response, file) => { + if (!file) { + response.writeHead(404).end('Not found'); + return; + } + + const body = await readFile(file); + response.writeHead(200, { + 'access-control-allow-origin': '*', + 'cache-control': 'no-store', + 'content-length': body.byteLength, + 'content-type': + contentTypes.get(path.extname(file)) ?? 'application/octet-stream', + 'cross-origin-embedder-policy': 'require-corp', + 'cross-origin-opener-policy': 'same-origin', + }); + response.end(request.method === 'HEAD' ? undefined : body); +}; diff --git a/apps/lynx-module-federation-demo/test/support/artifact-server.test.mjs b/apps/lynx-module-federation-demo/test/support/artifact-server.test.mjs new file mode 100644 index 00000000000..2122a372245 --- /dev/null +++ b/apps/lynx-module-federation-demo/test/support/artifact-server.test.mjs @@ -0,0 +1,65 @@ +import assert from 'node:assert/strict'; +import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { createArtifactServer } from './artifact-server.mjs'; + +test('serves rooted artifacts, dynamic routes, and request traces', async () => { + const parent = await mkdtemp(path.join(os.tmpdir(), 'lynx-artifacts-')); + const root = path.join(parent, 'public'); + await mkdir(root); + await writeFile(path.join(root, 'artifact.json'), '{"ready":true}'); + await writeFile(path.join(parent, 'secret.txt'), 'secret'); + await symlink(path.join(parent, 'secret.txt'), path.join(root, 'leak.txt')); + + const server = await createArtifactServer({ + root, + routes: { + '/failure': () => { + throw new Error('private stack marker'); + }, + '/health': (_request, response) => { + response.writeHead(200, { 'content-type': 'text/plain' }); + response.end('ready'); + }, + }, + }); + + try { + const artifact = await server.waitFor('/artifact.json', 1_000); + assert.deepEqual(await artifact.json(), { ready: true }); + assert.equal( + await (await fetch(`${server.origin}/health`)).text(), + 'ready', + ); + const failure = await fetch(`${server.origin}/failure`); + assert.equal(failure.status, 500); + assert.equal(await failure.text(), 'Internal server error'); + + const traversal = await fetch(`${server.origin}/%2e%2e%2fsecret.txt`); + assert.equal(traversal.status, 403); + const symlinkEscape = await fetch(`${server.origin}/leak.txt`); + assert.equal(symlinkEscape.status, 404); + assert.deepEqual( + server.requests.map(({ path: requestPath, status }) => ({ + path: requestPath, + status, + })), + [ + { path: '/artifact.json', status: 200 }, + { path: '/health', status: 200 }, + { path: '/failure', status: 500 }, + { path: '/../secret.txt', status: 403 }, + { path: '/leak.txt', status: 404 }, + ], + ); + } finally { + await server.close(); + await server.close(); + await rm(parent, { force: true, recursive: true }); + } + + await assert.rejects(fetch(`${server.origin}/artifact.json`)); +}); diff --git a/package.json b/package.json index 31ffc62b07a..62ccd2df924 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "verify:turbo": "node tools/scripts/verify-turbo-conventions.mjs", "e2e:modern": "pnpm exec turbo run test --filter=@module-federation/modern-js --filter=@module-federation/modern-js-v3 --concurrency=20", "e2e:runtime": "pnpm exec turbo run test:e2e --filter=runtime-host", + "e2e:lynx": "pnpm exec turbo run test --filter=lynx-module-federation-demo", "e2e:manifest:dev": "pnpm exec turbo run test:e2e --filter=3008-webpack-host", "e2e:manifest:prod": "pnpm exec turbo run test:e2e:production --filter=3008-webpack-host", "e2e:node": "node tools/scripts/run-node-e2e.mjs", @@ -142,6 +143,12 @@ }, "overrides": { "@changesets/assemble-release-plan": "workspace:*", + "@lynx-js/lynx-bundle-rslib-config": "https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/lynx-bundle-rslib-config@e22ca09", + "@lynx-js/react": "https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09", + "@lynx-js/react-rsbuild-plugin": "https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react-rsbuild-plugin@e22ca09", + "@lynx-js/rspeedy": "https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/rspeedy@e22ca09", + "@lynx-js/template-webpack-plugin": "https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09", + "@lynx-js/web-core": "https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-core@e22ca09", "ajv": "8.18.0", "eslint>ajv": "6.14.0", "@eslint/eslintrc>ajv": "6.14.0", diff --git a/packages/lynx/LICENSE b/packages/lynx/LICENSE new file mode 100644 index 00000000000..b0cebd8b67b --- /dev/null +++ b/packages/lynx/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Module Federation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/lynx/README.md b/packages/lynx/README.md new file mode 100644 index 00000000000..f2d865ec295 --- /dev/null +++ b/packages/lynx/README.md @@ -0,0 +1,231 @@ +# Module Federation for Lynx + +`@module-federation/lynx` adapts Rspack Module Federation to official Rspeedy +applications. It preserves normal federated imports, adds a Lynx transport for +manifest-addressed `.lynx.bundle` files, and isolates shared modules by Lynx +JavaScript realm. + +The package requires a layers-capable Rspack build. The version verified by this +repository is `2.1.5-canary-54a0d8f3-20260715194831`. The bundle transport uses +`lynx.loadScript`, available in Lynx SDK 3.7 and later; `remoteBundle.engineVersion` +therefore defaults to `3.7`. + +Split federation also requires `@lynx-js/web-core >= 0.22.3` and +`@lynx-js/template-webpack-plugin >= 0.13.1`. Those releases provide +bundle-relative Web execution URLs, valid empty main-thread chunks, and +assetless remote-chunk filtering used by this adapter. ReactLynx hosts require +`@lynx-js/react >= 0.123.1`; the adapter registers that release's public +lazy-bundle loader before federation async startup so non-eager shared chunks +use FetchBundle in native and Web builds. ReactLynx detection and bootstrap are +automatic; applications should not register the runtime plugin themselves, and +non-React Lynx builds do not receive it. + +## Host + +Register the adapter after the Lynx DSL plugin so it can use the official +`BACKGROUND` and `MAIN_THREAD` layers: + +```ts +import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin'; +import { defineConfig } from '@lynx-js/rspeedy'; +import { pluginLynxModuleFederation } from '@module-federation/lynx'; + +export default defineConfig({ + plugins: [ + pluginReactLynx(), + pluginLynxModuleFederation( + { + name: 'lynx_host', + remotes: { + catalog: 'catalog@https://example.test/mf-manifest.json', + }, + shared: { + 'app-state': { singleton: true, realm: 'background' }, + }, + }, + { + environment: 'lynx', + }, + ), + ], +}); +``` + +Application code keeps the usual syntax: + +```ts +const { default: Card } = await import('catalog/Card'); +``` + +The same manifest works through the runtime API: + +```ts +import { createInstance } from '@module-federation/runtime'; +import lynxRuntimePlugin from '@module-federation/lynx/runtimePlugin'; + +const federation = createInstance({ + name: 'lynx_runtime_host', + remotes: [ + { + name: 'catalog', + entry: 'https://example.test/mf-manifest.json', + }, + ], + plugins: [lynxRuntimePlugin()], +}); + +const Card = await federation.loadRemote('catalog/Card'); +``` + +Use the Module Federation manifest URL, not the generated JavaScript container +URL. The adapter rewrites `metaData.remoteEntry` to the public +`.lynx.bundle`; the generated `.js` container is an internal encoder input. + +## Remote bundles + +Native split remotes keep the Module Federation runtime in the background realm +while compiling each ReactLynx exposure for both official issuer layers: + +Configure the remote's ReactLynx plugin with +`pluginReactLynx({ experimental_isLazyBundle: true })`. Split federation uses +those DynamicComponent artifacts as independently transported exposures and +fails the build when the option is missing. + +```ts +pluginLynxModuleFederation( + { + name: 'catalog', + exposes: { + './Card': './src/Card', + }, + shared: { + '@lynx-js/react': { singleton: true }, + }, + }, + { + environment: 'lynx', + remoteBundle: { + target: 'lynx', + filename: 'catalog.lynx.bundle', + }, + }, +); +``` + +Set `target: 'web'` and `mainThread: true` for a Lynx for Web remote that +supports both realms: + +```ts +pluginLynxModuleFederation(federationOptions, { + environment: 'web', + mainThread: true, + remoteBundle: { + target: 'web', + filename: 'catalog.web.lynx.bundle', + }, +}); +``` + +### Chunking modes + +`remoteBundle.chunking` controls deployment shape: + +| Mode | Output | Best for | +| ----------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------- | +| `split` (default) | Small container `.lynx.bundle` plus independently fetched lazy `.bundle` chunks | ReactLynx UI, caching, and requested-expose loading | +| `single` | One native `.lynx.bundle` containing background-only module chunks | Non-UI native modules requiring one deployment artifact | + +`split` is the federation-safe default. It avoids placing every expose and +shared fallback in the entry bundle. Keep all emitted lazy `.bundle` files +beside the remote entry (or under its manifest `publicPath`) when publishing. +Each ReactLynx exposure needs its own paired background/main-thread lazy root. +Split native remote entries embed background and main-thread container sections, +and each lazy bundle encodes both component programs into one DynamicComponent +artifact. Web remotes use the equivalent paired external bundle. Web remotes +reject `single`, and native `single` is limited to background-only non-UI +modules. + +`publicPath: 'auto'` is supported and recommended for colocated Web split +assets. Module Federation still resolves the public remote entry through +manifest inference or `getPublicPath`. Browser and browser-like transports +infer a root-relative manifest's origin from the fetched `Response.url`; Node +hosts should provide an absolute manifest URL (or a custom fetch response with +an absolute URL). The Lynx runtime plugin then resolves lazy bundles relative +to that actual entry URL instead of the DOM `currentScript` signal used by +Webpack's browser auto-public-path runtime. Native remotes should follow +Rspeedy and set an absolute `output.assetPrefix` (for example from a CDN or +`LYNX_REMOTE_ORIGIN`) because Lynx's native lazy-bundle bootstrap also consumes +Webpack's public path. Explicit public paths remain unchanged. + +Ordinary Rspeedy entry bundles are preserved by default. A dedicated remote +environment may set `preserveSourceEntryBundles: false` to publish only its +manifest, federation container, and split exposure bundles. + +```mermaid +flowchart LR + H["Host import or loadRemote"] --> M["mf-manifest.json"] + M --> C["container.lynx.bundle"] + C --> R["Module Federation container"] + R --> A["ActivityFeed lazy .bundle"] + A --> N["nested lazy .bundle"] + R --> D["Details lazy .bundle"] + R --> S["host-provided shared modules"] +``` + +The runtime fetches and registers the container with `lynx.fetchBundle`, then +evaluates its section with `lynx.loadScript`. In split mode, the generated +Lynx async-chunk map supplies the lazy `.bundle` URL; `lynx.loadLazyBundle` +fetches it, and the returned `ids`, `modules`, and `runtime` install into the +calling webpack runtime. Cached lazy bundles retain ReactLynx's synchronous +thenable so a first render can resolve in the same turn; asynchronous shared +consumes and network loads remain promise- and timeout-bound. Failed and +timed-out entry loads are evicted so a later request can retry. The default +timeout is 30 seconds and can be changed with `runtimePluginOptions.timeout`. + +The manifest declares `remoteEntry.type: 'lynx'`. The runtime plugin handles +only that type (or a `.lynx.bundle` URL), leaving `script`, `module`, and other +runtime-core loaders untouched. Explicit background-only raw JavaScript entries +may use `type: 'lynx-js'` and `lynx.requireModuleAsync`. + +## Layers and singletons + +Split remote bundles and hosts with `mainThread: true` compile exposes for both +Lynx issuer layers and register shared declarations in realm-qualified scopes. +Unqualified shares default to the background realm; use +`realm: 'main-thread'` only for a module authored for that runtime. The semantic +realm is resolved against the DSL's exposed layer constants, so application +configs do not hard-code layer names. + +A singleton is unique within one JavaScript realm, share scope, and share key. +The host, compiled imports, runtime API consumers, and remotes can therefore +share one stateful instance inside the background realm. The main-thread realm +gets a separate instance by design: Lynx does not transfer JavaScript object +identity across its thread boundary. + +Prefer the official ReactLynx lazy-runtime bridge for UI bundles. An exact +`@lynx-js/react` share does not cover `/internal`, JSX-runtime, Lepus, and lazy +subpaths; partial ReactLynx sharing creates distinct runtime state. Application +state and other ordinary libraries are safe singleton candidates. + +One realm-neutral container program carries internal layer-specific expose +aliases. Keys ending in `__main_thread` are reserved for those aliases; users +still import the public key such as `catalog/Card`. + +```mermaid +flowchart TB + C["One compiled container"] --> B["BACKGROUND expose aliases"] + C --> T["MAIN_THREAD expose aliases"] + B --> BS["default:react:background"] + T --> TS["default:react:main-thread"] + BS --> BI["background singleton instance"] + TS --> TI["main-thread singleton instance"] +``` + +The enhanced federation runtime itself executes in the native background +realm. A native split remote also embeds a paired main-thread container runtime +section so fetched TASM/MTS snapshot chunks install into that realm's registry. +Both sections ship in the same external `.lynx.bundle`; there is no second +network-deployed remote entry. JavaScript object identity remains realm-local. + +See `apps/lynx-module-federation-demo` for an official Rspeedy native app, +native artifact checks, and a real Lynx for Web browser E2E. diff --git a/packages/lynx/package.json b/packages/lynx/package.json new file mode 100644 index 00000000000..4ab04b5ae7a --- /dev/null +++ b/packages/lynx/package.json @@ -0,0 +1,135 @@ +{ + "name": "@module-federation/lynx", + "version": "0.0.0", + "description": "Module Federation adapter for native Lynx and Lynx for Web runtimes", + "keywords": [ + "module-federation", + "lynx", + "rspeedy", + "rspack" + ], + "type": "commonjs", + "license": "MIT", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/module-federation/core.git", + "directory": "packages/lynx" + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ], + "sideEffects": false, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "./runtimePlugin": { + "import": { + "types": "./dist/runtimePlugin.d.mts", + "default": "./dist/runtimePlugin.mjs" + }, + "require": { + "types": "./dist/runtimePlugin.d.ts", + "default": "./dist/runtimePlugin.js" + } + }, + "./reactRuntimePlugin": { + "import": { + "types": "./dist/reactRuntimePlugin.d.mts", + "default": "./dist/reactRuntimePlugin.mjs" + }, + "require": { + "types": "./dist/reactRuntimePlugin.d.ts", + "default": "./dist/reactRuntimePlugin.js" + } + }, + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + ".": [ + "./dist/index.d.ts" + ], + "runtimePlugin": [ + "./dist/runtimePlugin.d.ts" + ], + "reactRuntimePlugin": [ + "./dist/reactRuntimePlugin.d.ts" + ] + } + }, + "dependencies": { + "@module-federation/runtime-core": "workspace:*", + "@module-federation/sdk": "workspace:*" + }, + "peerDependencies": { + "@lynx-js/cache-events-webpack-plugin": "^0.1.0 || ^0.2.0", + "@lynx-js/css-serializer": "^0.1.6", + "@lynx-js/tasm": "^0.0.39", + "@lynx-js/react": ">=0.123.1", + "@lynx-js/template-webpack-plugin": "^0.13.1", + "@lynx-js/web-core": "^0.22.3", + "@rsbuild/core": "^2.0.0", + "@rspack/core": ">=2.1.5-canary-54a0d8f3-20260715194831 <3" + }, + "peerDependenciesMeta": { + "@lynx-js/cache-events-webpack-plugin": { + "optional": true + }, + "@lynx-js/css-serializer": { + "optional": true + }, + "@lynx-js/tasm": { + "optional": true + }, + "@lynx-js/template-webpack-plugin": { + "optional": true + }, + "@lynx-js/web-core": { + "optional": true + }, + "@lynx-js/react": { + "optional": true + } + }, + "devDependencies": { + "@lynx-js/cache-events-webpack-plugin": "0.2.0", + "@lynx-js/css-serializer": "0.1.6", + "@lynx-js/chunk-loading-webpack-plugin": "0.4.0", + "@lynx-js/react": "https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09", + "@lynx-js/tasm": "0.0.39", + "@lynx-js/web-core": "https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-core@e22ca09", + "@module-federation/runtime-tools": "workspace:*", + "@rsbuild/core": "2.1.4", + "@rspack/core": "npm:@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831", + "@rstest/core": "^0.10.6", + "typescript": "7.0.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "scripts": { + "build": "tsdown --config tsdown.config.mts && pnpm run test:cjs", + "lint": "ESLINT_USE_FLAT_CONFIG=false pnpm exec eslint --ignore-pattern node_modules \"**/*.ts\" \"package.json\"", + "test": "pnpm exec rstest --passWithNoTests", + "test:cjs": "node ./scripts/cjs-require-smoke.cjs", + "typecheck": "tsc -p tsconfig.lib.json --noEmit", + "pre-release": "pnpm run test && pnpm run build" + } +} diff --git a/packages/lynx/rstest.config.mts b/packages/lynx/rstest.config.mts new file mode 100644 index 00000000000..cf8350429f5 --- /dev/null +++ b/packages/lynx/rstest.config.mts @@ -0,0 +1,7 @@ +import { defineConfig } from '@rstest/core'; + +export default defineConfig({ + testEnvironment: 'node', + include: ['src/**/*.{test,spec}.ts'], + reporters: ['default'], +}); diff --git a/packages/lynx/scripts/cjs-require-smoke.cjs b/packages/lynx/scripts/cjs-require-smoke.cjs new file mode 100644 index 00000000000..58289f368eb --- /dev/null +++ b/packages/lynx/scripts/cjs-require-smoke.cjs @@ -0,0 +1,17 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const lynx = require('..'); +const packageLynx = require('@module-federation/lynx'); +const reactRuntimePlugin = require('@module-federation/lynx/reactRuntimePlugin'); +const runtimePlugin = require('@module-federation/lynx/runtimePlugin'); + +assert.equal(typeof lynx.pluginLynxModuleFederation, 'function'); +assert.equal(lynx.LYNX_RUNTIME_PLUGIN, '@module-federation/lynx/runtimePlugin'); +assert.equal( + packageLynx.pluginLynxModuleFederation, + lynx.pluginLynxModuleFederation, +); +assert.equal(typeof runtimePlugin.default, 'function'); +assert.equal(typeof runtimePlugin.patchLynxChunkLoading, 'function'); +assert.equal(typeof reactRuntimePlugin.default, 'function'); diff --git a/packages/lynx/src/chunkLoadingMatcher.test.ts b/packages/lynx/src/chunkLoadingMatcher.test.ts new file mode 100644 index 00000000000..f3f8458e6be --- /dev/null +++ b/packages/lynx/src/chunkLoadingMatcher.test.ts @@ -0,0 +1,436 @@ +import { describe, expect, it, rs } from '@rstest/core'; + +import { createLynxChunkLoadingMatcherPlugin } from './chunkLoadingMatcher'; +import { createRemoteBundleCompilationStateStore } from './remoteBundleCompilationState'; + +describe('Lynx chunk-loading matcher', () => { + it('keeps lazy bundle state isolated between compilations', () => { + type Compilation = { + chunks: Set; + entrypoints: Map; + hooks: { + runtimeRequirementInTree: { + for: () => { tap: () => void }; + }; + }; + }; + const stateStore = createRemoteBundleCompilationStateStore(); + let onCompilation: ((compilation: Compilation) => void) | undefined; + const beforeEmitByCompilation = new Map any>(); + class RuntimeModule { + static STAGE_TRIGGER = 20; + + constructor( + readonly name: string, + readonly stage: number, + ) {} + } + const compiler = { + webpack: { + RuntimeGlobals: { ensureChunkHandlers: 'ensureChunkHandlers' }, + RuntimeModule, + Template: { + asString: (lines: string[]) => lines.flat().join('\n'), + indent: (lines: string | string[]) => + (Array.isArray(lines) ? lines : [lines]) + .flat() + .map((line) => ` ${line}`) + .join('\n'), + }, + }, + hooks: { + thisCompilation: { + tap(_name: string, callback: (compilation: Compilation) => void) { + onCompilation = callback; + }, + }, + }, + }; + createLynxChunkLoadingMatcherPlugin( + { + getLynxTemplatePluginHooks(compilation) { + return { + asyncChunkName: { + tap() {}, + }, + beforeEmit: { + tap(_name, callback) { + beforeEmitByCompilation.set( + compilation as Compilation, + callback, + ); + }, + }, + }; + }, + }, + { + chunking: 'split', + exposeByExpectedLazyBundleChunk: new Map([ + ['catalog__background_Card', './Card'], + ]), + stateStore, + } as any, + ).apply(compiler as any); + const firstCompilation: Compilation = { + chunks: new Set(), + entrypoints: new Map(), + hooks: { runtimeRequirementInTree: { for: () => ({ tap: () => {} }) } }, + }; + const secondCompilation: Compilation = { + chunks: new Set(), + entrypoints: new Map(), + hooks: { runtimeRequirementInTree: { for: () => ({ tap: () => {} }) } }, + }; + + onCompilation!(firstCompilation); + beforeEmitByCompilation.get(firstCompilation)!({ + entryNames: ['catalog__background_Card'], + finalEncodeOptions: { sourceContent: { appType: 'DynamicComponent' } }, + outputName: 'first.bundle', + }); + onCompilation!(secondCompilation); + + expect(stateStore.for(firstCompilation as any).lazyBundleAssets).toEqual( + new Set(['first.bundle']), + ); + expect( + stateStore.for(firstCompilation as any).lazyBundleAssetByExpose, + ).toEqual(new Map([['./Card', 'first.bundle']])); + expect( + stateStore.for(secondCompilation as any).discardedTemplateAssets, + ).toEqual(new Set()); + expect(stateStore.for(secondCompilation as any).lazyBundleAssets).toEqual( + new Set(), + ); + expect( + stateStore.for(secondCompilation as any).lazyBundleAssetByExpose, + ).toEqual(new Map()); + }); + + it('creates fresh state for non-remote matcher use', () => { + let onCompilation: ((compilation: any) => void) | undefined; + const beforeEmitByCompilation = new Map any>(); + class RuntimeModule { + static STAGE_TRIGGER = 20; + + constructor() {} + } + createLynxChunkLoadingMatcherPlugin( + { + getLynxTemplatePluginHooks(compilation) { + return { + asyncChunkName: { tap() {} }, + beforeEmit: { + tap(_name, callback) { + beforeEmitByCompilation.set(compilation, callback); + }, + }, + }; + }, + }, + { + chunking: 'split', + exposeByExpectedLazyBundleChunk: new Map([ + ['catalog__background_Card', './Card'], + ]), + pairedRealmChunkSuffixes: { + background: '-react__background', + mainThread: '-react__main-thread', + }, + }, + ).apply({ + webpack: { + RuntimeGlobals: { ensureChunkHandlers: 'ensureChunkHandlers' }, + RuntimeModule, + Template: {}, + }, + hooks: { + thisCompilation: { + tap(_name: string, callback: (compilation: any) => void) { + onCompilation = callback; + }, + }, + }, + } as any); + const createCompilation = () => ({ + chunks: new Set(), + entrypoints: new Map(), + hooks: { runtimeRequirementInTree: { for: () => ({ tap: () => {} }) } }, + }); + const firstCompilation = createCompilation(); + const secondCompilation = createCompilation(); + const lazyBundleArgs = (outputName: string) => ({ + entryNames: ['catalog__background_Card'], + finalEncodeOptions: { sourceContent: { appType: 'DynamicComponent' } }, + outputName, + }); + + onCompilation!(firstCompilation); + beforeEmitByCompilation.get(firstCompilation)!( + lazyBundleArgs('first.bundle'), + ); + onCompilation!(secondCompilation); + + expect(() => + beforeEmitByCompilation.get(secondCompilation)!( + lazyBundleArgs('second.bundle'), + ), + ).not.toThrow(); + }); + + it('guards chunks without JavaScript while preserving local JavaScript chunks', () => { + const entryChunk = { + files: new Set(['host.js']), + getAllAsyncChunks: () => new Set(), + ids: ['host'], + name: 'host', + }; + const nestedChunk = { + files: new Set(['nested.js']), + getAllAsyncChunks: () => new Set(), + ids: ['nested'], + name: 'nested-feature', + }; + const remoteChunk = { + files: new Set(), + getAllAsyncChunks: () => new Set([nestedChunk]), + ids: [802], + name: 'remote-react__background', + }; + const mainThreadAssetlessChunk = { + files: new Set(), + getAllAsyncChunks: () => new Set(), + ids: [803], + name: 'catalog__main-thread__Empty-react__main-thread', + }; + const localChunk = { + files: new Set(['local.js']), + getAllAsyncChunks: () => new Set(), + ids: [123], + name: 'local', + }; + const cssChunk = { + files: new Set(['styles.css']), + getAllAsyncChunks: () => new Set(), + ids: [456], + name: 'styles', + }; + const chunks = new Set([ + entryChunk, + remoteChunk, + mainThreadAssetlessChunk, + localChunk, + cssChunk, + ]); + const chunkGraph = { + getNumberOfEntryModules(chunk: unknown) { + return chunk === entryChunk ? 1 : 0; + }, + getChunkModulesIterableBySourceType(chunk: unknown) { + return chunk === localChunk ? [{}] : []; + }, + }; + let onCompilation: ((compilation: any) => void) | undefined; + let addMatcher: ((chunk: unknown) => void) | undefined; + let renameAsyncChunk: ((chunkName: string) => string) | undefined; + let beforeEncode: ((args: any) => any) | undefined; + let beforeEmit: ((args: any) => any) | undefined; + const addRuntimeModule = rs.fn(); + const stateStore = createRemoteBundleCompilationStateStore(); + + class RuntimeModule { + static STAGE_TRIGGER = 20; + protected compilation: any; + protected chunkGraph: any; + + constructor( + readonly name: string, + readonly stage: number, + ) {} + + attach(compilation: any, _chunk: unknown, graph: any) { + this.compilation = compilation; + this.chunkGraph = graph; + } + } + + const compiler = { + webpack: { + RuntimeGlobals: { ensureChunkHandlers: 'ensureChunkHandlers' }, + RuntimeModule, + Template: { + asString: (lines: string[]) => lines.flat().join('\n'), + indent: (lines: string | string[]) => + (Array.isArray(lines) ? lines : [lines]) + .flat() + .map((line) => ` ${line}`) + .join('\n'), + }, + }, + hooks: { + thisCompilation: { + tap(_name: string, callback: (compilation: any) => void) { + onCompilation = callback; + }, + }, + }, + }; + const compilation = { + addRuntimeModule, + chunkGraph, + chunks, + entrypoints: new Map([['remote', { chunks: [remoteChunk] }]]), + hooks: { + runtimeRequirementInTree: { + for() { + return { + tap(_name: string, callback: (chunk: unknown) => void) { + addMatcher = callback; + }, + }; + }, + }, + }, + }; + + createLynxChunkLoadingMatcherPlugin( + { + getLynxTemplatePluginHooks() { + return { + asyncChunkName: { + tap(_name, callback) { + renameAsyncChunk = callback; + }, + }, + beforeEncode: { + tap(_name, callback) { + beforeEncode = callback; + }, + }, + beforeEmit: { + tap(_name, callback) { + beforeEmit = callback; + }, + }, + }; + }, + }, + { + autoPublicPath: true, + backgroundOnlyRemote: true, + chunking: 'split', + discardSourceEntryBundles: true, + exposeByExpectedLazyBundleChunk: new Map([ + ['catalog__background_Card', './Card'], + ['catalog__background_Details', './Details'], + ]), + includedChunkPrefixes: ['catalog__background_'], + remoteEntryName: 'remote', + pairedRealmChunkPrefixes: { + background: 'catalog__background_', + mainThread: 'catalog__main-thread__', + }, + pairedRealmChunkSuffixes: { + background: '-react__background', + mainThread: '-react__main-thread', + }, + stateStore, + }, + ).apply(compiler as any); + onCompilation!(compilation); + const state = stateStore.for(compilation as any); + expect(state.discardedTemplateAssets.size).toBe(0); + expect(state.lazyBundleAssets.size).toBe(0); + expect(state.lazyBundleAssetByExpose.size).toBe(0); + addMatcher!(entryChunk); + + const cardArgs = { + encodeData: { + lepusCode: { root: { source: 'main-thread runtime' } }, + sourceContent: { appType: 'card' }, + }, + }; + expect(beforeEncode!(cardArgs)).toBe(cardArgs); + expect(cardArgs.encodeData.lepusCode.root).toBeUndefined(); + const lazyRoot = { source: 'component main thread' }; + beforeEncode!({ + encodeData: { + lepusCode: { root: lazyRoot }, + sourceContent: { appType: 'DynamicComponent' }, + }, + }); + expect(lazyRoot).toEqual({ source: 'component main thread' }); + + const lazyArgs = { + finalEncodeOptions: { + sourceContent: { appType: 'DynamicComponent' }, + }, + chunkGroups: [ + { name: 'catalog__background_Card-react__background' }, + { name: 'catalog__main-thread__Card-react__main-thread' }, + ], + outputName: 'async/catalog__background_Card.hash.bundle', + }; + expect(beforeEmit!(lazyArgs)).toBe(lazyArgs); + beforeEmit!({ + chunkGroups: [{ name: 'remote-react__background' }], + finalEncodeOptions: { sourceContent: { appType: 'card' } }, + outputName: 'bootstrap.lynx.bundle', + }); + beforeEmit!({ + chunkGroups: [{ name: 'nested-feature' }], + finalEncodeOptions: { + sourceContent: { appType: 'DynamicComponent' }, + }, + outputName: 'async/nested-feature.bundle', + }); + expect(state.lazyBundleAssets).toEqual( + new Set([ + 'async/catalog__background_Card.hash.bundle', + 'async/nested-feature.bundle', + ]), + ); + expect(state.lazyBundleAssetByExpose).toEqual( + new Map([['./Card', 'async/catalog__background_Card.hash.bundle']]), + ); + expect(state.discardedTemplateAssets).toEqual( + new Set(['bootstrap.lynx.bundle']), + ); + + expect(addRuntimeModule).toHaveBeenCalledTimes(2); + const runtimeModule = addRuntimeModule.mock.calls.find( + ([, module]) => module.name === 'lynx chunk loading matcher', + )![1]; + runtimeModule.attach(compilation, entryChunk, chunkGraph); + const source = runtimeModule.generate(); + + expect(source).toContain('"802":1'); + expect(source).toContain('__webpack_require__.lynx_chunking = "split"'); + expect(source).toContain( + '__webpack_require__.lynx_public_path_auto = true', + ); + expect(source).toContain('"456":1'); + expect(source).not.toContain('"123":1'); + expect(source).not.toContain('"host":1'); + expect(source).toContain('__webpack_require__.f.require = function'); + const startupModule = addRuntimeModule.mock.calls.find( + ([, module]) => module.name === 'lynx federation startup promise', + )![1]; + startupModule.attach(compilation, entryChunk, chunkGraph); + expect(startupModule.generate()).toContain( + 'Promise.resolve(lynxFederationStartup.apply(this, arguments))', + ); + expect(renameAsyncChunk!('remote')).toBe(''); + expect(renameAsyncChunk!('local')).toBe('local'); + expect(renameAsyncChunk!('catalog__background_Card')).toBe( + 'catalog__background_Card', + ); + expect( + renameAsyncChunk!('catalog__main-thread__Card-react__main-thread'), + ).toBe('catalog__background_Card'); + expect( + renameAsyncChunk!('catalog__main-thread__Empty-react__main-thread'), + ).toBe(''); + }); +}); diff --git a/packages/lynx/src/chunkLoadingMatcher.ts b/packages/lynx/src/chunkLoadingMatcher.ts new file mode 100644 index 00000000000..362b9869b26 --- /dev/null +++ b/packages/lynx/src/chunkLoadingMatcher.ts @@ -0,0 +1,339 @@ +import type { + Chunk, + Compilation, + Compiler, + WebpackPluginInstance, +} from '@rspack/core'; + +import { + createRemoteBundleCompilationStateStore, + type RemoteBundleCompilationStateStore, +} from './remoteBundleCompilationState'; + +interface TemplateEncodeArgs { + encodeData: { + lepusCode: { + root?: unknown; + }; + sourceContent: { + appType: string; + }; + }; +} + +interface TemplateEmitArgs { + chunkGroups?: Array<{ name?: string | null }>; + entryNames?: string[]; + finalEncodeOptions: { + sourceContent: { + appType: string; + }; + }; + outputName: string; +} + +export interface LynxTemplatePluginApi { + getLynxTemplatePluginHooks(compilation: unknown): { + asyncChunkName: { + tap(name: string, callback: (chunkName: string) => string): void; + }; + beforeEncode?: { + tap(name: string, callback: (args: TemplateEncodeArgs) => unknown): void; + }; + beforeEmit?: { + tap(name: string, callback: (args: TemplateEmitArgs) => unknown): void; + }; + }; +} + +interface ChunkLoadingMatcherOptions { + autoPublicPath?: boolean; + backgroundOnlyRemote?: boolean; + chunking?: 'single' | 'split'; + discardSourceEntryBundles?: boolean; + exposeByExpectedLazyBundleChunk?: ReadonlyMap; + includedChunkPrefixes?: string[]; + remoteEntryName?: string; + pairedRealmChunkPrefixes?: { + background: string; + mainThread: string; + }; + pairedRealmChunkSuffixes?: { + background: string; + mainThread: string; + }; + stateStore?: RemoteBundleCompilationStateStore; +} + +interface ChunkGraph { + getNumberOfEntryModules(chunk: Chunk): number; + getChunkModulesIterableBySourceType( + chunk: Chunk, + sourceType: string, + ): Iterable | undefined; +} + +const chunkHasJavaScript = (chunk: Chunk, chunkGraph: ChunkGraph): boolean => { + if (chunkGraph.getNumberOfEntryModules(chunk) > 0) { + return true; + } + + const modules = chunkGraph.getChunkModulesIterableBySourceType( + chunk, + 'javascript', + ); + return modules?.[Symbol.iterator]().next().done === false; +}; + +const getNonJavaScriptChunkIds = ( + chunks: Iterable, + chunkGraph: ChunkGraph, +): Array => + Array.from(chunks).flatMap((chunk) => + chunkHasJavaScript(chunk, chunkGraph) ? [] : [...(chunk.ids ?? [])], + ); + +const getRemoteChunkNames = ( + compilation: { + chunks: Iterable; + entrypoints: ReadonlyMap }>; + }, + options: ChunkLoadingMatcherOptions, +): Set => { + const chunks = new Set(); + const addChunkGraph = (chunk: Chunk): void => { + chunks.add(chunk); + for (const asyncChunk of chunk.getAllAsyncChunks()) { + chunks.add(asyncChunk); + } + }; + + if (options.remoteEntryName) { + for (const chunk of compilation.entrypoints.get(options.remoteEntryName) + ?.chunks ?? []) { + addChunkGraph(chunk); + } + } + for (const chunk of compilation.chunks) { + if ( + typeof chunk.name === 'string' && + options.includedChunkPrefixes?.some((prefix) => + chunk.name!.startsWith(prefix), + ) + ) { + addChunkGraph(chunk); + } + } + + return new Set( + Array.from(chunks).flatMap((chunk) => + typeof chunk.name === 'string' ? [chunk.name] : [], + ), + ); +}; + +const stripPairedRealmChunkSuffix = ( + chunkName: string, + suffixes: ChunkLoadingMatcherOptions['pairedRealmChunkSuffixes'], +): string => + suffixes + ? [suffixes.background, suffixes.mainThread].reduce( + (name, suffix) => + name.endsWith(suffix) ? name.slice(0, -suffix.length) : name, + chunkName, + ) + : chunkName; + +const normalizeLazyBundleChunkName = ( + chunkName: string, + options: ChunkLoadingMatcherOptions, +): string => { + const normalizedChunkName = stripPairedRealmChunkSuffix( + chunkName, + options.pairedRealmChunkSuffixes, + ); + const prefixes = options.pairedRealmChunkPrefixes; + return prefixes && normalizedChunkName.startsWith(prefixes.mainThread) + ? `${prefixes.background}${normalizedChunkName.slice(prefixes.mainThread.length)}` + : normalizedChunkName; +}; + +export const createLynxChunkLoadingMatcherPlugin = ( + lynxTemplatePlugin: LynxTemplatePluginApi | undefined, + options: ChunkLoadingMatcherOptions, +): WebpackPluginInstance => ({ + apply(compiler: Compiler) { + const stateStore = + options.stateStore ?? createRemoteBundleCompilationStateStore(); + const pluginName = 'LynxModuleFederationChunkLoadingMatcher'; + const { RuntimeGlobals, RuntimeModule, Template } = compiler.webpack; + + compiler.hooks.thisCompilation.tap(pluginName, (compilation) => { + const state = stateStore.for(compilation as Compilation); + let remoteChunkNames: Set | undefined; + const templateHooks = + lynxTemplatePlugin?.getLynxTemplatePluginHooks(compilation); + templateHooks?.asyncChunkName.tap(pluginName, (chunkName) => { + const layerSuffixes = options.pairedRealmChunkSuffixes; + const normalizedChunkName = stripPairedRealmChunkSuffix( + chunkName, + layerSuffixes, + ); + const hasAssetlessChunk = Array.from(compilation.chunks).some( + (chunk) => { + if ( + chunkHasJavaScript(chunk, compilation.chunkGraph) || + typeof chunk.name !== 'string' + ) { + return false; + } + const suffix = layerSuffixes + ? [layerSuffixes.background, layerSuffixes.mainThread].find( + (candidate) => chunk.name!.endsWith(candidate), + ) + : undefined; + return ( + (suffix ? chunk.name.slice(0, -suffix.length) : chunk.name) === + normalizedChunkName + ); + }, + ); + if (hasAssetlessChunk) { + return ''; + } + return normalizeLazyBundleChunkName(chunkName, options); + }); + if (options.backgroundOnlyRemote) { + templateHooks?.beforeEncode?.tap(pluginName, (args) => { + if (args.encodeData.sourceContent.appType !== 'DynamicComponent') { + args.encodeData.lepusCode.root = undefined; + } + return args; + }); + } + templateHooks?.beforeEmit?.tap(pluginName, (args) => { + let chunkNames = remoteChunkNames; + if (!chunkNames) { + chunkNames = getRemoteChunkNames(compilation, options); + remoteChunkNames = chunkNames; + } + const isDynamicComponent = + args.finalEncodeOptions.sourceContent.appType === 'DynamicComponent'; + const templateChunkNames = [ + ...(args.entryNames ?? []), + ...(args.chunkGroups ?? []).flatMap(({ name }) => + typeof name === 'string' ? [name] : [], + ), + ]; + const isRemoteOutput = templateChunkNames.some((name) => + chunkNames.has(name), + ); + const exposedKeys = new Set( + templateChunkNames.flatMap((name) => { + const exposedKey = options.exposeByExpectedLazyBundleChunk?.get( + normalizeLazyBundleChunkName(name, options), + ); + return exposedKey === undefined ? [] : [exposedKey]; + }), + ); + + if (isDynamicComponent && (isRemoteOutput || exposedKeys.size > 0)) { + state.lazyBundleAssets.add(args.outputName); + for (const exposedKey of exposedKeys) { + const previousAsset = state.lazyBundleAssetByExpose.get(exposedKey); + if (previousAsset && previousAsset !== args.outputName) { + throw new Error( + `@module-federation/lynx expose "${exposedKey}" emitted multiple DynamicComponent lazy bundles: "${previousAsset}" and "${args.outputName}".`, + ); + } + state.lazyBundleAssetByExpose.set(exposedKey, args.outputName); + } + if (options.chunking === 'split') { + state.discardedTemplateAssets.delete(args.outputName); + } else { + state.discardedTemplateAssets.add(args.outputName); + } + } else if (options.discardSourceEntryBundles) { + state.discardedTemplateAssets.add(args.outputName); + } + return args; + }); + + class ChunkLoadingMatcherRuntimeModule extends RuntimeModule { + constructor() { + super('lynx chunk loading matcher', RuntimeModule.STAGE_TRIGGER); + } + + override generate(): string { + const autoPublicPath = options.autoPublicPath + ? '__webpack_require__.lynx_public_path_auto = true;' + : ''; + const chunking = options.chunking + ? `__webpack_require__.lynx_chunking = ${JSON.stringify(options.chunking)};` + : ''; + const chunkIds = getNonJavaScriptChunkIds( + this.compilation!.chunks, + this.chunkGraph!, + ); + if (chunkIds.length === 0) { + return Template.asString([autoPublicPath, chunking]); + } + + const matcher = Object.fromEntries( + chunkIds.map((id) => [String(id), 1]), + ); + return Template.asString([ + autoPublicPath, + chunking, + 'var lynxChunkLoader = __webpack_require__.f.require;', + 'if (lynxChunkLoader) {', + Template.indent([ + `var lynxChunksWithoutJavaScript = ${JSON.stringify(matcher)};`, + '__webpack_require__.f.require = function(chunkId, promises) {', + Template.indent([ + 'if (!__webpack_require__.o(lynxChunksWithoutJavaScript, chunkId)) {', + Template.indent('return lynxChunkLoader(chunkId, promises);'), + '}', + ]), + '};', + ]), + '}', + ]); + } + } + + class StartupPromiseRuntimeModule extends RuntimeModule { + constructor() { + super( + 'lynx federation startup promise', + RuntimeModule.STAGE_TRIGGER - 1, + ); + } + + override generate(): string { + return Template.asString([ + 'var lynxFederationStartup = __webpack_require__.x;', + '__webpack_require__.x = function() {', + Template.indent( + 'return Promise.resolve(lynxFederationStartup.apply(this, arguments));', + ), + '};', + ]); + } + } + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.ensureChunkHandlers) + .tap(pluginName, (chunk) => { + compilation.addRuntimeModule( + chunk, + new ChunkLoadingMatcherRuntimeModule(), + ); + compilation.addRuntimeModule( + chunk, + new StartupPromiseRuntimeModule(), + ); + }); + }); + }, +}); diff --git a/packages/lynx/src/compilerFederation.ts b/packages/lynx/src/compilerFederation.ts new file mode 100644 index 00000000000..b8c0f8a2ff3 --- /dev/null +++ b/packages/lynx/src/compilerFederation.ts @@ -0,0 +1,20 @@ +import type { + Compiler, + ModuleFederationPluginOptions, + WebpackPluginInstance, +} from '@rspack/core'; + +interface CompilerModuleFederationPlugin extends WebpackPluginInstance { + _options: ModuleFederationPluginOptions; +} + +export const createCompilerModuleFederationPlugin = ( + options: ModuleFederationPluginOptions, +): CompilerModuleFederationPlugin => ({ + _options: options, + apply(compiler: Compiler) { + new compiler.webpack.container.ModuleFederationPlugin(options).apply( + compiler, + ); + }, +}); diff --git a/packages/lynx/src/externalBundle.test.ts b/packages/lynx/src/externalBundle.test.ts new file mode 100644 index 00000000000..4d988a8e650 --- /dev/null +++ b/packages/lynx/src/externalBundle.test.ts @@ -0,0 +1,285 @@ +import { describe, expect, it, rs } from '@rstest/core'; + +import { createLynxExternalBundlePlugin } from './externalBundle'; +import { createRemoteBundleCompilationStateStore } from './remoteBundleCompilationState'; + +interface TestAsset { + name: string; + source: { source(): string | Buffer }; +} + +const createAsset = (name: string, content = name): TestAsset => ({ + name, + source: { source: () => content }, +}); + +const setupPlugin = ( + chunking: 'split' | 'single', + lazyBundleAssets = new Set([ + 'async/Card.hash.bundle', + 'async/Nested.hash.bundle', + ]), + lazyBundleAssetByExpose = new Map([ + ['./Card', 'async/Card.hash.bundle'], + ['./Nested', 'async/Nested.hash.bundle'], + ]), +) => { + const encode = rs.fn(async () => ({ buffer: Buffer.from('external') })); + const discardedTemplateAssets = new Set(['bootstrap.bundle']); + const stateStore = createRemoteBundleCompilationStateStore(); + if (chunking === 'single') { + discardedTemplateAssets.add('async/Card.hash.bundle'); + discardedTemplateAssets.add('async/Nested.hash.bundle'); + } + const plugin = createLynxExternalBundlePlugin({ + bundleFileName: 'catalog.lynx.bundle', + chunking, + encode, + entryAssets: ['catalog.js'], + entryName: 'catalog', + entrySectionNames: new Map([['catalog.js', 'catalog_global']]), + exposeByExpectedLazyBundleChunk: new Map([ + ['catalog__background_Card', './Card'], + ['catalog__background_Nested', './Nested'], + ]), + includedChunkPrefixes: ['catalog__background_', 'catalog__main-thread__'], + preservedAssets: ['mf-manifest.json', 'mf-stats.json'], + stateStore, + }); + let onCompilation: ((compilation: any) => void) | undefined; + let onEmit: ((compilation: any) => Promise) | undefined; + const compiler = { + webpack: { + Compilation: { PROCESS_ASSETS_STAGE_REPORT: 5_000 }, + sources: { + RawSource: class { + constructor(readonly value: Buffer | string) {} + }, + }, + }, + hooks: { + thisCompilation: { + tap(_name: string, callback: (compilation: any) => void) { + onCompilation = callback; + }, + }, + emit: { + tapPromise( + _name: string, + callback: (compilation: any) => Promise, + ) { + onEmit = callback; + }, + }, + }, + }; + plugin.apply(compiler as any); + + return { + encode, + onCompilation: () => (compilation: any) => { + const state = stateStore.for(compilation); + state.discardedTemplateAssets = new Set(discardedTemplateAssets); + state.lazyBundleAssets = new Set(lazyBundleAssets); + state.lazyBundleAssetByExpose = new Map(lazyBundleAssetByExpose); + state.pairedBundleChunks.add('catalog__main-thread.js'); + onCompilation!(compilation); + }, + onEmit: () => onEmit!, + }; +}; + +const createCompilation = (sourceAssets: TestAsset[]) => { + let assets = [...sourceAssets]; + let snapshot: (() => void) | undefined; + const deleteAsset = rs.fn((name: string) => { + assets = assets.filter((asset) => asset.name !== name); + }); + const emitAsset = rs.fn((name: string, source: TestAsset['source']) => { + assets = [ + ...assets.filter((asset) => asset.name !== name), + { name, source }, + ]; + }); + const nestedChunk = { + name: 'nested-feature', + files: new Set(['async/nested-feature.js', 'async/nested-feature.css']), + auxiliaryFiles: new Set(), + getAllAsyncChunks: () => new Set(), + }; + const remoteChunk = { + name: 'catalog__background_Card', + files: new Set([ + 'async/catalog__background_Card.js', + 'async/catalog__background_Card.css', + ]), + auxiliaryFiles: new Set(), + getAllAsyncChunks: () => new Set([nestedChunk]), + }; + const compilation = { + entrypoints: new Map([ + [ + 'catalog', + { + chunks: [remoteChunk], + getFiles: () => ['catalog.js', 'runtime.js'], + }, + ], + ]), + chunks: [remoteChunk, nestedChunk], + getAssets: () => assets, + getAsset: (name: string) => assets.find((asset) => asset.name === name), + deleteAsset, + emitAsset, + hooks: { + processAssets: { + tap(options: { stage: number }, callback: () => void) { + expect(options.stage).toBe(5_000); + snapshot = callback; + }, + }, + }, + }; + + return { + compilation, + names: () => assets.map(({ name }) => name).sort(), + replaceAssets(nextAssets: TestAsset[]) { + assets = nextAssets; + }, + snapshot: () => snapshot!(), + }; +}; + +describe('Lynx external bundle', () => { + const sourceAssets = [ + createAsset('catalog.js', 'module.exports = "container"'), + createAsset('runtime.js', 'module.exports = "runtime"'), + createAsset('async/catalog__background_Card.js', 'module.exports = "card"'), + createAsset('async/catalog__background_Card.css', '.card {}'), + createAsset('async/nested-feature.js', 'module.exports = "nested"'), + createAsset('async/nested-feature.css', '.nested {}'), + createAsset('unrelated-app.js', 'module.exports = "unrelated"'), + createAsset('ignored.map'), + ]; + + it('keeps split lazy bundles outside the container bundle', async () => { + const { encode, onCompilation, onEmit } = setupPlugin('split'); + const harness = createCompilation(sourceAssets); + onCompilation()(harness.compilation); + harness.snapshot(); + harness.replaceAssets([ + createAsset('mf-manifest.json'), + createAsset('mf-stats.json'), + createAsset('async/Card.hash.bundle'), + createAsset('async/Nested.hash.bundle'), + createAsset('bootstrap.bundle'), + createAsset('unrelated-app.js'), + createAsset('images/orbit.png'), + ]); + + await onEmit()(harness.compilation); + + const encodeOptions = encode.mock.calls[0][0] as any; + expect(encodeOptions.compilerOptions.targetSdkVersion).toBe('3.7'); + expect(encodeOptions.compilerOptions.isExternalBundle).toBe(true); + expect(encodeOptions.compilerOptions.isLazy).toBe(false); + expect(Object.keys(encodeOptions.customSections).sort()).toEqual([ + 'catalog_global', + 'runtime', + ]); + expect(harness.names()).toEqual([ + 'async/Card.hash.bundle', + 'async/Nested.hash.bundle', + 'catalog.lynx.bundle', + 'images/orbit.png', + 'mf-manifest.json', + 'mf-stats.json', + 'unrelated-app.js', + ]); + }); + + it('keeps REPORT asset snapshots with their compilation', async () => { + const { encode, onCompilation, onEmit } = setupPlugin('single'); + const first = createCompilation([createAsset('catalog.js', 'first')]); + const second = createCompilation([createAsset('catalog.js', 'second')]); + + onCompilation()(first.compilation); + first.snapshot(); + onCompilation()(second.compilation); + second.snapshot(); + + await onEmit()(first.compilation); + + const encodeOptions = encode.mock.calls[0][0] as any; + expect(encodeOptions.customSections.catalog_global.content).toBe('first'); + }); + + it('rejects split builds when any expose lacks a ReactLynx lazy bundle', async () => { + const { onCompilation, onEmit } = setupPlugin( + 'split', + new Set(['async/Card.hash.bundle']), + new Map([['./Card', 'async/Card.hash.bundle']]), + ); + const harness = createCompilation(sourceAssets); + onCompilation()(harness.compilation); + harness.snapshot(); + harness.replaceAssets([createAsset('async/Card.hash.bundle')]); + + await expect(onEmit()(harness.compilation)).rejects.toThrow( + 'missing bundles for "./Nested"', + ); + }); + + it('rejects tracked lazy bundles that are missing from the compilation', async () => { + const { onCompilation, onEmit } = setupPlugin('split'); + const harness = createCompilation(sourceAssets); + onCompilation()(harness.compilation); + harness.snapshot(); + harness.replaceAssets([createAsset('async/Card.hash.bundle')]); + + await expect(onEmit()(harness.compilation)).rejects.toThrow( + 'missing bundles for "./Nested"', + ); + }); + + it('snapshots every JS and CSS chunk into one atomic bundle', async () => { + const { encode, onCompilation, onEmit } = setupPlugin('single'); + const harness = createCompilation(sourceAssets); + onCompilation()(harness.compilation); + harness.snapshot(); + harness.replaceAssets([ + createAsset('mf-manifest.json'), + createAsset('mf-stats.json'), + createAsset('async/Card.hash.bundle'), + createAsset('async/Nested.hash.bundle'), + createAsset('unrelated-app.js'), + createAsset('fonts/orbit.woff2'), + ]); + + await onEmit()(harness.compilation); + + const encodeOptions = encode.mock.calls[0][0] as any; + expect(Object.keys(encodeOptions.customSections).sort()).toEqual([ + 'async/catalog__background_Card', + 'async/catalog__background_Card:CSS', + 'async/nested-feature', + 'async/nested-feature:CSS', + 'catalog_global', + 'runtime', + ]); + expect( + encodeOptions.customSections['async/catalog__background_Card:CSS'], + ).toMatchObject({ + encoding: 'CSS', + }); + expect(encodeOptions.customSections).not.toHaveProperty('unrelated-app'); + expect(harness.names()).toEqual([ + 'catalog.lynx.bundle', + 'fonts/orbit.woff2', + 'mf-manifest.json', + 'mf-stats.json', + 'unrelated-app.js', + ]); + }); +}); diff --git a/packages/lynx/src/externalBundle.ts b/packages/lynx/src/externalBundle.ts new file mode 100644 index 00000000000..ab1bdd03fb0 --- /dev/null +++ b/packages/lynx/src/externalBundle.ts @@ -0,0 +1,201 @@ +import type { + Chunk, + Compilation, + Compiler, + WebpackPluginInstance, +} from '@rspack/core'; + +import type { RemoteBundleCompilationStateStore } from './remoteBundleCompilationState'; + +interface ExternalBundleOptions { + bundleFileName: string; + chunking: 'split' | 'single'; + encode: (value: unknown) => Promise<{ buffer: Buffer }>; + engineVersion?: string; + entryAssets: string[]; + entryName: string; + entrySectionNames: ReadonlyMap; + exposeByExpectedLazyBundleChunk: ReadonlyMap; + includedChunkPrefixes: string[]; + preservedAssets: string[]; + stateStore: RemoteBundleCompilationStateStore; +} + +interface LynxExternalBundlePlugin extends WebpackPluginInstance { + options: ExternalBundleOptions; +} + +export const createLynxExternalBundlePlugin = ( + options: ExternalBundleOptions, +): LynxExternalBundlePlugin => ({ + options, + apply(compiler: Compiler) { + compiler.hooks.thisCompilation.tap( + 'LynxModuleFederationExternalBundleSources', + (compilation) => { + const state = options.stateStore.for(compilation as Compilation); + compilation.hooks.processAssets.tap( + { + name: 'LynxModuleFederationExternalBundleSources', + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT, + }, + () => { + const includedAssets = new Set(options.entryAssets); + const entrypoint = compilation.entrypoints.get(options.entryName); + for (const asset of entrypoint?.getFiles() ?? []) { + includedAssets.add(asset); + } + const includedChunks = new Set(); + const addChunkGraph = (chunk: Chunk): void => { + includedChunks.add(chunk); + for (const asyncChunk of chunk.getAllAsyncChunks()) { + includedChunks.add(asyncChunk); + } + }; + for (const chunk of entrypoint?.chunks ?? []) { + addChunkGraph(chunk); + } + for (const chunk of compilation.chunks) { + if ( + typeof chunk.name !== 'string' || + !options.includedChunkPrefixes.some((prefix) => + chunk.name!.startsWith(prefix), + ) + ) { + continue; + } + addChunkGraph(chunk); + } + for (const chunk of includedChunks) { + for (const asset of [ + ...chunk.files, + ...(chunk.auxiliaryFiles ?? []), + ]) { + includedAssets.add(asset); + } + } + state.sourceAssets = compilation + .getAssets() + .filter( + ({ name }) => + includedAssets.has(name) && + (name.endsWith('.js') || name.endsWith('.css')), + ) + .map(({ name, source }) => ({ + content: source.source().toString(), + name, + })); + }, + ); + }, + ); + + compiler.hooks.emit.tapPromise( + { + name: 'LynxModuleFederationExternalBundle', + stage: 10_000, + }, + async (compilation) => { + const state = options.stateStore.for(compilation); + if (options.chunking === 'split') { + const expectedExposes = new Set( + options.exposeByExpectedLazyBundleChunk.values(), + ); + const missingExposes = Array.from(expectedExposes).filter( + (expose) => { + const asset = state.lazyBundleAssetByExpose.get(expose); + return !asset || !compilation.getAsset(asset); + }, + ); + if (missingExposes.length > 0) { + throw new Error( + `@module-federation/lynx split remote bundles require every expose to emit a DynamicComponent lazy bundle; missing bundles for ${missingExposes.map((expose) => `"${expose}"`).join(', ')}.`, + ); + } + } + const { cssChunksToMap } = await import('@lynx-js/css-serializer'); + const entryAssets = new Set(options.entryAssets); + for (const asset of compilation.entrypoints + .get(options.entryName) + ?.getFiles() ?? []) { + entryAssets.add(asset); + } + const encodedAssets = + options.chunking === 'single' + ? state.sourceAssets + : state.sourceAssets.filter(({ name }) => entryAssets.has(name)); + const customSections = encodedAssets.reduce>( + (sections, asset) => { + if (asset.name.endsWith('.js')) { + const sectionName = + options.entrySectionNames.get(asset.name) ?? + asset.name.replace(/\.js$/, ''); + sections[sectionName] = { + ...(state.pairedBundleChunks.has(asset.name) + ? { encoding: 'JsBytecode' } + : {}), + content: asset.content, + }; + } else if (asset.name.endsWith('.css')) { + sections[`${asset.name.replace(/\.css$/, '')}:CSS`] = { + encoding: 'CSS', + content: { + ruleList: + cssChunksToMap([asset.content], [], true).cssMap[0] ?? [], + }, + }; + } + return sections; + }, + {}, + ); + const encodeOptions = { + compilerOptions: { + enableFiberArch: true, + useLepusNG: true, + isExternalBundle: true, + isLazy: false, + targetSdkVersion: options.engineVersion ?? '3.7', + enableCSSInvalidation: true, + enableCSSSelector: true, + }, + sourceContent: { appType: 'DynamicComponent' }, + customSections, + }; + const { buffer } = await options.encode(encodeOptions); + const preservedAssets = new Set(options.preservedAssets); + if (options.chunking === 'split') { + for (const name of state.lazyBundleAssets) { + preservedAssets.add(name); + } + } + for (const { name } of state.sourceAssets) { + if (!preservedAssets.has(name)) { + compilation.deleteAsset(name); + } + } + for (const name of state.discardedTemplateAssets) { + if (!preservedAssets.has(name)) { + compilation.deleteAsset(name); + } + } + compilation.emitAsset( + options.bundleFileName, + new compiler.webpack.sources.RawSource(buffer, false), + ); + if ( + process.env.DEBUG?.toLowerCase() + .split(',') + .some((value) => ['*', 'rsbuild', 'rspeedy'].includes(value)) + ) { + compilation.emitAsset( + 'tasm.json', + new compiler.webpack.sources.RawSource( + JSON.stringify(encodeOptions, null, 2), + ), + ); + } + }, + ); + }, +}); diff --git a/packages/lynx/src/index.ts b/packages/lynx/src/index.ts new file mode 100644 index 00000000000..2e3100688e1 --- /dev/null +++ b/packages/lynx/src/index.ts @@ -0,0 +1,16 @@ +export { + LYNX_REACT_RUNTIME_PLUGIN, + LYNX_RUNTIME_PLUGIN, + pluginLynxModuleFederation, +} from './plugin'; +export type { + LynxModuleFederationAdapterOptions, + LynxModuleFederationOptions, + LynxNativeRemoteBundleOptions, + LynxRemoteBundleOptions, + LynxRuntimePluginOptions, + LynxShared, + LynxSharedConfig, + LynxSharedRealm, + LynxWebRemoteBundleOptions, +} from './plugin'; diff --git a/packages/lynx/src/lazyChunkLoadController.ts b/packages/lynx/src/lazyChunkLoadController.ts new file mode 100644 index 00000000000..308b328a02a --- /dev/null +++ b/packages/lynx/src/lazyChunkLoadController.ts @@ -0,0 +1,180 @@ +import type { + ChunkPromise, + InstalledChunk, + LynxChunk, +} from './runtimeChunkLoading'; +import { loadWithTimeout } from './runtimeTimeout'; + +export type { + ChunkPromise, + InstalledChunk, + LynxChunk, +} from './runtimeChunkLoading'; + +type LazyChunkLoadState = + | { kind: 'loading'; phase: 'invoking' | 'pending' } + | { + chunk: LynxChunk; + consumes: Promise; + kind: 'waiting-consumes'; + } + | { chunk: LynxChunk; kind: 'installed' } + | { kind: 'failed' }; + +interface LazyChunkLoadControllerArgs { + chunkKey: string; + installedChunks: Record; + timeout: number; + installChunkAfterConsumes( + chunk: LynxChunk, + isCurrent: () => boolean, + ): Promise | undefined; + isChunk(value: unknown): value is LynxChunk; + loadQueryComponent(request: string): PromiseLike; +} + +const timeoutMessage = (request: string, timeout: number): string => + `Timed out loading Lynx lazy bundle "${request}" after ${timeout}ms.`; + +export const createLazyChunkLoadController = ({ + chunkKey, + installedChunks, + timeout, + installChunkAfterConsumes, + isChunk, + loadQueryComponent, +}: LazyChunkLoadControllerArgs) => { + const loadTuple: Exclude = [ + undefined, + undefined, + undefined, + ]; + let state: LazyChunkLoadState = { + kind: 'loading', + phase: 'invoking', + }; + + const isCurrent = (): boolean => + state.kind !== 'failed' && installedChunks[chunkKey] === loadTuple; + + const fail = (): void => { + if (state.kind === 'failed') { + return; + } + state = { kind: 'failed' }; + if (installedChunks[chunkKey] === loadTuple) { + delete installedChunks[chunkKey]; + } + }; + + return { + load(request: string): ChunkPromise { + installedChunks[chunkKey] = loadTuple; + let loaded: ChunkPromise; + try { + loaded = loadQueryComponent(request).then((value) => { + if (!isCurrent()) { + return value; + } + try { + if (!isChunk(value)) { + throw new Error( + `Lynx lazy bundle "${request}" did not export a valid webpack chunk.`, + ); + } + if (!value.ids.some((id) => String(id) === chunkKey)) { + throw new Error( + `Lynx lazy bundle "${request}" did not include requested chunk "${chunkKey}".`, + ); + } + + const invokedSynchronously = + state.kind === 'loading' && state.phase === 'invoking'; + const consumes = installChunkAfterConsumes(value, isCurrent); + if (!consumes) { + state = { chunk: value, kind: 'installed' }; + return value; + } + + state = { + chunk: value, + consumes, + kind: 'waiting-consumes', + }; + return invokedSynchronously ? value : consumes.then(() => value); + } catch (error) { + fail(); + throw error; + } + }); + } catch (error) { + fail(); + return Promise.reject(error); + } + + if (state.kind === 'loading' && state.phase === 'invoking') { + state = { kind: 'loading', phase: 'pending' }; + } + const initialState = state; + let primary: ChunkPromise; + if (initialState.kind === 'waiting-consumes') { + primary = loadWithTimeout( + timeout, + timeoutMessage(request, timeout), + (resolve, reject) => { + initialState.consumes + .then(() => initialState.chunk) + .then(resolve, reject); + }, + ); + } else if ( + initialState.kind === 'installed' || + initialState.kind === 'failed' + ) { + primary = loaded; + } else { + primary = loadWithTimeout( + timeout, + timeoutMessage(request, timeout), + (resolve, reject) => { + loaded.then(resolve, reject); + }, + ); + } + + let tracked = primary; + if ( + initialState.kind !== 'installed' && + initialState.kind !== 'failed' && + isCurrent() + ) { + const installedElsewhere = new Promise((resolve, reject) => { + loadTuple[0] = (value) => { + if (state.kind !== 'failed') { + state = { + chunk: value as LynxChunk, + kind: 'installed', + }; + } + resolve(value); + }; + loadTuple[1] = (error) => { + fail(); + reject(error); + }; + }); + tracked = Promise.race([primary, installedElsewhere]); + } + + const promise = tracked.then( + (value) => value, + (error) => { + fail(); + throw error; + }, + ); + loadTuple[2] = promise; + return promise; + }, + }; +}; diff --git a/packages/lynx/src/plugin.host.test.ts b/packages/lynx/src/plugin.host.test.ts new file mode 100644 index 00000000000..36b818f4e29 --- /dev/null +++ b/packages/lynx/src/plugin.host.test.ts @@ -0,0 +1,445 @@ +import { describe, expect, it, rs } from '@rstest/core'; + +import { LYNX_REACT_RUNTIME_PLUGIN, LYNX_RUNTIME_PLUGIN } from './plugin'; +import type { LynxModuleFederationAdapterOptions } from './plugin'; +import { federationOptions, LAYERS, setupPlugin } from './plugin.testUtils'; + +describe('pluginLynxModuleFederation host adapter', () => { + it('installs the Rspack plugin with Lynx output and layer defaults', async () => { + const { modifyEnvironmentConfig, modifyRspackConfig } = setupPlugin({ + name: 'lynx_host', + exposes: { './App': './src/App' }, + runtimePlugins: ['custom-runtime-plugin'], + }); + const config = await modifyRspackConfig({ plugins: [] }); + const federationPlugin = config.plugins[0]; + + expect(config.output).toEqual({ + chunkLoading: 'lynx', + chunkFormat: 'commonjs', + iife: false, + uniqueName: 'lynx_host', + }); + expect(config.experiments.layers).toBe(true); + expect(federationOptions(federationPlugin)).toMatchObject({ + name: 'lynx_host', + filename: 'remoteEntry.js', + library: { type: 'commonjs-module' }, + remoteType: 'script', + runtimePlugins: [ + ['custom-runtime-plugin', {}], + [ + LYNX_RUNTIME_PLUGIN, + { + realmLayers: { + background: LAYERS.BACKGROUND, + 'main-thread': LAYERS.MAIN_THREAD, + }, + }, + ], + ], + exposes: { + './App': { import: './src/App', layer: LAYERS.BACKGROUND }, + }, + }); + expect(modifyEnvironmentConfig).toHaveBeenCalledTimes(1); + }); + + it('merges resolved options into an explicitly registered runtime plugin', async () => { + const customLayers = { + BACKGROUND: 'worker:realm', + MAIN_THREAD: 'ui:realm', + }; + const { modifyRspackConfig } = setupPlugin( + { + name: 'lynx_host', + runtimePlugins: [[LYNX_RUNTIME_PLUGIN, { timeout: 500 }]], + }, + { runtimePluginOptions: { timeout: 2_000 } }, + customLayers, + ); + const config = await modifyRspackConfig({ plugins: [] }); + + expect(federationOptions(config.plugins[0]).runtimePlugins).toEqual([ + [ + LYNX_RUNTIME_PLUGIN, + { + timeout: 2_000, + realmLayers: { + background: customLayers.BACKGROUND, + 'main-thread': customLayers.MAIN_THREAD, + }, + }, + ], + ]); + }); + + it('bootstraps the ReactLynx lazy-bundle loader before federation startup', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'lynx_host', + runtimePlugins: ['custom-runtime-plugin'], + }, + undefined, + LAYERS, + { resolve: async (request) => request }, + ); + const config = await modifyRspackConfig({ plugins: [] }); + + expect(federationOptions(config.plugins[0]).runtimePlugins).toEqual([ + [LYNX_REACT_RUNTIME_PLUGIN, {}], + ['custom-runtime-plugin', {}], + [ + LYNX_RUNTIME_PLUGIN, + { + realmLayers: { + background: LAYERS.BACKGROUND, + 'main-thread': LAYERS.MAIN_THREAD, + }, + }, + ], + ]); + }); + + it('keeps unqualified host shares in the default background share scope', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'lynx_host', + remotes: { catalog: 'catalog@catalog.lynx.bundle' }, + shared: [{ react: '^19.0.0' }, { react: { singleton: true } }], + runtimePlugins: ['custom-runtime-plugin'], + }, + { + runtimePluginOptions: { timeout: 2_000 }, + }, + ); + const config = await modifyRspackConfig({ plugins: [] }); + + expect(config.plugins).toHaveLength(2); + expect(federationOptions(config.plugins[0])).toMatchObject({ + name: 'lynx_host', + shareScope: [`default:${LAYERS.BACKGROUND}`], + runtimePlugins: [ + ['custom-runtime-plugin', {}], + [LYNX_RUNTIME_PLUGIN, { timeout: 2_000 }], + ], + shared: [ + { + react: { + import: 'react', + requiredVersion: '^19.0.0', + layer: LAYERS.BACKGROUND, + issuerLayer: LAYERS.BACKGROUND, + shareScope: [`default:${LAYERS.BACKGROUND}`], + }, + }, + { + react: { + singleton: true, + layer: LAYERS.BACKGROUND, + issuerLayer: LAYERS.BACKGROUND, + shareScope: [`default:${LAYERS.BACKGROUND}`], + }, + }, + ], + }); + }); + + it('inherits a custom top-level share scope for host shares', async () => { + const { modifyRspackConfig } = setupPlugin({ + name: 'lynx_host', + shareScope: 'application', + shared: { state: { singleton: true } }, + }); + const config = await modifyRspackConfig({ plugins: [] }); + const options = federationOptions(config.plugins[0]); + + expect(options.shareScope).toEqual([`application:${LAYERS.BACKGROUND}`]); + expect(options.shared).toEqual([ + { + state: { + singleton: true, + layer: LAYERS.BACKGROUND, + issuerLayer: LAYERS.BACKGROUND, + shareScope: [`application:${LAYERS.BACKGROUND}`], + }, + }, + ]); + }); + + it('inherits multiple top-level share scopes for each enabled realm', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'lynx_host', + shareScope: ['application', 'vendor'], + shared: { state: { singleton: true } }, + }, + { mainThread: true }, + ); + const config = await modifyRspackConfig({ plugins: [] }); + const options = federationOptions(config.plugins[0]); + + expect(options.shareScope).toEqual([ + `application:${LAYERS.BACKGROUND}`, + `application:${LAYERS.MAIN_THREAD}`, + `vendor:${LAYERS.BACKGROUND}`, + `vendor:${LAYERS.MAIN_THREAD}`, + ]); + expect(options.shared).toEqual([ + { + state: { + singleton: true, + layer: LAYERS.BACKGROUND, + issuerLayer: LAYERS.BACKGROUND, + shareScope: [ + `application:${LAYERS.BACKGROUND}`, + `vendor:${LAYERS.BACKGROUND}`, + ], + }, + }, + ]); + }); + + it('qualifies advanced remote share scopes for each enabled realm', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'lynx_host', + remotes: { + catalog: { + external: 'catalog@catalog.lynx.bundle', + shareScope: 'application', + }, + analytics: { + external: ['analytics@analytics.lynx.bundle'], + shareScope: ['vendor', 'application'], + }, + }, + }, + { mainThread: true }, + ); + const config = await modifyRspackConfig({ plugins: [] }); + + expect(federationOptions(config.plugins[0]).remotes).toEqual({ + catalog: { + external: 'catalog@catalog.lynx.bundle', + shareScope: [ + `application:${LAYERS.BACKGROUND}`, + `application:${LAYERS.MAIN_THREAD}`, + ], + }, + analytics: { + external: ['analytics@analytics.lynx.bundle'], + shareScope: [ + `vendor:${LAYERS.BACKGROUND}`, + `vendor:${LAYERS.MAIN_THREAD}`, + `application:${LAYERS.BACKGROUND}`, + `application:${LAYERS.MAIN_THREAD}`, + ], + }, + }); + }); + + it('lets a shared item override the top-level share scope', async () => { + const { modifyRspackConfig } = setupPlugin({ + name: 'lynx_host', + shareScope: 'application', + shared: { + state: { shareScope: 'isolated', singleton: true }, + }, + }); + const config = await modifyRspackConfig({ plugins: [] }); + const options = federationOptions(config.plugins[0]); + + expect(options.shareScope).toEqual([`application:${LAYERS.BACKGROUND}`]); + expect(options.shared).toEqual([ + { + state: { + singleton: true, + layer: LAYERS.BACKGROUND, + issuerLayer: LAYERS.BACKGROUND, + shareScope: [`isolated:${LAYERS.BACKGROUND}`], + }, + }, + ]); + }); + + it('adds the main-thread share scope only when that realm is enabled', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'lynx_host', + shared: { react: { singleton: true } }, + }, + { mainThread: true }, + ); + const config = await modifyRspackConfig({ plugins: [] }); + + expect(federationOptions(config.plugins[0])).toMatchObject({ + shareScope: [ + `default:${LAYERS.BACKGROUND}`, + `default:${LAYERS.MAIN_THREAD}`, + ], + shared: [ + { + react: { + singleton: true, + layer: LAYERS.BACKGROUND, + issuerLayer: LAYERS.BACKGROUND, + shareScope: [`default:${LAYERS.BACKGROUND}`], + }, + }, + ], + }); + }); + + it('keeps custom compiler layers separate from runtime share scopes', async () => { + const customLayer = 'custom-layer'; + const { modifyRspackConfig } = setupPlugin( + { + name: 'lynx_host', + shared: { state: { singleton: true } }, + }, + { layer: customLayer }, + ); + const config = await modifyRspackConfig({ plugins: [] }); + + expect(federationOptions(config.plugins[0])).toMatchObject({ + shareScope: [`default:${LAYERS.BACKGROUND}`], + shared: [ + { + state: { + singleton: true, + layer: customLayer, + issuerLayer: customLayer, + shareScope: [`default:${LAYERS.BACKGROUND}`], + }, + }, + ], + }); + }); + + it('rejects shared modules assigned to a disabled host realm', async () => { + const { modifyRspackConfig } = setupPlugin({ + name: 'lynx_host', + shared: { + state: { realm: 'main-thread', singleton: true }, + }, + }); + + await expect(modifyRspackConfig({ plugins: [] })).rejects.toThrow( + `shared module "state" uses inactive realm layer "${LAYERS.MAIN_THREAD}"`, + ); + }); + + it('maps semantic shared realms to exposed Lynx layers', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'lynx_host', + shared: { + 'main-thread-state': { + realm: 'main-thread', + singleton: true, + }, + }, + }, + { mainThread: true }, + ); + const config = await modifyRspackConfig({ plugins: [] }); + + expect(federationOptions(config.plugins[0]).shared).toEqual([ + { + 'main-thread-state': { + singleton: true, + layer: LAYERS.MAIN_THREAD, + issuerLayer: LAYERS.MAIN_THREAD, + shareScope: [`default:${LAYERS.MAIN_THREAD}`], + }, + }, + ]); + }); + + it('does not duplicate an explicitly issuer-layered shared entry', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'lynx_host', + shared: { + react: { + layer: LAYERS.MAIN_THREAD, + issuerLayer: LAYERS.MAIN_THREAD, + }, + }, + }, + { mainThread: true }, + ); + const config = await modifyRspackConfig({ plugins: [] }); + + expect(federationOptions(config.plugins[0]).shared).toEqual([ + { + react: { + layer: LAYERS.MAIN_THREAD, + issuerLayer: LAYERS.MAIN_THREAD, + shareScope: [`default:${LAYERS.MAIN_THREAD}`], + }, + }, + ]); + }); + + it('does not duplicate an explicit entry beside a default shared entry', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'lynx_host', + shared: { + react: { + layer: LAYERS.MAIN_THREAD, + issuerLayer: LAYERS.MAIN_THREAD, + }, + lodash: { singleton: true }, + }, + }, + { mainThread: true }, + ); + const config = await modifyRspackConfig({ plugins: [] }); + + expect(federationOptions(config.plugins[0]).shared).toEqual([ + { + react: { + layer: LAYERS.MAIN_THREAD, + issuerLayer: LAYERS.MAIN_THREAD, + shareScope: [`default:${LAYERS.MAIN_THREAD}`], + }, + lodash: { + singleton: true, + layer: LAYERS.BACKGROUND, + issuerLayer: LAYERS.BACKGROUND, + shareScope: [`default:${LAYERS.BACKGROUND}`], + }, + }, + ]); + }); + it('only applies to configured environments', async () => { + const { applyEnvironmentConfig, modifyRspackConfig } = setupPlugin( + { name: 'lynx_host' }, + { environment: 'lynx' }, + ); + const config = { plugins: [] }; + const environmentConfig = { source: { include: [] } }; + + expect(await modifyRspackConfig(config, 'web')).toBe(config); + expect(config.plugins).toHaveLength(0); + expect(applyEnvironmentConfig(environmentConfig, 'web')).toBe( + environmentConfig, + ); + expect(environmentConfig.source.include).toEqual([]); + }); + + it('fails clearly when the Lynx DSL exposes invalid layers', async () => { + const { modifyRspackConfig } = setupPlugin( + { name: 'lynx_host' }, + undefined, + { BACKGROUND: 'background' }, + ); + + await expect(modifyRspackConfig({})).rejects.toThrow( + 'distinct string `BACKGROUND` and `MAIN_THREAD` values', + ); + }); +}); diff --git a/packages/lynx/src/plugin.normalization.test.ts b/packages/lynx/src/plugin.normalization.test.ts new file mode 100644 index 00000000000..aa73e7be2a6 --- /dev/null +++ b/packages/lynx/src/plugin.normalization.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, it } from '@rstest/core'; +import { normalizeLynxExposes, normalizeLynxShared } from './plugin'; +import { LAYERS } from './plugin.testUtils'; + +describe('Lynx federation option normalization', () => { + it('defaults exposes and shared modules to the background layer', () => { + expect( + normalizeLynxExposes( + { + './Button': './src/Button', + './Card': { + import: './src/Card', + layer: 'custom-layer', + }, + }, + LAYERS.BACKGROUND, + ), + ).toEqual({ + './Button': { import: './src/Button', layer: LAYERS.BACKGROUND }, + './Card': { import: './src/Card', layer: 'custom-layer' }, + }); + + expect( + normalizeLynxShared( + { + react: '^19.0.0', + '@lynx-js/react': { + singleton: true, + layer: 'custom-layer', + }, + }, + LAYERS.BACKGROUND, + ), + ).toEqual({ + react: { + import: 'react', + requiredVersion: '^19.0.0', + layer: LAYERS.BACKGROUND, + issuerLayer: LAYERS.BACKGROUND, + }, + '@lynx-js/react': { + singleton: true, + layer: 'custom-layer', + issuerLayer: LAYERS.BACKGROUND, + }, + }); + }); + + it('preserves duplicate shared array entries and explicit layers', () => { + expect( + normalizeLynxShared( + [ + { react: '^19.0.0' }, + { + react: { + singleton: true, + layer: 'provided-layer', + issuerLayer: 'consumer-layer', + }, + }, + ], + LAYERS.BACKGROUND, + ), + ).toEqual([ + { + react: { + import: 'react', + requiredVersion: '^19.0.0', + layer: LAYERS.BACKGROUND, + issuerLayer: LAYERS.BACKGROUND, + }, + }, + { + react: { + singleton: true, + layer: 'provided-layer', + issuerLayer: 'consumer-layer', + }, + }, + ]); + }); +}); diff --git a/packages/lynx/src/plugin.remoteBundle.test.ts b/packages/lynx/src/plugin.remoteBundle.test.ts new file mode 100644 index 00000000000..ab64c3930f2 --- /dev/null +++ b/packages/lynx/src/plugin.remoteBundle.test.ts @@ -0,0 +1,756 @@ +import { LynxCacheEventsPlugin } from '@lynx-js/cache-events-webpack-plugin'; +import { describe, expect, it, rs } from '@rstest/core'; + +import type { LynxModuleFederationAdapterOptions } from './plugin'; +import { federationOptions, LAYERS, setupPlugin } from './plugin.testUtils'; + +describe('pluginLynxModuleFederation remote bundles', () => { + it('requires the ReactLynx lazy export condition for split remotes', async () => { + const resolve = rs.fn(async (request: string) => + request === '@lynx-js/react' + ? '/virtual/react/runtime/lib/index.js' + : '/virtual/react/runtime/lazy/import.js', + ); + const { modifyBundlerChain } = setupPlugin( + { name: 'catalog', exposes: { './Card': './src/Card' } }, + { remoteBundle: { target: 'lynx' } }, + LAYERS, + { resolve }, + ); + + await expect(modifyBundlerChain()).rejects.toThrow( + 'pluginReactLynx({ experimental_isLazyBundle: true })', + ); + expect(resolve).toHaveBeenCalledTimes(2); + }); + + it('accepts host-backed ReactLynx lazy exports for split remotes', async () => { + const resolve = rs.fn(async (request: string) => + request === '@lynx-js/react' + ? '/virtual/react/runtime/lazy/react.js' + : '/virtual/react/runtime/lazy/import.js', + ); + const { modifyBundlerChain } = setupPlugin( + { name: 'catalog', exposes: { './Card': './src/Card' } }, + { remoteBundle: { target: 'web' } }, + LAYERS, + { resolve }, + ); + + await expect(modifyBundlerChain('web')).resolves.toBeUndefined(); + expect(resolve).toHaveBeenCalledTimes(2); + }); + + it('allows split remotes from non-React Lynx DSL plugins', async () => { + const { modifyBundlerChain } = setupPlugin( + { name: 'catalog', exposes: { './Card': './src/Card' } }, + { remoteBundle: { target: 'lynx' } }, + ); + + await expect(modifyBundlerChain()).resolves.toBeUndefined(); + }); + + it.each([ + ['host builds', undefined, 'lynx'], + [ + 'native single remotes', + { remoteBundle: { target: 'lynx', chunking: 'single' } }, + 'lynx', + ], + [ + 'unselected environments', + { environment: 'web', remoteBundle: { target: 'web' } }, + 'lynx', + ], + ] as const)( + 'skips ReactLynx lazy-export validation for %s', + async (_name, adapterOptions, environment) => { + const resolve = rs.fn(async () => { + throw new Error('unexpected ReactLynx resolver call'); + }); + const { modifyBundlerChain } = setupPlugin( + { name: 'catalog', exposes: { './Card': './src/Card' } }, + adapterOptions as LynxModuleFederationAdapterOptions | undefined, + LAYERS, + { resolve }, + ); + + await expect(modifyBundlerChain(environment)).resolves.toBeUndefined(); + expect(resolve).not.toHaveBeenCalled(); + }, + ); + + it('builds background and main-thread containers into one external bundle', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'catalog', + exposes: { './Card': './src/Card' }, + shared: { react: { singleton: true } }, + }, + { + remoteBundle: { + target: 'web', + filename: 'catalog-custom.lynx.bundle', + engineVersion: '3.6', + }, + }, + ); + const config = await modifyRspackConfig({ plugins: [] }); + const [federationPlugin, , collector] = config.plugins; + const remoteOptions = federationOptions(federationPlugin); + + expect(remoteOptions).toMatchObject({ + name: 'catalog', + shareScope: [ + `default:${LAYERS.BACKGROUND}`, + `default:${LAYERS.MAIN_THREAD}`, + ], + filename: 'catalog.js', + manifest: true, + runtime: false, + exposes: { + './Card': { + import: './src/Card', + layer: LAYERS.BACKGROUND, + name: 'catalog__background_Card', + }, + './Card__main_thread': { + import: './src/Card', + layer: LAYERS.MAIN_THREAD, + name: 'catalog__main-thread__Card-main-thread', + }, + }, + shared: [ + { + react: { + singleton: true, + layer: LAYERS.BACKGROUND, + issuerLayer: LAYERS.BACKGROUND, + shareScope: [`default:${LAYERS.BACKGROUND}`], + }, + }, + ], + }); + + const encoder = config.plugins.find( + (plugin: any) => plugin.options?.bundleFileName, + ) as any; + + expect(config.plugins).toHaveLength(5); + expect(encoder.options).toMatchObject({ + bundleFileName: 'catalog-custom.lynx.bundle', + engineVersion: '3.6', + }); + + const PROCESS_ASSETS_STAGE_ADDITIONS = -100; + const PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE = 400; + const processAssets = new Map void>(); + let onCompilation: ((compilation: any) => void) | undefined; + collector.apply({ + webpack: { + Compilation: { + PROCESS_ASSETS_STAGE_ADDITIONS, + PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE, + }, + sources: { + ConcatSource: class { + private readonly parts: Array; + + constructor(...parts: Array) { + this.parts = parts; + } + + source() { + return this.parts + .map((part) => + typeof part === 'string' ? part : part.source(), + ) + .join(''); + } + }, + }, + }, + hooks: { + thisCompilation: { + tap(_name: string, callback: (compilation: any) => void) { + onCompilation = callback; + }, + }, + }, + } as any); + const emitAsset = rs.fn(); + const updateAsset = rs.fn(); + let containerAsset = { + source: { source: () => 'container' }, + info: {}, + }; + const nestedBackgroundChunk = { + name: 'nested-activity-metadata', + files: new Set(['nested-activity-metadata.js']), + layers: [LAYERS.BACKGROUND], + getAllAsyncChunks: () => new Set(), + }; + const compilation = { + chunks: [ + { + name: 'catalog', + files: new Set(['catalog.js']), + layers: [LAYERS.BACKGROUND, LAYERS.MAIN_THREAD], + getAllAsyncChunks: () => new Set(), + }, + { + name: 'catalog__main-thread__Card', + files: new Set([ + 'catalog__main-thread__Card-main-thread.js', + 'styles.css', + ]), + layers: [LAYERS.MAIN_THREAD], + getAllAsyncChunks: () => new Set(), + }, + { + name: 'catalog__background_Card', + files: new Set(['catalog__background_Card.js']), + layers: [LAYERS.BACKGROUND], + getAllAsyncChunks: () => new Set([nestedBackgroundChunk]), + }, + nestedBackgroundChunk, + ], + chunkGraph: { + getChunkModulesIterable(chunk: { layers: string[] }) { + return chunk.layers.map((layer) => ({ layer })); + }, + }, + emitAsset, + updateAsset, + getAsset(name: string) { + if (name === 'catalog.js') { + return containerAsset; + } + if (name === 'catalog__background_Card.js') { + return { + source: { source: () => 'exports.ids = ["card"];' }, + info: { minimized: true }, + }; + } + if (name === 'nested-activity-metadata.js') { + return { + source: { source: () => 'exports.ids = ["metadata"];' }, + info: { minimized: true }, + }; + } + return undefined; + }, + hooks: { + processAssets: { + tap(options: { stage: number }, callback: () => void) { + processAssets.set(options.stage, callback); + }, + }, + }, + }; + onCompilation!(compilation); + const backgroundIdentityStage = PROCESS_ASSETS_STAGE_ADDITIONS + 1; + const pairedBundleChunksStage = PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE + 2; + processAssets.get(backgroundIdentityStage)!(); + containerAsset = { + source: { source: () => 'tt.define("catalog.js", container);' }, + info: {}, + }; + processAssets.get(pairedBundleChunksStage)!(); + + expect([...processAssets.keys()]).toEqual([ + backgroundIdentityStage, + pairedBundleChunksStage, + ]); + expect(updateAsset).toHaveBeenCalled(); + const backgroundSource = updateAsset.mock.calls[0][1].source(); + expect(backgroundSource).toContain( + 'exports.__lynx_dynamic_component_entry__ = globDynamicComponentEntry;', + ); + expect(updateAsset).toHaveBeenCalledWith( + 'catalog__background_Card.js', + expect.any(Object), + { minimized: true }, + ); + expect(updateAsset).toHaveBeenCalledWith( + 'nested-activity-metadata.js', + expect.any(Object), + { minimized: true }, + ); + expect( + encoder.options.stateStore.for(compilation).pairedBundleChunks, + ).toEqual( + new Set([ + 'catalog__main-thread.js', + 'catalog__main-thread__Card-main-thread.js', + 'catalog__background_Card.js', + 'nested-activity-metadata.js', + ]), + ); + expect(emitAsset).toHaveBeenCalledWith( + 'catalog__main-thread.js', + expect.any(Object), + { 'lynx:main-thread': true }, + ); + const mainThreadContainerSource = emitAsset.mock.calls[0][1].source(); + expect(mainThreadContainerSource).toContain( + 'var module = { exports: {} };', + ); + expect(mainThreadContainerSource).not.toContain('tt.define('); + expect(mainThreadContainerSource).toContain( + 'globalThis.processEvalResultByHost', + ); + expect(mainThreadContainerSource).toContain('__webpack_require__.C(chunk)'); + }); + + it('configures cache events through the public chain slot for remote bundles', async () => { + const { bundlerPluginUses, modifyBundlerChain } = setupPlugin( + { name: 'catalog', exposes: { './Card': './src/Card' } }, + { remoteBundle: { target: 'web' } }, + ); + + await modifyBundlerChain('web'); + + const cacheEventsUse = bundlerPluginUses.get('lynx:cache-events')!; + expect(cacheEventsUse).toEqual([ + LynxCacheEventsPlugin, + [{ setupListTransformer: expect.any(Function) }], + ]); + expect(cacheEventsUse[1][0].setupListTransformer(['event'])).toEqual([]); + }); + + it('replaces the late Rspeedy cache-events plugin for remote bundles', async () => { + const { modifyRspackConfig } = setupPlugin( + { name: 'catalog', exposes: { './Card': './src/Card' } }, + { remoteBundle: { target: 'web' } }, + ); + const original = new LynxCacheEventsPlugin(); + + const config = await modifyRspackConfig({ plugins: [original] }, 'web'); + const configured = config.plugins.find( + (plugin: unknown) => plugin instanceof LynxCacheEventsPlugin, + ) as unknown as { + options: { setupListTransformer(setups: string[]): string[] }; + }; + + expect(configured).not.toBe(original); + expect(configured.options.setupListTransformer(['event'])).toEqual([]); + }); + + it('does not override cache events for hosts', async () => { + const { bundlerPluginUses, modifyBundlerChain } = setupPlugin({ + name: 'host', + exposes: { './Card': './src/Card' }, + }); + + await modifyBundlerChain('web'); + + expect(bundlerPluginUses.has('lynx:cache-events')).toBe(false); + }); + + it('builds a paired native remote with the official TASM encoder', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'catalog', + exposes: { './Card': './src/Card' }, + shared: { '@lynx-js/react': { singleton: true } }, + }, + { + remoteBundle: { + target: 'lynx', + filename: 'catalog-native.lynx.bundle', + engineVersion: '3.6', + }, + }, + ); + const config = await modifyRspackConfig({ plugins: [] }); + const remoteOptions = federationOptions(config.plugins[0]); + const encoder = config.plugins.find( + (plugin: any) => plugin.options?.bundleFileName, + ) as any; + + expect(config.plugins).toHaveLength(5); + expect(remoteOptions).toMatchObject({ + name: 'catalog', + shareScope: [ + `default:${LAYERS.BACKGROUND}`, + `default:${LAYERS.MAIN_THREAD}`, + ], + filename: 'catalog.js', + manifest: true, + runtime: false, + exposes: { + './Card': { + import: './src/Card', + layer: LAYERS.BACKGROUND, + name: 'catalog__background_Card', + }, + './Card__main_thread': { + import: './src/Card', + layer: LAYERS.MAIN_THREAD, + name: 'catalog__main-thread__Card-main-thread', + }, + }, + shared: [ + { + '@lynx-js/react': { + singleton: true, + layer: LAYERS.BACKGROUND, + issuerLayer: LAYERS.BACKGROUND, + shareScope: [`default:${LAYERS.BACKGROUND}`], + }, + }, + ], + }); + expect(encoder.options).toMatchObject({ + bundleFileName: 'catalog-native.lynx.bundle', + engineVersion: '3.6', + entryAssets: ['catalog.js', 'catalog__main-thread.js'], + stateStore: { for: expect.any(Function) }, + }); + expect(encoder.options.encode).toBeTypeOf('function'); + const { buffer } = await encoder.options.encode({ + compilerOptions: { + enableFiberArch: true, + useLepusNG: true, + targetSdkVersion: '3.5', + enableCSSInvalidation: true, + enableCSSSelector: true, + }, + sourceContent: { appType: 'DynamicComponent' }, + customSections: { + catalog: { + content: 'module.exports = { get() {}, init() {} };', + }, + }, + }); + expect(Buffer.isBuffer(buffer)).toBe(true); + expect(buffer.byteLength).toBeGreaterThan(100); + }); + + it('derives paired chunk suffixes from custom DSL layers', async () => { + const customLayers = { + BACKGROUND: 'worker:realm', + MAIN_THREAD: 'ui:realm', + }; + const { modifyRspackConfig } = setupPlugin( + { + name: 'catalog', + exposes: { './Card': './src/Card' }, + }, + { remoteBundle: { target: 'lynx' } }, + customLayers, + ); + + const config = await modifyRspackConfig({ plugins: [] }); + expect(federationOptions(config.plugins[0]).exposes).toMatchObject({ + './Card': { + layer: customLayers.BACKGROUND, + name: 'catalog__background_Card', + }, + './Card__main_thread': { + layer: customLayers.MAIN_THREAD, + name: 'catalog__main-thread__Card-ui__realm', + }, + }); + }); + + it('inherits a custom top-level share scope for remote bundle shares', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'catalog', + exposes: { './data': './src/data' }, + shareScope: 'application', + shared: { state: { singleton: true } }, + }, + { remoteBundle: { target: 'lynx', chunking: 'single' } }, + ); + const config = await modifyRspackConfig({ plugins: [] }); + const remoteOptions = federationOptions(config.plugins[0]); + + expect(remoteOptions.shareScope).toEqual([ + `application:${LAYERS.BACKGROUND}`, + ]); + expect(remoteOptions.shared).toEqual([ + { + state: { + singleton: true, + layer: LAYERS.BACKGROUND, + issuerLayer: LAYERS.BACKGROUND, + shareScope: [`application:${LAYERS.BACKGROUND}`], + }, + }, + ]); + }); + + it('keeps native single bundles background-only', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'catalog', + exposes: { './data': './src/data' }, + shared: { state: { singleton: true } }, + }, + { remoteBundle: { target: 'lynx', chunking: 'single' } }, + ); + const config = await modifyRspackConfig({ plugins: [] }); + const remoteOptions = federationOptions(config.plugins[0]); + + expect(config.plugins).toHaveLength(4); + expect(remoteOptions.shareScope).toEqual([`default:${LAYERS.BACKGROUND}`]); + expect(remoteOptions.exposes).toEqual({ + './data': { + import: './src/data', + layer: LAYERS.BACKGROUND, + name: 'catalog__background_data', + }, + }); + expect(remoteOptions.shared).toEqual([ + { + state: { + singleton: true, + layer: LAYERS.BACKGROUND, + issuerLayer: LAYERS.BACKGROUND, + shareScope: [`default:${LAYERS.BACKGROUND}`], + }, + }, + ]); + }); + + it('rejects main-thread shares in native single bundles', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'catalog', + exposes: { './data': './src/data' }, + shared: { + state: { realm: 'main-thread', singleton: true }, + }, + }, + { remoteBundle: { target: 'lynx', chunking: 'single' } }, + ); + + await expect(modifyRspackConfig({ plugins: [] })).rejects.toThrow( + `shared module "state" uses inactive realm layer "${LAYERS.MAIN_THREAD}"`, + ); + }); + + it('preserves manifest file customization', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'catalog', + exposes: { './Card': './src/Card' }, + manifest: { + fileName: 'catalog-manifest.json', + }, + }, + { + remoteBundle: { + target: 'web', + filename: 'catalog.lynx.bundle', + }, + }, + ); + const config = await modifyRspackConfig({ plugins: [] }); + const remoteOptions = federationOptions(config.plugins[0]); + expect(remoteOptions.manifest).toEqual({ + fileName: 'catalog-manifest.json', + }); + expect(config.plugins).toHaveLength(5); + }); + + it.each([ + ['catalog', 'catalog'], + ['@scope/catalog', '@scope_catalog'], + ['teams\\catalog', 'teams_catalog'], + ['catalog?blue', 'catalog_blue'], + ['catalog#blue', 'catalog_blue'], + ['teams:catalog', 'teams_catalog'], + ['___catalog___', 'catalog'], + ['___', 'remote'], + ['CON', '_CON'], + ])( + 'derives safe remote bundle artifacts from federation name %s', + async (name, outputName) => { + const { modifyRspackConfig } = setupPlugin( + { name, exposes: { './Card': './src/Card' } }, + { remoteBundle: { target: 'web' } }, + ); + const config = await modifyRspackConfig({ plugins: [] }); + const remoteOptions = federationOptions(config.plugins[0]); + const encoder = config.plugins.find( + (plugin: any) => plugin.options?.bundleFileName, + ) as any; + + expect(remoteOptions).toMatchObject({ + name, + filename: `${outputName}.js`, + }); + expect(encoder.options).toMatchObject({ + bundleFileName: `${outputName}.lynx.bundle`, + entryAssets: [`${outputName}.js`, `${outputName}__main-thread.js`], + includedChunkPrefixes: [ + `${outputName}__background_`, + `${outputName}__main-thread__`, + ], + }); + }, + ); + + it('rejects nested remote bundle filenames', async () => { + const { modifyRspackConfig } = setupPlugin( + { name: 'catalog', exposes: { './Card': './src/Card' } }, + { + remoteBundle: { + target: 'web', + filename: 'remotes/catalog.lynx.bundle', + }, + }, + ); + + await expect(modifyRspackConfig({ plugins: [] })).rejects.toThrow( + 'must be a basename without path separators', + ); + }); + + it.each([ + [ + { + name: 'catalog', + exposes: { './Card': './src/Card' }, + filename: 'x.js', + }, + 'remove `options.filename`', + ], + [ + { name: 'catalog', exposes: { './Card': './src/Card' }, runtime: 'x' }, + 'remove `options.runtime`', + ], + [ + { + name: 'catalog', + exposes: { './Card': './src/Card' }, + manifest: false, + }, + 'requires the Module Federation manifest', + ], + [ + { + name: 'catalog', + exposes: { + './Card': { import: './src/Card', layer: LAYERS.MAIN_THREAD }, + }, + }, + 'owns expose layers', + ], + [ + { + name: 'catalog', + exposes: { + './Card__main_thread': './src/Card', + }, + }, + 'reserves expose keys ending in "__main_thread"', + ], + ])('rejects conflicting remote bundle options', async (options, message) => { + const { modifyRspackConfig } = setupPlugin(options as any, { + remoteBundle: { target: 'web' }, + }); + + await expect(modifyRspackConfig({ plugins: [] })).rejects.toThrow(message); + }); + + it('reserves internal main-thread expose keys for native remotes', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'catalog', + exposes: { './Card__main_thread': './src/Card' }, + }, + { remoteBundle: { target: 'lynx' } }, + ); + + await expect(modifyRspackConfig({ plugins: [] })).rejects.toThrow( + 'reserves expose keys ending in "__main_thread"', + ); + }); + + it('requires exposes for a remote bundle', async () => { + const { modifyRspackConfig } = setupPlugin( + { name: 'catalog' }, + { remoteBundle: { target: 'web' } }, + ); + + await expect(modifyRspackConfig({ plugins: [] })).rejects.toThrow( + 'requires at least one expose', + ); + }); + + it('rejects expose keys that collide after chunk-name sanitization', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'catalog', + exposes: { + './profile/card': './src/Card', + './profile?card': './src/OtherCard', + }, + }, + { remoteBundle: { target: 'web' } }, + ); + + await expect(modifyRspackConfig({ plugins: [] })).rejects.toThrow( + 'both map to chunk name "profile_card"', + ); + }); + + it('rejects unknown remote bundle targets', async () => { + const { modifyRspackConfig } = setupPlugin( + { name: 'catalog', exposes: { './Card': './src/Card' } }, + { + remoteBundle: { target: 'native' } as any, + }, + ); + + await expect(modifyRspackConfig({ plugins: [] })).rejects.toThrow( + 'must be either `"lynx"` or `"web"`', + ); + }); + + it('rejects atomic web bundles because exposures require paired lazy roots', async () => { + const { modifyRspackConfig } = setupPlugin( + { name: 'catalog', exposes: { './Card': './src/Card' } }, + { + remoteBundle: { target: 'web', chunking: 'single' } as any, + }, + ); + + await expect(modifyRspackConfig({ plugins: [] })).rejects.toThrow( + 'one external bundle has only one main-thread root', + ); + }); + + it('rejects remote bundle filenames that the runtime cannot recognize', async () => { + const { modifyRspackConfig } = setupPlugin( + { name: 'catalog', exposes: { './Card': './src/Card' } }, + { + remoteBundle: { target: 'web', filename: 'catalog.bin' }, + }, + ); + + await expect(modifyRspackConfig({ plugins: [] })).rejects.toThrow( + 'must end with `.lynx.bundle`', + ); + }); + + it('rejects remote bundle library types that contradict the manifest', async () => { + const { modifyRspackConfig } = setupPlugin( + { + name: 'catalog', + exposes: { './Card': './src/Card' }, + library: { type: 'module' }, + }, + { remoteBundle: { target: 'web' } }, + ); + + await expect(modifyRspackConfig({ plugins: [] })).rejects.toThrow( + 'requires `library.type: "commonjs-module"`', + ); + }); +}); diff --git a/packages/lynx/src/plugin.testUtils.ts b/packages/lynx/src/plugin.testUtils.ts new file mode 100644 index 00000000000..85d6ab9ad72 --- /dev/null +++ b/packages/lynx/src/plugin.testUtils.ts @@ -0,0 +1,84 @@ +import { rs } from '@rstest/core'; + +import { pluginLynxModuleFederation } from './plugin'; +import type { + LynxModuleFederationAdapterOptions, + LynxModuleFederationOptions, +} from './plugin'; + +export const LAYERS = { + BACKGROUND: 'background', + MAIN_THREAD: 'main-thread', +}; + +type ModifyRspackConfig = (config: any, context: any) => any; +type ModifyEnvironmentConfig = (config: any, context: any) => any; +type ModifyBundlerChain = (chain: any, context: any) => any; +type BundlerChainPluginUse = [ + unknown, + Array<{ + setupListTransformer: (setupList: unknown[]) => unknown; + }>, +]; +type ReactResolver = { + resolve(request: string): Promise; +}; + +export const setupPlugin = ( + options: LynxModuleFederationOptions, + adapterOptions?: LynxModuleFederationAdapterOptions, + layers: unknown = LAYERS, + reactResolver?: ReactResolver, +) => { + let modifyRspackConfig: ModifyRspackConfig | undefined; + let modifyEnvironmentConfigCallback: ModifyEnvironmentConfig | undefined; + let modifyBundlerChainCallback: ModifyBundlerChain | undefined; + const bundlerPluginUses = new Map(); + const chain = { + plugin(name: string) { + return { + use(Plugin: unknown, args: BundlerChainPluginUse[1]) { + bundlerPluginUses.set(name, [Plugin, args]); + }, + }; + }, + }; + const modifyEnvironmentConfig = rs.fn((callback: ModifyEnvironmentConfig) => { + modifyEnvironmentConfigCallback = callback; + }); + const plugin = pluginLynxModuleFederation(options, adapterOptions); + + plugin.setup!({ + modifyEnvironmentConfig, + modifyBundlerChain(callback: ModifyBundlerChain) { + modifyBundlerChainCallback = callback; + }, + modifyRspackConfig(callback: ModifyRspackConfig) { + modifyRspackConfig = callback; + }, + useExposed(symbol: symbol) { + if (symbol === Symbol.for('LAYERS')) { + return layers; + } + if (symbol === Symbol.for('@lynx-js/react/internal:resolve')) { + return reactResolver; + } + return undefined; + }, + } as any); + + return { + modifyEnvironmentConfig, + bundlerPluginUses, + applyEnvironmentConfig: (config: any, environment = 'lynx') => + modifyEnvironmentConfigCallback!(config, { name: environment }), + modifyBundlerChain: (environment = 'lynx') => + modifyBundlerChainCallback!(chain, { + environment: { name: environment }, + }), + modifyRspackConfig: (config: any, environment = 'lynx') => + modifyRspackConfig!(config, { environment: { name: environment } }), + }; +}; + +export const federationOptions = (plugin: unknown) => (plugin as any)._options; diff --git a/packages/lynx/src/plugin.ts b/packages/lynx/src/plugin.ts new file mode 100644 index 00000000000..4461522007f --- /dev/null +++ b/packages/lynx/src/plugin.ts @@ -0,0 +1,228 @@ +import { dirname } from 'node:path'; + +import type { Configuration } from '@rspack/core'; +import type { RsbuildPlugin } from '@rsbuild/core'; + +import { + createLynxChunkLoadingMatcherPlugin, + type LynxTemplatePluginApi, +} from './chunkLoadingMatcher'; +import { createCompilerModuleFederationPlugin } from './compilerFederation'; +import { + createFederationOptions, + getLynxShareScopes, + getRemoteBundleOptions, + injectRuntimePlugin, + normalizeLynxExposes, + normalizeLynxShared, + normalizeRealmScopedRemotes, + normalizeRealmScopedShared, + resolveRuntimePluginOptions, + shouldApplyToEnvironment, + validateLayers, + type ExposedLayers, + type LynxModuleFederationAdapterOptions, + type LynxModuleFederationOptions, +} from './pluginOptions'; +import { configureRemoteBundle } from './remoteBundle'; +import type { LynxRuntimePluginOptions } from './runtimeCore'; + +export const LYNX_RUNTIME_PLUGIN = '@module-federation/lynx/runtimePlugin'; +export const LYNX_REACT_RUNTIME_PLUGIN = + '@module-federation/lynx/reactRuntimePlugin'; + +export { normalizeLynxExposes, normalizeLynxShared }; +export type { + LynxModuleFederationAdapterOptions, + LynxModuleFederationOptions, + LynxNativeRemoteBundleOptions, + LynxRemoteBundleOptions, + LynxShared, + LynxSharedConfig, + LynxSharedRealm, + LynxWebRemoteBundleOptions, +} from './pluginOptions'; +export type { LynxRuntimePluginOptions } from './runtimeCore'; + +export const pluginLynxModuleFederation = ( + options: LynxModuleFederationOptions, + adapterOptions: LynxModuleFederationAdapterOptions = {}, +): RsbuildPlugin => { + if (!options?.name) { + throw new Error( + 'The module federation option "name" is required in @module-federation/lynx.', + ); + } + + return { + name: 'module-federation:lynx', + setup(api) { + api.modifyEnvironmentConfig((config, { name }) => { + if (!shouldApplyToEnvironment(adapterOptions.environment, name)) { + return config; + } + config.source.include = [ + ...(config.source.include || []), + /@module-federation[\\/]/, + ]; + return config; + }); + + api.modifyBundlerChain(async (chain, { environment }) => { + const remoteBundle = getRemoteBundleOptions(adapterOptions); + if ( + !remoteBundle || + !shouldApplyToEnvironment( + adapterOptions.environment, + environment.name, + ) + ) { + return; + } + + const { LynxCacheEventsPlugin } = + await import('@lynx-js/cache-events-webpack-plugin'); + chain + .plugin('lynx:cache-events') + .use(LynxCacheEventsPlugin, [{ setupListTransformer: () => [] }]); + + if (remoteBundle.chunking === 'single') { + return; + } + + const reactResolver = api.useExposed<{ + resolve(request: string): Promise; + }>(Symbol.for('@lynx-js/react/internal:resolve')); + if (!reactResolver) { + return; + } + + const [reactEntry, lazyImportEntry] = await Promise.all([ + reactResolver.resolve('@lynx-js/react'), + reactResolver.resolve('@lynx-js/react/experimental/lazy/import'), + ]); + if (dirname(reactEntry) !== dirname(lazyImportEntry)) { + throw new Error( + '@module-federation/lynx split ReactLynx remote bundles require `pluginReactLynx({ experimental_isLazyBundle: true })` so exposed components use the host-backed ReactLynx lazy runtime.', + ); + } + }); + + api.modifyRspackConfig(async (config, { environment }) => { + if ( + !shouldApplyToEnvironment( + adapterOptions.environment, + environment.name, + ) + ) { + return config; + } + + const layers = validateLayers( + api.useExposed(Symbol.for('LAYERS')), + ); + const defaultLayer = adapterOptions.layer ?? layers.BACKGROUND; + const runtimePlugin = + adapterOptions.runtimePlugin ?? LYNX_RUNTIME_PLUGIN; + const hasReactLynx = Boolean( + api.useExposed(Symbol.for('@lynx-js/react/internal:resolve')), + ); + const federationOptions = hasReactLynx + ? { + ...options, + runtimePlugins: injectRuntimePlugin( + options.runtimePlugins, + LYNX_REACT_RUNTIME_PLUGIN, + undefined, + 'prepend', + ), + } + : options; + const lynxTemplatePlugin = api.useExposed<{ + LynxTemplatePlugin: LynxTemplatePluginApi; + }>(Symbol.for('LynxTemplatePlugin'))?.LynxTemplatePlugin; + const remoteBundle = getRemoteBundleOptions(adapterOptions); + const mainThreadEnabled = + Boolean(adapterOptions.mainThread) || remoteBundle?.target === 'web'; + const activeRealmLayers = mainThreadEnabled + ? [layers.BACKGROUND, layers.MAIN_THREAD] + : [layers.BACKGROUND]; + + config.output ||= {}; + config.output.chunkLoading ??= 'lynx'; + config.output.chunkFormat ??= 'commonjs'; + config.output.iife ??= false; + config.output.uniqueName ??= options.name; + config.experiments ||= {}; + const experiments = config.experiments as typeof config.experiments & { + layers?: boolean; + }; + experiments.layers ??= true; + + if (remoteBundle) { + const { LynxCacheEventsPlugin } = + await import('@lynx-js/cache-events-webpack-plugin'); + config.plugins = config.plugins?.map((plugin) => + plugin && + typeof plugin === 'object' && + plugin.constructor.name === LynxCacheEventsPlugin.name + ? new LynxCacheEventsPlugin({ setupListTransformer: () => [] }) + : plugin, + ); + await configureRemoteBundle( + config as unknown as Configuration, + federationOptions, + adapterOptions, + layers, + remoteBundle, + runtimePlugin, + lynxTemplatePlugin, + ); + return config; + } + + const normalizedFederationOptions = createFederationOptions( + { + ...federationOptions, + remotes: normalizeRealmScopedRemotes( + federationOptions.remotes, + layers, + activeRealmLayers, + ), + shareScope: getLynxShareScopes( + federationOptions.shareScope, + layers, + activeRealmLayers, + ), + }, + normalizeLynxExposes(options.exposes, defaultLayer), + normalizeRealmScopedShared( + options.shared, + layers, + defaultLayer, + activeRealmLayers, + options.shareScope, + ), + runtimePlugin, + resolveRuntimePluginOptions( + adapterOptions.runtimePluginOptions, + layers, + ), + ); + + config.plugins ||= []; + config.plugins.push( + createCompilerModuleFederationPlugin(normalizedFederationOptions), + createLynxChunkLoadingMatcherPlugin(lynxTemplatePlugin, { + pairedRealmChunkSuffixes: { + background: `-${layers.BACKGROUND.replace(/:/g, '__')}`, + mainThread: `-${layers.MAIN_THREAD.replace(/:/g, '__')}`, + }, + }), + ); + + return config; + }); + }, + }; +}; diff --git a/packages/lynx/src/pluginOptions.ts b/packages/lynx/src/pluginOptions.ts new file mode 100644 index 00000000000..53b26758835 --- /dev/null +++ b/packages/lynx/src/pluginOptions.ts @@ -0,0 +1,440 @@ +import { isRequiredVersion } from '@module-federation/sdk'; +import type { + Exposes, + ExposesConfig, + ModuleFederationPluginOptions, + Shared, + SharedConfig, +} from '@rspack/core'; + +import type { LynxRuntimePluginOptions } from './runtimeCore'; + +export interface ExposedLayers { + BACKGROUND: string; + MAIN_THREAD: string; +} + +interface LynxRemoteBundleBaseOptions { + /** Output basename ending in `.lynx.bundle`. Defaults to `.lynx.bundle`. */ + filename?: string; + /** Lynx engine version passed to the external bundle encoder. Defaults to `3.7`. */ + engineVersion?: string; + /** + * Keep ordinary Rspeedy entry bundles beside federation artifacts. Disable + * only for a dedicated remote environment whose source entry is disposable. + * + * @defaultValue `true` + */ + preserveSourceEntryBundles?: boolean; +} + +export interface LynxNativeRemoteBundleOptions extends LynxRemoteBundleBaseOptions { + /** Emit native Lynx bundles using `@lynx-js/tasm`. */ + target: 'lynx'; + /** + * Keep paired UI bundles separate (`split`) or embed background-only module + * chunks in the container (`single`). ReactLynx UI exposures require split. + * + * @defaultValue `'split'` + */ + chunking?: 'split' | 'single'; +} + +export interface LynxWebRemoteBundleOptions extends LynxRemoteBundleBaseOptions { + /** Emit a Lynx for Web bundle containing background and main-thread sections. */ + target: 'web'; + /** Paired ReactLynx exposure roots require independently loadable bundles. */ + chunking?: 'split'; +} + +export type LynxRemoteBundleOptions = + | LynxNativeRemoteBundleOptions + | LynxWebRemoteBundleOptions; + +export type LynxSharedRealm = 'background' | 'main-thread'; + +export interface LynxSharedConfig extends SharedConfig { + /** Select the Lynx JavaScript realm without depending on DSL layer names. */ + realm?: LynxSharedRealm; +} + +export type LynxShared = + | string + | Record + | Array>; + +export type LynxModuleFederationOptions = Omit< + ModuleFederationPluginOptions, + 'shared' +> & { + shared?: LynxShared; +}; + +export interface LynxModuleFederationAdapterOptions { + /** Only apply federation to these Rsbuild environments. */ + environment?: string | string[]; + /** Default Rspack layer for exposes and shared modules. */ + layer?: string; + /** Emit a manifest-addressable native or web external bundle. */ + remoteBundle?: LynxRemoteBundleOptions; + /** Enable main-thread federation. */ + mainThread?: boolean; + /** Override the runtime transport plugin module. */ + runtimePlugin?: string; + /** Options passed to the Lynx runtime transport plugin. */ + runtimePluginOptions?: LynxRuntimePluginOptions; +} + +export const shouldApplyToEnvironment = ( + configuredEnvironment: string | string[] | undefined, + environmentName: string, +): boolean => { + if (!configuredEnvironment) { + return true; + } + + return Array.isArray(configuredEnvironment) + ? configuredEnvironment.includes(environmentName) + : configuredEnvironment === environmentName; +}; + +export const validateLayers = ( + layers: ExposedLayers | undefined, +): ExposedLayers => { + if ( + !layers || + typeof layers.BACKGROUND !== 'string' || + !layers.BACKGROUND || + typeof layers.MAIN_THREAD !== 'string' || + !layers.MAIN_THREAD || + layers.BACKGROUND === layers.MAIN_THREAD + ) { + throw new Error( + '@module-federation/lynx requires exposed `LAYERS` with distinct string `BACKGROUND` and `MAIN_THREAD` values. Install a Lynx DSL plugin such as `pluginReactLynx` before it.', + ); + } + + return layers; +}; + +export const normalizeLynxExposes = ( + exposes: Exposes | undefined, + defaultLayer: string, +): Exposes | undefined => { + if (!exposes) { + return undefined; + } + + const normalized: Record = {}; + for (const item of Array.isArray(exposes) ? exposes : [exposes]) { + if (typeof item === 'string') { + normalized[item] = { import: item, layer: defaultLayer }; + continue; + } + + for (const [key, value] of Object.entries(item)) { + normalized[key] = + typeof value === 'string' || Array.isArray(value) + ? { import: value, layer: defaultLayer } + : { ...value, layer: value.layer ?? defaultLayer }; + } + } + + return normalized; +}; + +type SharedNormalizationContext = + | { + defaultLayer: string; + isolateShareScope: false; + layers?: ExposedLayers; + } + | { + activeRealmLayers: readonly string[]; + defaultLayer: string; + isolateShareScope: true; + layers: ExposedLayers; + shareScope: ModuleFederationPluginOptions['shareScope']; + }; + +const getRealmLayer = ( + realm: LynxSharedConfig['realm'], + layers: ExposedLayers | undefined, +): string | undefined => { + if (realm === 'background') return layers?.BACKGROUND; + if (realm === 'main-thread') return layers?.MAIN_THREAD; + return undefined; +}; + +const getShareScopeLayer = ( + realmLayer: string | undefined, + issuerLayer: string, + layers: ExposedLayers, +): string => { + if (realmLayer) return realmLayer; + if (issuerLayer === layers.MAIN_THREAD) return layers.MAIN_THREAD; + if (issuerLayer === layers.BACKGROUND) return layers.BACKGROUND; + return layers.BACKGROUND; +}; + +const normalizeSharedValue = ( + key: string, + value: string | LynxSharedConfig, + context: SharedNormalizationContext, +): SharedConfig => { + const config: LynxSharedConfig = + typeof value === 'string' + ? value === key || !isRequiredVersion(value) + ? { import: value } + : { import: key, requiredVersion: value } + : value; + const { realm, ...sharedConfig } = config; + const realmLayer = getRealmLayer(realm, context.layers); + if (realm && !realmLayer) { + throw new Error( + `@module-federation/lynx cannot resolve shared realm "${realm}" without exposed Lynx layers.`, + ); + } + if ( + realmLayer && + ((sharedConfig.layer && sharedConfig.layer !== realmLayer) || + (sharedConfig.issuerLayer && sharedConfig.issuerLayer !== realmLayer)) + ) { + throw new Error( + `@module-federation/lynx shared module "${key}" cannot combine realm "${realm}" with a different layer or issuerLayer.`, + ); + } + const layer = sharedConfig.layer ?? realmLayer ?? context.defaultLayer; + const issuerLayer = + sharedConfig.issuerLayer ?? realmLayer ?? context.defaultLayer; + let scopedShareScopes: string[] | undefined; + if (context.isolateShareScope) { + const shareScopeLayer = getShareScopeLayer( + realmLayer, + issuerLayer, + context.layers, + ); + if (!context.activeRealmLayers.includes(shareScopeLayer)) { + throw new Error( + `@module-federation/lynx shared module "${key}" uses inactive realm layer "${shareScopeLayer}". Enable its Lynx realm or choose one of: ${context.activeRealmLayers.join(', ')}.`, + ); + } + const shareScope = config.shareScope ?? context.shareScope ?? 'default'; + scopedShareScopes = ( + Array.isArray(shareScope) ? shareScope : [shareScope] + ).map((scope) => `${scope}:${shareScopeLayer}`); + } + + return { + ...sharedConfig, + layer, + issuerLayer, + ...(scopedShareScopes ? { shareScope: scopedShareScopes } : {}), + }; +}; + +const normalizeSharedItem = ( + item: string | Record, + context: SharedNormalizationContext, +): Record => { + if (typeof item === 'string') { + return { + [item]: normalizeSharedValue(item, item, context), + }; + } + + return Object.fromEntries( + Object.entries(item).map(([key, value]) => [ + key, + normalizeSharedValue(key, value, context), + ]), + ); +}; + +export const normalizeLynxShared = ( + shared: LynxShared | undefined, + defaultLayer: string, + layers?: ExposedLayers, +): Shared | undefined => { + if (!shared) { + return undefined; + } + + const context: SharedNormalizationContext = { + defaultLayer, + isolateShareScope: false, + layers, + }; + return Array.isArray(shared) + ? shared.map((item) => normalizeSharedItem(item, context)) + : normalizeSharedItem(shared, context); +}; + +export const normalizeRealmScopedShared = ( + shared: LynxShared | undefined, + layers: ExposedLayers, + defaultLayer = layers.BACKGROUND, + activeRealmLayers: readonly string[] = [ + layers.BACKGROUND, + layers.MAIN_THREAD, + ], + shareScope?: ModuleFederationPluginOptions['shareScope'], +): Shared | undefined => { + if (!shared) { + return undefined; + } + + const items = Array.isArray(shared) ? shared : [shared]; + const context: SharedNormalizationContext = { + activeRealmLayers, + defaultLayer, + isolateShareScope: true, + layers, + shareScope, + }; + return items.map((item) => normalizeSharedItem(item, context)); +}; + +export const getLynxShareScopes = ( + shareScope: ModuleFederationPluginOptions['shareScope'], + layers: ExposedLayers, + activeRealmLayers: readonly string[] = [ + layers.BACKGROUND, + layers.MAIN_THREAD, + ], +): string[] => + (Array.isArray(shareScope) ? shareScope : [shareScope ?? 'default']).flatMap( + (scope) => activeRealmLayers.map((layer) => `${scope}:${layer}`), + ); + +type Remotes = NonNullable; +type RemotesObject = Exclude; + +const normalizeRemotesObject = ( + remotes: RemotesObject, + layers: ExposedLayers, + activeRealmLayers: readonly string[], +): RemotesObject => + Object.fromEntries( + Object.entries(remotes).map(([alias, value]) => { + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + return [alias, value]; + } + const config = value as { shareScope?: string | string[] }; + return [ + alias, + config.shareScope === undefined + ? config + : { + ...config, + shareScope: getLynxShareScopes( + config.shareScope, + layers, + activeRealmLayers, + ), + }, + ]; + }), + ) as RemotesObject; + +export const normalizeRealmScopedRemotes = ( + remotes: ModuleFederationPluginOptions['remotes'], + layers: ExposedLayers, + activeRealmLayers: readonly string[], +): ModuleFederationPluginOptions['remotes'] => { + if (!remotes) return remotes; + if (Array.isArray(remotes)) { + return remotes.map((remote) => + typeof remote === 'string' + ? remote + : normalizeRemotesObject(remote, layers, activeRealmLayers), + ); + } + return normalizeRemotesObject(remotes, layers, activeRealmLayers); +}; + +export const resolveRuntimePluginOptions = ( + options: LynxRuntimePluginOptions | undefined, + layers: ExposedLayers, +): LynxRuntimePluginOptions => ({ + ...options, + realmLayers: { + background: layers.BACKGROUND, + 'main-thread': layers.MAIN_THREAD, + }, +}); + +export const injectRuntimePlugin = ( + runtimePlugins: ModuleFederationPluginOptions['runtimePlugins'], + runtimePlugin: string, + runtimePluginOptions: LynxRuntimePluginOptions | undefined, + placement: 'append' | 'prepend' = 'append', +): NonNullable => { + const plugins = runtimePlugins ?? []; + const useTuples = + runtimePluginOptions !== undefined || plugins.some(Array.isArray); + let hasRuntimePlugin = false; + const normalizedPlugins = plugins.map((plugin) => { + const pluginPath = Array.isArray(plugin) ? plugin[0] : plugin; + if (pluginPath !== runtimePlugin) { + return useTuples && !Array.isArray(plugin) ? [plugin, {}] : plugin; + } + + hasRuntimePlugin = true; + if (!useTuples) return plugin; + const existingOptions = Array.isArray(plugin) ? plugin[1] : undefined; + return [runtimePlugin, { ...existingOptions, ...runtimePluginOptions }]; + }); + + if (hasRuntimePlugin) { + return normalizedPlugins as NonNullable< + ModuleFederationPluginOptions['runtimePlugins'] + >; + } + if (useTuples) { + const runtimePluginEntry: [string, Record] = [ + runtimePlugin, + { ...runtimePluginOptions }, + ]; + const normalizedTuplePlugins = normalizedPlugins as [ + string, + Record, + ][]; + return placement === 'prepend' + ? [runtimePluginEntry, ...normalizedTuplePlugins] + : [...normalizedTuplePlugins, runtimePluginEntry]; + } + + const normalizedStringPlugins = normalizedPlugins as string[]; + return placement === 'prepend' + ? [runtimePlugin, ...normalizedStringPlugins] + : [...normalizedStringPlugins, runtimePlugin]; +}; + +export const getRemoteBundleOptions = ( + adapterOptions: LynxModuleFederationAdapterOptions, +): LynxRemoteBundleOptions | undefined => adapterOptions.remoteBundle; + +export const createFederationOptions = ( + options: LynxModuleFederationOptions, + exposes: Exposes | undefined, + shared: Shared | undefined, + runtimePlugin: string, + runtimePluginOptions: LynxRuntimePluginOptions | undefined, +): ModuleFederationPluginOptions => ({ + ...options, + exposes, + shared, + filename: options.filename ?? 'remoteEntry.js', + library: options.library ?? { type: 'commonjs-module' }, + remoteType: options.remoteType ?? 'script', + experiments: { + ...options.experiments, + asyncStartup: options.experiments?.asyncStartup ?? true, + }, + runtimePlugins: injectRuntimePlugin( + options.runtimePlugins, + runtimePlugin, + runtimePluginOptions, + ), +}); diff --git a/packages/lynx/src/reactRuntimePlugin.ts b/packages/lynx/src/reactRuntimePlugin.ts new file mode 100644 index 00000000000..d532a5650cb --- /dev/null +++ b/packages/lynx/src/reactRuntimePlugin.ts @@ -0,0 +1,13 @@ +import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime-core/types'; +import { loadLazyBundle } from '@lynx-js/react/experimental/lazy/load'; + +import { getLynxRuntime, type LynxGlobal } from './runtimeCore'; + +export default function reactLynxRuntimePlugin(): ModuleFederationRuntimePlugin { + const lynx = getLynxRuntime(globalThis as LynxGlobal); + if (lynx) { + lynx.loadLazyBundle = loadLazyBundle; + } + + return { name: 'react-lynx-lazy-bundle-runtime-plugin' }; +} diff --git a/packages/lynx/src/remoteBundle.ts b/packages/lynx/src/remoteBundle.ts new file mode 100644 index 00000000000..01d529f6ed6 --- /dev/null +++ b/packages/lynx/src/remoteBundle.ts @@ -0,0 +1,642 @@ +import type { + Chunk, + Compilation, + Compiler, + Configuration, + Exposes, + ExposesConfig, +} from '@rspack/core'; +import { getManifestFileName } from '@module-federation/sdk'; + +import { + createLynxChunkLoadingMatcherPlugin, + type LynxTemplatePluginApi, +} from './chunkLoadingMatcher'; +import { createCompilerModuleFederationPlugin } from './compilerFederation'; +import { createLynxExternalBundlePlugin } from './externalBundle'; +import { + createFederationOptions, + getLynxShareScopes, + normalizeLynxExposes, + normalizeRealmScopedRemotes, + normalizeRealmScopedShared, + resolveRuntimePluginOptions, + type ExposedLayers, + type LynxModuleFederationAdapterOptions, + type LynxModuleFederationOptions, + type LynxRemoteBundleOptions, +} from './pluginOptions'; +import { createLynxRemoteManifestPlugin } from './remoteManifest'; +import { createRemoteBundleCompilationStateStore } from './remoteBundleCompilationState'; +import { MAIN_THREAD_EXPOSE_SUFFIX } from './runtimeCore'; +import { getLynxWebEncodeMode } from './webEncode'; + +interface RemoteBundlePlanBase { + backgroundChunkPrefix: string; + backgroundEntry: string; + bundleFileName: string; + entryAssets: string[]; + includedChunkPrefixes: string[]; +} + +type RemoteBundlePlan = RemoteBundlePlanBase & + ( + | { + chunking: 'single'; + mainThreadChunkPrefix: undefined; + mainThreadEntry: undefined; + mode: 'native-single'; + } + | { + chunking: 'split'; + mainThreadChunkPrefix: string; + mainThreadEntry: string; + mode: 'native-split'; + } + | { + chunking: 'split'; + mainThreadChunkPrefix: string; + mainThreadEntry: string; + mode: 'web-split'; + } + ); + +const toSafeOutputName = (name: string): string => { + const sanitizedName = name.replace(/[^A-Za-z0-9@_-]+/g, '_'); + let start = 0; + let end = sanitizedName.length; + while (start < end && sanitizedName[start] === '_') { + start += 1; + } + while (end > start && sanitizedName[end - 1] === '_') { + end -= 1; + } + const outputName = sanitizedName.slice(start, end) || 'remote'; + return /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])$/i.test(outputName) + ? `_${outputName}` + : outputName; +}; + +const normalizeRemoteBundlePlan = ( + name: string, + remoteBundle: LynxRemoteBundleOptions, +): RemoteBundlePlan => { + if (remoteBundle.target !== 'lynx' && remoteBundle.target !== 'web') { + throw new Error( + '@module-federation/lynx `remoteBundle.target` must be either `"lynx"` or `"web"`.', + ); + } + + const chunking = remoteBundle.chunking ?? 'split'; + if (remoteBundle.target === 'web' && chunking === 'single') { + throw new Error( + '@module-federation/lynx web remotes require `chunking: "split"`; one external bundle has only one main-thread root and cannot activate independently scoped ReactLynx exposure roots.', + ); + } + + const outputName = toSafeOutputName(name); + const backgroundEntry = `${outputName}.js`; + const backgroundChunkPrefix = `${outputName}__background_`; + const bundleFileName = remoteBundle.filename ?? `${outputName}.lynx.bundle`; + const basePlan = { + backgroundChunkPrefix, + backgroundEntry, + bundleFileName, + }; + if (chunking === 'single') { + return { + ...basePlan, + chunking, + entryAssets: [backgroundEntry], + includedChunkPrefixes: [backgroundChunkPrefix], + mainThreadChunkPrefix: undefined, + mainThreadEntry: undefined, + mode: 'native-single', + }; + } + + const mainThreadChunkPrefix = `${outputName}__main-thread__`; + const splitPlan = { + ...basePlan, + chunking, + includedChunkPrefixes: [backgroundChunkPrefix, mainThreadChunkPrefix], + mainThreadChunkPrefix, + }; + const mainThreadEntry = `${outputName}__main-thread.js`; + if (remoteBundle.target === 'web') { + return { + ...splitPlan, + entryAssets: [backgroundEntry, mainThreadEntry], + mainThreadEntry, + mode: 'web-split', + }; + } + + return { + ...splitPlan, + entryAssets: [backgroundEntry, mainThreadEntry], + mainThreadEntry, + mode: 'native-split', + }; +}; + +const hasExposes = (exposes: Exposes | undefined): exposes is Exposes => { + if (!exposes) { + return false; + } + + return Array.isArray(exposes) + ? exposes.some( + (item) => typeof item === 'string' || Object.keys(item).length > 0, + ) + : Object.keys(exposes).length > 0; +}; + +const findConflictingExposeLayer = ( + exposes: Exposes, + backgroundLayer: string, +): string | undefined => { + for (const item of Array.isArray(exposes) ? exposes : [exposes]) { + if (typeof item === 'string') { + continue; + } + + for (const value of Object.values(item)) { + if ( + value !== null && + typeof value === 'object' && + !Array.isArray(value) && + value.layer !== undefined && + value.layer !== backgroundLayer + ) { + return value.layer; + } + } + } + + return undefined; +}; + +const findReservedExposeKey = ( + exposes: Exposes, + suffix: string, +): string | undefined => { + for (const item of Array.isArray(exposes) ? exposes : [exposes]) { + const keys = typeof item === 'string' ? [item] : Object.keys(item); + const reservedKey = keys.find((key) => key.endsWith(suffix)); + if (reservedKey) { + return reservedKey; + } + } + + return undefined; +}; + +const toChunkName = (key: string): string => { + const name = key.replace(/^\.\//, '').replace(/[^A-Za-z0-9_-]+/g, '_'); + return name || 'expose'; +}; + +const assertUniqueChunkNames = (exposes: Exposes): Map => { + const normalized = normalizeLynxExposes(exposes, '') as Record< + string, + ExposesConfig + >; + const keysByChunkName = new Map(); + const chunkNamesByExpose = new Map(); + for (const key of Object.keys(normalized)) { + const chunkName = toChunkName(key); + const previousKey = keysByChunkName.get(chunkName); + if (previousKey) { + throw new Error( + `@module-federation/lynx expose keys "${previousKey}" and "${key}" both map to chunk name "${chunkName}"; rename one expose to keep lazy bundle names unique.`, + ); + } + keysByChunkName.set(chunkName, key); + chunkNamesByExpose.set(key, chunkName); + } + return chunkNamesByExpose; +}; + +const createRemoteExposes = ( + exposes: Exposes, + layer: string, + prefix: string, + keySuffix = '', + chunkNameSuffix = '', +): Record => { + const normalized = normalizeLynxExposes(exposes, layer) as Record< + string, + ExposesConfig + >; + + return Object.fromEntries( + Object.entries(normalized).map(([key, value]) => [ + `${key}${keySuffix}`, + { + ...value, + layer, + name: `${prefix}${toChunkName(key)}${chunkNameSuffix}`, + }, + ]), + ); +}; + +const isModuleInLayer = (module: unknown, layer: string): boolean => { + return (module as { layer?: string }).layer === layer; +}; + +const classifyRemoteChunk = ( + modules: Iterable, + containsBackgroundExpose: boolean, + mainThreadLayer: string, +) => ({ + containsBackgroundExpose, + containsMainThreadModule: Array.from(modules).some((module) => + isModuleInLayer(module, mainThreadLayer), + ), +}); + +const getBackgroundRemoteChunks = ( + chunks: Iterable, + backgroundChunkPrefix: string, +): ReadonlySet => { + const included = new Set(); + for (const chunk of chunks) { + if ( + typeof chunk.name !== 'string' || + !chunk.name.startsWith(backgroundChunkPrefix) + ) { + continue; + } + included.add(chunk); + for (const asyncChunk of chunk.getAllAsyncChunks()) { + included.add(asyncChunk); + } + } + return included; +}; + +const mainThreadChunkInstaller = ` +globalThis.processEvalResult = (globalThis.processEvalResultByHost || (globalThis.processEvalResultByHost = {}))[globDynamicComponentEntry] = function (result, schema) { + var chunk = result && result(schema); + if (chunk && chunk.ids && chunk.modules) { + __webpack_require__.C(chunk); + for (var moduleId in chunk.modules) { + __webpack_require__(moduleId); + } + } + return chunk; +}; +`; + +const createRemoteAssetsPlugin = ( + stateStore: ReturnType, + backgroundEntry: string, + mainThreadEntry: string | undefined, + backgroundChunkPrefix: string, + mainThreadLayer: string, +) => ({ + apply(compiler: Compiler) { + const pluginName = 'LynxModuleFederationPairedBundleChunks'; + compiler.hooks.thisCompilation.tap(pluginName, (compilation) => { + const state = stateStore.for(compilation as Compilation); + compilation.hooks.processAssets.tap( + { + name: `${pluginName}BackgroundIdentity`, + // ReactLynx adds debug metadata during ADDITIONS, then RuntimeWrapper + // closes over `exports` at stage NONE. Inject the identity between them. + stage: + compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS + 1, + }, + () => { + if (mainThreadEntry) { + const entryAsset = compilation.getAsset(backgroundEntry); + if (!entryAsset) { + throw new Error( + `@module-federation/lynx could not find generated container asset "${backgroundEntry}".`, + ); + } + compilation.emitAsset( + mainThreadEntry, + new compiler.webpack.sources.ConcatSource( + '(function () {\n', + ' var module = { exports: {} };\n', + ' var exports = module.exports;\n', + entryAsset.source, + mainThreadChunkInstaller, + '\n return module.exports;\n', + '})()', + ), + { + ...entryAsset.info, + 'lynx:main-thread': true, + }, + ); + state.pairedBundleChunks.add(mainThreadEntry); + } + + const backgroundRemoteChunks = getBackgroundRemoteChunks( + compilation.chunks, + backgroundChunkPrefix, + ); + for (const chunk of compilation.chunks) { + const { containsBackgroundExpose, containsMainThreadModule } = + classifyRemoteChunk( + compilation.chunkGraph.getChunkModulesIterable(chunk), + backgroundRemoteChunks.has(chunk), + mainThreadLayer, + ); + if (!containsBackgroundExpose || containsMainThreadModule) { + continue; + } + + for (const filename of chunk.files) { + if (filename.endsWith('.js') && filename !== backgroundEntry) { + const asset = compilation.getAsset(filename); + if (asset) { + compilation.updateAsset( + filename, + new compiler.webpack.sources.ConcatSource( + asset.source, + "\nif (typeof globDynamicComponentEntry !== 'string') { throw new Error('Lynx DynamicComponent entry identity is unavailable.'); }\n", + 'exports.__lynx_dynamic_component_entry__ = globDynamicComponentEntry;\n', + ), + asset.info, + ); + } + } + } + } + }, + ); + + compilation.hooks.processAssets.tap( + { + name: pluginName, + stage: + compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE + 2, + }, + () => { + const backgroundRemoteChunks = getBackgroundRemoteChunks( + compilation.chunks, + backgroundChunkPrefix, + ); + for (const chunk of compilation.chunks) { + const { containsBackgroundExpose, containsMainThreadModule } = + classifyRemoteChunk( + compilation.chunkGraph.getChunkModulesIterable(chunk), + backgroundRemoteChunks.has(chunk), + mainThreadLayer, + ); + if (!containsMainThreadModule && !containsBackgroundExpose) { + continue; + } + + for (const filename of chunk.files) { + if (filename.endsWith('.js') && filename !== backgroundEntry) { + state.pairedBundleChunks.add(filename); + const asset = compilation.getAsset(filename); + if (asset && containsMainThreadModule) { + compilation.updateAsset( + filename, + new compiler.webpack.sources.ConcatSource( + '(function (globDynamicComponentEntry) {\n', + ' const module = { exports: {} };\n', + ' const exports = module.exports;\n', + asset.source, + '\n module.exports.__lynx_dynamic_component_entry__ = globDynamicComponentEntry;\n', + ' return module.exports;\n})', + ), + { + ...asset.info, + 'lynx:main-thread': true, + }, + ); + } + } + } + } + }, + ); + }); + }, +}); + +export const configureRemoteBundle = async ( + config: Configuration, + options: LynxModuleFederationOptions, + adapterOptions: LynxModuleFederationAdapterOptions, + layers: ExposedLayers, + remoteBundle: LynxRemoteBundleOptions, + runtimePlugin: string, + lynxTemplatePlugin?: LynxTemplatePluginApi, +): Promise => { + const exposes = options.exposes; + if (!hasExposes(exposes)) { + throw new Error( + '@module-federation/lynx `remoteBundle` requires at least one expose.', + ); + } + const plan = normalizeRemoteBundlePlan(options.name, remoteBundle); + if ( + remoteBundle.filename !== undefined && + !remoteBundle.filename.endsWith('.lynx.bundle') + ) { + throw new Error( + '@module-federation/lynx `remoteBundle.filename` must end with `.lynx.bundle`.', + ); + } + if (remoteBundle.filename && /[\\/]/.test(remoteBundle.filename)) { + throw new Error( + '@module-federation/lynx `remoteBundle.filename` must be a basename without path separators so split lazy bundles resolve from the same output root.', + ); + } + if (options.filename !== undefined) { + throw new Error( + '@module-federation/lynx `remoteBundle` manages container filenames; remove `options.filename`.', + ); + } + if (options.library && options.library.type !== 'commonjs-module') { + throw new Error( + '@module-federation/lynx `remoteBundle` requires `library.type: "commonjs-module"`.', + ); + } + if (options.runtime !== undefined) { + throw new Error( + '@module-federation/lynx `remoteBundle` manages the container runtime; remove `options.runtime`.', + ); + } + if (options.manifest === false) { + throw new Error( + '@module-federation/lynx `remoteBundle` requires the Module Federation manifest; remove `manifest: false`.', + ); + } + if (adapterOptions.layer && adapterOptions.layer !== layers.BACKGROUND) { + throw new Error( + '@module-federation/lynx `remoteBundle` requires the adapter layer to be `LAYERS.BACKGROUND`.', + ); + } + + const conflictingLayer = findConflictingExposeLayer( + exposes, + layers.BACKGROUND, + ); + if (conflictingLayer) { + throw new Error( + `@module-federation/lynx \`remoteBundle\` owns expose layers; remove the explicit \`${conflictingLayer}\` expose layer.`, + ); + } + const chunkNamesByExpose = assertUniqueChunkNames(exposes); + + const reservedExposeKey = findReservedExposeKey( + exposes, + MAIN_THREAD_EXPOSE_SUFFIX, + ); + if (reservedExposeKey) { + throw new Error( + `@module-federation/lynx remoteBundle reserves expose keys ending in "${MAIN_THREAD_EXPOSE_SUFFIX}"; rename "${reservedExposeKey}".`, + ); + } + + const pairedPlan = plan.mode === 'native-single' ? undefined : plan; + const mainThreadChunkSuffix = `-${layers.MAIN_THREAD.replace(/:/g, '__')}`; + const remoteExposes = pairedPlan + ? { + ...createRemoteExposes( + exposes, + layers.BACKGROUND, + plan.backgroundChunkPrefix, + ), + ...createRemoteExposes( + exposes, + layers.MAIN_THREAD, + pairedPlan.mainThreadChunkPrefix, + MAIN_THREAD_EXPOSE_SUFFIX, + mainThreadChunkSuffix, + ), + } + : createRemoteExposes( + exposes, + layers.BACKGROUND, + plan.backgroundChunkPrefix, + ); + const activeRealmLayers = pairedPlan + ? [layers.BACKGROUND, layers.MAIN_THREAD] + : [layers.BACKGROUND]; + const remoteShared = normalizeRealmScopedShared( + options.shared, + layers, + layers.BACKGROUND, + activeRealmLayers, + options.shareScope, + ); + const federationOptions = { + ...createFederationOptions( + { + ...options, + remotes: normalizeRealmScopedRemotes( + options.remotes, + layers, + activeRealmLayers, + ), + shareScope: getLynxShareScopes( + options.shareScope, + layers, + activeRealmLayers, + ), + }, + remoteExposes, + remoteShared, + runtimePlugin, + resolveRuntimePluginOptions(adapterOptions.runtimePluginOptions, layers), + ), + manifest: options.manifest ?? true, + filename: plan.backgroundEntry, + runtime: false as const, + }; + const entryGlobalName = + typeof federationOptions.library?.name === 'string' + ? federationOptions.library.name + : options.name; + const entrySectionNames = new Map([[plan.backgroundEntry, entryGlobalName]]); + if (plan.mainThreadEntry) { + entrySectionNames.set( + plan.mainThreadEntry, + `${entryGlobalName}__main-thread`, + ); + } + const exposeByExpectedLazyBundleChunk = new Map( + Array.from(chunkNamesByExpose, ([expose, chunkName]) => [ + `${plan.backgroundChunkPrefix}${chunkName}`, + expose, + ]), + ); + const stateStore = createRemoteBundleCompilationStateStore(); + const { manifestFileName, statsFileName } = getManifestFileName( + federationOptions.manifest, + ); + const encode = + plan.mode === 'web-split' + ? getLynxWebEncodeMode() + : ((await import('@lynx-js/tasm')).getEncodeMode() as ( + value: unknown, + ) => Promise<{ buffer: Buffer }>); + config.plugins ||= []; + config.plugins.push( + createCompilerModuleFederationPlugin(federationOptions), + createLynxChunkLoadingMatcherPlugin(lynxTemplatePlugin, { + autoPublicPath: config.output?.publicPath === 'auto', + backgroundOnlyRemote: !pairedPlan, + chunking: plan.chunking, + discardSourceEntryBundles: + remoteBundle.preserveSourceEntryBundles === false, + includedChunkPrefixes: plan.includedChunkPrefixes, + exposeByExpectedLazyBundleChunk, + remoteEntryName: options.name, + ...(pairedPlan + ? { + pairedRealmChunkPrefixes: { + background: plan.backgroundChunkPrefix, + mainThread: pairedPlan.mainThreadChunkPrefix, + }, + pairedRealmChunkSuffixes: { + background: `-${layers.BACKGROUND.replace(/:/g, '__')}`, + mainThread: `-${layers.MAIN_THREAD.replace(/:/g, '__')}`, + }, + } + : {}), + stateStore, + }), + ); + if (pairedPlan) { + config.plugins.push( + createRemoteAssetsPlugin( + stateStore, + plan.backgroundEntry, + plan.mainThreadEntry, + plan.backgroundChunkPrefix, + layers.MAIN_THREAD, + ), + ); + } + config.plugins.push( + createLynxExternalBundlePlugin({ + bundleFileName: plan.bundleFileName, + chunking: plan.chunking, + encode, + engineVersion: remoteBundle.engineVersion, + entryAssets: plan.entryAssets, + entryName: options.name, + entrySectionNames, + includedChunkPrefixes: plan.includedChunkPrefixes, + exposeByExpectedLazyBundleChunk, + preservedAssets: [manifestFileName, statsFileName], + stateStore, + }), + createLynxRemoteManifestPlugin( + federationOptions.manifest, + plan.bundleFileName, + ), + ); +}; diff --git a/packages/lynx/src/remoteBundleCompilationState.ts b/packages/lynx/src/remoteBundleCompilationState.ts new file mode 100644 index 00000000000..f1ca1488245 --- /dev/null +++ b/packages/lynx/src/remoteBundleCompilationState.ts @@ -0,0 +1,35 @@ +import type { Compilation } from '@rspack/core'; + +export interface RemoteBundleCompilationState { + discardedTemplateAssets: Set; + lazyBundleAssets: Set; + lazyBundleAssetByExpose: Map; + pairedBundleChunks: Set; + sourceAssets: Array<{ content: string; name: string }>; +} + +export interface RemoteBundleCompilationStateStore { + for(compilation: Compilation): RemoteBundleCompilationState; +} + +export const createRemoteBundleCompilationStateStore = + (): RemoteBundleCompilationStateStore => { + const states = new WeakMap(); + + return { + for(compilation) { + let state = states.get(compilation); + if (!state) { + state = { + discardedTemplateAssets: new Set(), + lazyBundleAssets: new Set(), + lazyBundleAssetByExpose: new Map(), + pairedBundleChunks: new Set(), + sourceAssets: [], + }; + states.set(compilation, state); + } + return state; + }, + }; + }; diff --git a/packages/lynx/src/remoteManifest.test.ts b/packages/lynx/src/remoteManifest.test.ts new file mode 100644 index 00000000000..b6453a9953e --- /dev/null +++ b/packages/lynx/src/remoteManifest.test.ts @@ -0,0 +1,154 @@ +import { describe, expect, it, rs } from '@rstest/core'; + +import { + createLynxRemoteManifestPlugin, + retargetRemoteEntry, +} from './remoteManifest'; + +const createManifest = () => ({ + id: 'catalog', + metaData: { + remoteEntry: { + path: 'static/js/', + name: 'catalog.js', + type: 'global', + extra: 'preserved', + }, + }, + exposes: [ + { + name: 'Card', + requiredShared: [{ name: 'react' }], + assets: { + js: { sync: ['static/Card.js'], async: ['static/vendor.js'] }, + css: { sync: ['static/Card.css'], async: [] }, + }, + }, + { + name: 'Card__main_thread', + path: './Card__main_thread', + assets: { + js: { sync: ['static/Card.main.js'], async: [] }, + css: { sync: [], async: [] }, + }, + }, + ], +}); + +describe('Lynx remote manifest', () => { + it('retargets the public remote entry to the external bundle', () => { + const result = JSON.parse( + retargetRemoteEntry( + JSON.stringify(createManifest()), + 'mf-manifest.json', + 'remotes/catalog.lynx.bundle', + ), + ); + + expect(result).toMatchObject({ + id: 'catalog', + metaData: { + remoteEntry: { + path: 'remotes/', + name: 'catalog.lynx.bundle', + type: 'lynx', + extra: 'preserved', + }, + }, + exposes: [ + { + name: 'Card', + requiredShared: [{ name: 'react' }], + assets: { + js: { sync: [], async: [] }, + css: { sync: [], async: [] }, + }, + }, + ], + }); + }); + + it('does not advertise external bundle sections as browser assets', () => { + const manifest = createManifest(); + const result = JSON.parse( + retargetRemoteEntry( + JSON.stringify(manifest), + 'mf-manifest.json', + 'catalog.lynx.bundle', + ), + ); + + expect(result.exposes[0].assets).toEqual({ + js: { sync: [], async: [] }, + css: { sync: [], async: [] }, + }); + expect(result.exposes).toHaveLength(1); + expect(manifest.exposes[0].assets.js.sync).toEqual(['static/Card.js']); + }); + + it.each([ + ['not JSON', 'could not parse'], + [JSON.stringify({ metaData: {} }), 'has no metaData.remoteEntry'], + ])('rejects an invalid generated asset', (source, message) => { + expect(() => + retargetRemoteEntry(source, 'mf-manifest.json', 'catalog.lynx.bundle'), + ).toThrow(message); + }); + + it('rewrites configured manifest and stats assets after generation', () => { + const plugin = createLynxRemoteManifestPlugin( + { filePath: 'metadata', fileName: 'catalog-manifest.json' }, + 'catalog.lynx.bundle', + ); + let rewriteAssets: (() => void) | undefined; + const updateAsset = rs.fn(); + const compiler = { + webpack: { + Compilation: { PROCESS_ASSETS_STAGE_REPORT: 5_000 }, + sources: { + RawSource: class { + constructor(readonly value: string) {} + }, + }, + }, + hooks: { + emit: { + tap(_name: string, callback: (compilation: any) => void) { + rewriteAssets = () => callback(compilation); + }, + }, + }, + }; + const source = JSON.stringify(createManifest()); + const compilation = { + getAsset(name: string) { + return [ + 'metadata/catalog-manifest.json', + 'metadata/catalog-manifest-stats.json', + ].includes(name) + ? { source: { source: () => source } } + : undefined; + }, + updateAsset, + }; + plugin.apply(compiler as any); + rewriteAssets!(); + + expect(updateAsset).toHaveBeenCalledTimes(2); + expect(updateAsset.mock.calls.map((call) => call[0])).toEqual([ + 'metadata/catalog-manifest.json', + 'metadata/catalog-manifest-stats.json', + ]); + for (const call of updateAsset.mock.calls) { + expect(JSON.parse(call[1].value)).toMatchObject({ + metaData: { + remoteEntry: { + path: '', + name: 'catalog.lynx.bundle', + type: 'lynx', + }, + }, + }); + } + }); +}); diff --git a/packages/lynx/src/remoteManifest.ts b/packages/lynx/src/remoteManifest.ts new file mode 100644 index 00000000000..e0244c9c062 --- /dev/null +++ b/packages/lynx/src/remoteManifest.ts @@ -0,0 +1,139 @@ +import { getManifestFileName } from '@module-federation/sdk'; +import type { + Compiler, + ModuleFederationPluginOptions, + WebpackPluginInstance, +} from '@rspack/core'; + +import { MAIN_THREAD_EXPOSE_SUFFIX } from './runtimeCore'; + +interface RemoteEntryRecord extends Record { + name: string; + path: string; + type: string; +} + +interface FederationMetadata extends Record { + remoteEntry: RemoteEntryRecord; +} + +interface FederationManifest extends Record { + exposes?: unknown; + metaData: FederationMetadata; +} + +const isRecord = (value: unknown): value is Record => + typeof value === 'object' && value !== null; + +const parseManifest = ( + source: string, + assetName: string, +): FederationManifest => { + let value: unknown; + try { + value = JSON.parse(source); + } catch { + throw new Error( + `@module-federation/lynx could not parse generated manifest asset "${assetName}".`, + ); + } + + if ( + !isRecord(value) || + !isRecord(value.metaData) || + !isRecord(value.metaData.remoteEntry) + ) { + throw new Error( + `@module-federation/lynx generated manifest asset "${assetName}" has no metaData.remoteEntry.`, + ); + } + + return value as FederationManifest; +}; + +const getBundleResource = ( + bundleFileName: string, +): Pick => { + const lastSlash = bundleFileName.lastIndexOf('/'); + return { + path: lastSlash === -1 ? '' : bundleFileName.slice(0, lastSlash + 1), + name: bundleFileName.slice(lastSlash + 1), + type: 'lynx', + }; +}; + +export const retargetRemoteEntry = ( + source: string, + assetName: string, + bundleFileName: string, +): string => { + const manifest = parseManifest(source, assetName); + manifest.metaData.remoteEntry = { + ...manifest.metaData.remoteEntry, + ...getBundleResource(bundleFileName), + }; + if (Array.isArray(manifest.exposes)) { + const exposes = manifest.exposes.filter( + (expose) => + !isRecord(expose) || + ![expose.name, expose.path].some( + (value) => + typeof value === 'string' && + value.endsWith(MAIN_THREAD_EXPOSE_SUFFIX), + ), + ); + manifest.exposes = exposes; + for (const expose of exposes) { + if (!isRecord(expose) || !isRecord(expose.assets)) { + continue; + } + + for (const type of ['js', 'css']) { + const assets = expose.assets[type]; + if (isRecord(assets)) { + expose.assets[type] = { ...assets, sync: [], async: [] }; + } + } + } + } + return JSON.stringify(manifest, null, 2); +}; + +export const createLynxRemoteManifestPlugin = ( + manifest: ModuleFederationPluginOptions['manifest'], + bundleFileName: string, +): WebpackPluginInstance => { + const { manifestFileName, statsFileName } = getManifestFileName(manifest); + const assetNames = [manifestFileName, statsFileName]; + + return { + apply(compiler: Compiler) { + const pluginName = 'LynxModuleFederationRemoteManifest'; + compiler.hooks.emit.tap(pluginName, (compilation) => { + for (const assetName of assetNames) { + const asset = compilation.getAsset(assetName); + if (!asset) { + const emittedAssets = compilation + .getAssets() + .map(({ name }) => name) + .sort() + .join(', '); + throw new Error( + `@module-federation/lynx could not find generated manifest asset "${assetName}". Emitted assets: ${emittedAssets || '(none)'}.`, + ); + } + compilation.updateAsset( + assetName, + new compiler.webpack.sources.RawSource( + retargetRemoteEntry( + asset.source.source().toString(), + assetName, + bundleFileName, + ), + ), + ); + } + }); + }, + }; +}; diff --git a/packages/lynx/src/runtimeChunkLoading.testUtils.ts b/packages/lynx/src/runtimeChunkLoading.testUtils.ts new file mode 100644 index 00000000000..4ddf43e1d1b --- /dev/null +++ b/packages/lynx/src/runtimeChunkLoading.testUtils.ts @@ -0,0 +1,40 @@ +import { rs } from '@rstest/core'; + +import { LYNX_BUNDLE_REGISTRY, type LynxWebpackRequire } from './runtimePlugin'; + +export const remoteRegistry = () => + new Map([ + ['remote', 'lynx-cache://catalog'], + ['remote:remote-origin', 'https://cdn.example/remotes/catalog.lynx.bundle'], + ]); + +export const createWebpackRequire = ( + filename = 'chunks/feature.js?cache=1#fragment', +): LynxWebpackRequire => ({ + f: {}, + m: {}, + u: rs.fn(() => filename), +}); + +export const createGlobalObject = ( + loadLazyBundle: (request: string) => PromiseLike, +) => ({ + lynx: { loadLazyBundle, loadScript: rs.fn() }, + [LYNX_BUNDLE_REGISTRY]: remoteRegistry(), +}); + +export const makeSynchronousThenable = (value: T): PromiseLike => { + const thenable = { + then(onFulfilled?: ((resolved: T) => unknown) | null) { + if (!onFulfilled) { + return makeSynchronousThenable(value); + } + try { + return makeSynchronousThenable(onFulfilled(value)); + } catch (error) { + return Promise.reject(error); + } + }, + }; + return thenable as PromiseLike; +}; diff --git a/packages/lynx/src/runtimeChunkLoading.ts b/packages/lynx/src/runtimeChunkLoading.ts new file mode 100644 index 00000000000..afec72cbe6d --- /dev/null +++ b/packages/lynx/src/runtimeChunkLoading.ts @@ -0,0 +1,426 @@ +import { + getLynxRealm, + getLynxRuntime, + getRemoteOriginKey, + getRegistryKey, + isRecord, + LYNX_BUNDLE_REGISTRY, + type LynxGlobal, +} from './runtimeCore'; +import { createLazyChunkLoadController } from './lazyChunkLoadController'; + +type ChunkId = string | number; +export type ChunkPromise = PromiseLike; +type ChunkHandler = (chunkId: ChunkId, promises: ChunkPromise[]) => void; + +export interface LynxWebpackRequire { + consumesLoadingData?: { + chunkMapping?: Record; + }; + f: Record; + lynx_aci?: Record; + lynx_chunking?: 'single' | 'split'; + lynx_public_path_auto?: boolean; + m: Record; + p?: string; + u(chunkId: ChunkId): string; +} + +export interface LynxChunk { + __lynx_dynamic_component_entry__?: string; + ids: ChunkId[]; + modules: Record; + runtime?: (webpackRequire: LynxWebpackRequire) => void; +} + +export type InstalledChunk = + | 0 + | [ + ((value?: unknown) => void) | undefined, + ((error: unknown) => void) | undefined, + ChunkPromise | undefined, + ]; + +const isChunk = (value: unknown): value is LynxChunk => + isRecord(value) && + Array.isArray(value.ids) && + isRecord(value.modules) && + (value.runtime === undefined || typeof value.runtime === 'function'); + +const getChunkSectionPath = (filename: string): string => + filename.split(/[?#]/, 1)[0].replace(/\.js$/, ''); + +const joinRemoteUrl = ( + entryUrl: string, + publicPath: string | undefined, + assetPath: string, +): string => { + if (/^(?:[a-z][a-z\d+.-]*:)?\/\//i.test(assetPath)) { + return assetPath; + } + + const entry = entryUrl.split(/[?#]/, 1)[0]; + const origin = entry.match(/^(?:[a-z][a-z\d+.-]*:)?\/\/[^/]+/i)?.[0] ?? ''; + const base = + publicPath && publicPath !== 'auto' + ? publicPath + : entry.slice(0, entry.lastIndexOf('/') + 1); + if (/^(?:[a-z][a-z\d+.-]*:)?\/\//i.test(base)) { + return `${base.replace(/\/$/, '')}/${assetPath.replace(/^\//, '')}`; + } + if (base.startsWith('/')) { + return `${origin}${base.replace(/\/$/, '')}/${assetPath.replace(/^\//, '')}`; + } + return `${entry.slice(0, entry.lastIndexOf('/') + 1)}${base.replace(/\/$/, '')}/${assetPath.replace(/^\//, '')}`; +}; + +const installChunk = ( + chunk: LynxChunk, + webpackRequire: LynxWebpackRequire, + installedChunks: Record, + globalObject: LynxGlobal, +): void => { + const entryName = chunk.__lynx_dynamic_component_entry__; + const modules = Object.fromEntries( + Object.entries(chunk.modules).map(([id, factory]) => [ + id, + typeof factory !== 'function' || !entryName + ? factory + : function wrappedFactory( + this: unknown, + module: unknown, + exports: unknown, + runtimeRequire: LynxWebpackRequire, + ) { + const hadEntryName = Object.prototype.hasOwnProperty.call( + globalObject, + 'globDynamicComponentEntry', + ); + const previousEntryName = globalObject.globDynamicComponentEntry; + globalObject.globDynamicComponentEntry = entryName; + try { + return factory.call(this, module, exports, runtimeRequire); + } finally { + if (hadEntryName) { + globalObject.globDynamicComponentEntry = previousEntryName; + } else { + delete globalObject.globDynamicComponentEntry; + } + } + }, + ]), + ); + Object.assign(webpackRequire.m, modules); + chunk.runtime?.(webpackRequire); + + for (const id of chunk.ids) { + const installed = installedChunks[id]; + if (installed) { + installed[0]?.(chunk); + } + installedChunks[id] = 0; + } +}; + +const installChunkAfterConsumes = ( + chunk: LynxChunk, + webpackRequire: LynxWebpackRequire, + installedChunks: Record, + globalObject: LynxGlobal, + isActive: () => boolean = () => true, +): Promise | undefined => { + const getMissingConsumes = (): string[] => + chunk.ids.flatMap( + (id) => + webpackRequire.consumesLoadingData?.chunkMapping?.[String(id)]?.filter( + (moduleId) => typeof webpackRequire.m[moduleId] !== 'function', + ) ?? [], + ); + const install = (): void => { + if (!isActive()) { + return; + } + const missing = getMissingConsumes(); + if (missing.length > 0) { + throw new Error( + `Lynx chunk shared dependencies were not installed: ${missing.join(', ')}.`, + ); + } + installChunk(chunk, webpackRequire, installedChunks, globalObject); + }; + const consume = webpackRequire.f.consumes; + if (!consume) { + install(); + return; + } + + const promises: Promise[] = []; + for (const id of chunk.ids) { + consume(id, promises); + } + if (promises.length === 0) { + install(); + return; + } + const participatingLoads = new Map( + chunk.ids.map((id) => [id, installedChunks[id]]), + ); + + return Promise.all(promises).then( + () => { + install(); + }, + (error) => { + if (!isActive()) { + throw error; + } + for (const id of chunk.ids) { + const installed = installedChunks[id]; + if (installed && installed === participatingLoads.get(id)) { + installed[1]?.(error); + } + } + throw error; + }, + ); +}; + +interface QueryResolver { + promise: Promise; + reject(error: Error): void; + resolve(value: unknown): void; +} + +const createQueryResolver = (): QueryResolver => { + let resolve!: (value: unknown) => void; + let reject!: (error: Error) => void; + const promise = new Promise((resolvePromise, rejectPromise) => { + resolve = resolvePromise; + reject = rejectPromise; + }); + return { promise, reject, resolve }; +}; + +const queryError = (request: string, result: unknown): Error => { + const error = new Error(`Failed to load Lynx lazy bundle "${request}".`); + (error as Error & { cause?: string }).cause = JSON.stringify(result); + return error; +}; + +const loadQueryComponent = ( + request: string, + lynx: NonNullable>, + globalObject: LynxGlobal, + hostOverride?: string, +): PromiseLike => { + const host = + hostOverride ?? + (typeof globalObject.globDynamicComponentEntry === 'string' + ? globalObject.globDynamicComponentEntry + : undefined); + if (typeof lynx.loadLazyBundle === 'function') { + return host === undefined + ? lynx.loadLazyBundle(request) + : lynx.loadLazyBundle(request, undefined, host); + } + const nativeLynx = lynx.getNativeLynx?.(); + if (typeof nativeLynx?.loadLazyBundle === 'function') { + return host === undefined + ? nativeLynx.loadLazyBundle(request) + : nativeLynx.loadLazyBundle(request, undefined, host); + } + + if (getLynxRealm(lynx) === 'main-thread') { + const queryComponent = globalObject.__QueryComponent; + if (!queryComponent) { + return Promise.reject( + new Error( + 'Lynx main-thread split chunk loading requires __QueryComponent.', + ), + ); + } + const resolver = createQueryResolver(); + try { + const query = queryComponent(request, (result) => { + if (result.code === 0 && result.data && 'evalResult' in result.data) { + resolver.resolve(result.data.evalResult); + } else { + resolver.reject(queryError(request, result)); + } + }); + if (query && 'evalResult' in query) { + resolver.resolve(query.evalResult); + } + } catch (error) { + resolver.reject( + error instanceof Error ? error : new Error(String(error)), + ); + } + return resolver.promise; + } + + const queryComponent = lynx.QueryComponent ?? nativeLynx?.QueryComponent; + const getExports = + globalObject.lynxCoreInject?.tt?.getDynamicComponentExports; + if (!queryComponent) { + return Promise.reject( + new Error('Lynx background split chunk loading requires QueryComponent.'), + ); + } + + const resolver = createQueryResolver(); + queryComponent(request, (result) => { + const schema = + isRecord(result) && isRecord(result.detail) + ? result.detail.schema + : undefined; + if (typeof schema === 'string' && !getExports) { + resolver.reject( + new Error( + 'Lynx background split chunk loading requires getDynamicComponentExports.', + ), + ); + return; + } + const exports = + isRecord(result) && result.code === 0 && typeof schema === 'string' + ? getExports!(schema) + : undefined; + if (exports !== undefined) { + resolver.resolve(exports); + return; + } + resolver.reject(queryError(request, result)); + }); + return resolver.promise; +}; + +export const patchLynxChunkLoading = ( + webpackRequire: LynxWebpackRequire, + originName: string, + globalObject: LynxGlobal = globalThis as LynxGlobal, + timeout = 30_000, +): boolean => { + const lynx = getLynxRuntime(globalObject); + if (!lynx?.loadScript) { + return false; + } + const { loadScript } = lynx; + + const registry = globalObject[LYNX_BUNDLE_REGISTRY]; + const registeredBundleName = + registry?.get(originName) ?? + registry?.get(getRegistryKey(originName, getLynxRealm(lynx))); + if (!registeredBundleName && !webpackRequire.lynx_aci) { + return false; + } + const baseName = originName.replace(/__main_thread$/, ''); + const registeredRemoteOrigin = registry?.get(getRemoteOriginKey(baseName)); + const getBundleName = (): string | undefined => + registeredBundleName ?? + globalObject[LYNX_BUNDLE_REGISTRY]?.get(originName) ?? + globalObject[LYNX_BUNDLE_REGISTRY]?.get( + getRegistryKey(originName, getLynxRealm(lynx)), + ); + + const installedChunks: Record = {}; + + const loadChunk: ChunkHandler = (chunkId, promises) => { + const key = String(chunkId); + const installed = installedChunks[key]; + if (installed === 0) { + return; + } + if (installed) { + if (installed[2]) { + promises.push(installed[2]); + } + return; + } + + const lazyBundlePath = + webpackRequire.lynx_chunking === 'single' + ? undefined + : webpackRequire.lynx_aci?.[key]; + if (lazyBundlePath) { + const currentRegistry = globalObject[LYNX_BUNDLE_REGISTRY]; + const loadingHost = + registeredRemoteOrigin ?? + currentRegistry?.get(getRemoteOriginKey(baseName)); + const remoteOrigin = + loadingHost ?? getBundleName() ?? webpackRequire.p ?? ''; + const request = joinRemoteUrl( + remoteOrigin, + webpackRequire.lynx_public_path_auto ? undefined : webpackRequire.p, + lazyBundlePath, + ); + const controller = createLazyChunkLoadController({ + chunkKey: key, + installedChunks, + timeout, + loadQueryComponent: (lazyRequest) => + loadQueryComponent(lazyRequest, lynx, globalObject, loadingHost), + isChunk, + installChunkAfterConsumes: (chunk, isCurrent) => + installChunkAfterConsumes( + chunk, + webpackRequire, + installedChunks, + globalObject, + isCurrent, + ), + }); + promises.push(controller.load(request)); + return; + } + + if (webpackRequire.lynx_aci && !getBundleName()) { + return; + } + + let resolveChunk!: (value?: unknown) => void; + let rejectChunk!: (error: unknown) => void; + const promise = new Promise((resolve, reject) => { + resolveChunk = resolve; + rejectChunk = reject; + }); + installedChunks[key] = [resolveChunk, rejectChunk, promise]; + promises.push(promise); + + try { + const bundleName = getBundleName(); + if (!bundleName) { + throw new Error( + `Lynx section loading requires a registered bundle for "${originName}".`, + ); + } + const sectionPath = getChunkSectionPath(webpackRequire.u(chunkId)); + const value = loadScript(sectionPath, { bundleName }); + if (!isChunk(value)) { + throw new Error( + `Lynx section "${sectionPath}" did not export a valid webpack chunk.`, + ); + } + if (!value.ids.some((id) => String(id) === key)) { + throw new Error( + `Lynx section "${sectionPath}" did not include requested chunk "${key}".`, + ); + } + installChunk(value, webpackRequire, installedChunks, globalObject); + } catch (error) { + delete installedChunks[key]; + rejectChunk(error); + } + }; + + const configuredHandlers = ['j', 'require'].filter( + (key) => webpackRequire.f[key] !== undefined, + ); + for (const key of configuredHandlers.length > 0 + ? configuredHandlers + : ['j']) { + webpackRequire.f[key] = loadChunk; + } + + return true; +}; diff --git a/packages/lynx/src/runtimeChunkUrl.test.ts b/packages/lynx/src/runtimeChunkUrl.test.ts new file mode 100644 index 00000000000..5e6f8b31c74 --- /dev/null +++ b/packages/lynx/src/runtimeChunkUrl.test.ts @@ -0,0 +1,307 @@ +import { describe, expect, it, rs } from '@rstest/core'; + +import { LYNX_BUNDLE_REGISTRY, patchLynxChunkLoading } from './runtimePlugin'; +import { + createWebpackRequire, + remoteRegistry, +} from './runtimeChunkLoading.testUtils'; + +describe('patchLynxChunkLoading chunk URLs', () => { + it('loads host lazy bundles before a bundle registry exists', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: 'lazy-bundle/Feature.bundle' }; + webpackRequire.p = 'https://app.example/dist/host-web/'; + const loadLazyBundle = rs.fn(async () => ({ + ids: ['feature'], + modules: {}, + })); + const globalObject = { + globDynamicComponentEntry: + 'https://app.example/dist/host-web/main.lynx.bundle', + lynx: { loadLazyBundle, loadScript: rs.fn() }, + }; + + expect(patchLynxChunkLoading(webpackRequire, 'host', globalObject)).toBe( + true, + ); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + await Promise.all(promises); + + expect(loadLazyBundle).toHaveBeenCalledWith( + 'https://app.example/dist/host-web/lazy-bundle/Feature.bundle', + undefined, + 'https://app.example/dist/host-web/main.lynx.bundle', + ); + }); + + it('leaves assetless host chunks to other webpack handlers', () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { + local: 'lazy-bundle/Local.bundle', + }; + const loadLazyBundle = rs.fn(); + const globalObject = { + lynx: { loadLazyBundle, loadScript: rs.fn() }, + }; + + patchLynxChunkLoading(webpackRequire, 'host', globalObject); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('remote-only', promises); + + expect(promises).toEqual([]); + expect(loadLazyBundle).not.toHaveBeenCalled(); + }); + + it('captures the remote origin when the container runtime is patched', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: 'async/Card.bundle' }; + const loadLazyBundle = rs.fn(async () => ({ + ids: ['feature'], + modules: {}, + })); + const registry = remoteRegistry(); + const globalObject = { + globDynamicComponentEntry: '__Card__', + lynx: { loadLazyBundle, loadScript: rs.fn() }, + [LYNX_BUNDLE_REGISTRY]: registry, + }; + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + registry.set( + 'remote:remote-origin', + 'https://other.example/remotes/catalog.lynx.bundle', + ); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + await Promise.all(promises); + + expect(loadLazyBundle).toHaveBeenCalledWith( + 'https://cdn.example/remotes/async/Card.bundle', + undefined, + 'https://cdn.example/remotes/catalog.lynx.bundle', + ); + }); + + it('loads split remote chunks as independently fetched Lynx lazy bundles', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { + feature: 'async/catalog__background_Card.123.bundle', + }; + webpackRequire.p = '/remote-assets/'; + const factory = rs.fn(); + const loadScript = rs.fn(); + const loadLazyBundle = rs.fn(async () => ({ + ids: ['feature'], + modules: { factory }, + })); + const globalObject = { + globDynamicComponentEntry: + 'https://cdn.example/remotes/catalog.lynx.bundle', + lynx: { loadLazyBundle, loadScript }, + [LYNX_BUNDLE_REGISTRY]: new Map([ + ['remote', 'https://cdn.example/cache/catalog.lynx.bundle'], + [ + 'remote:remote-origin', + 'https://cdn.example/remotes/catalog.lynx.bundle', + ], + ]), + }; + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + + await expect(Promise.all(promises)).resolves.toBeDefined(); + expect(loadLazyBundle).toHaveBeenCalledWith( + 'https://cdn.example/remote-assets/async/catalog__background_Card.123.bundle', + undefined, + 'https://cdn.example/remotes/catalog.lynx.bundle', + ); + expect(loadScript).not.toHaveBeenCalled(); + expect(webpackRequire.m.factory).toBe(factory); + }); + + it('preserves protocol-relative lazy bundle and public-path URLs', async () => { + const loadLazyBundle = rs.fn(async () => ({ + ids: ['feature'], + modules: {}, + })); + const globalObject = { + lynx: { loadLazyBundle, loadScript: rs.fn() }, + [LYNX_BUNDLE_REGISTRY]: new Map([ + ['remote', 'https://origin.example/catalog.lynx.bundle'], + ['remote:remote-origin', 'https://origin.example/catalog.lynx.bundle'], + ]), + }; + const publicPathRequire = createWebpackRequire(); + publicPathRequire.lynx_aci = { feature: 'async/Card.bundle' }; + publicPathRequire.p = '//cdn.example/assets/'; + patchLynxChunkLoading(publicPathRequire, 'remote', globalObject); + const publicPathPromises: PromiseLike[] = []; + publicPathRequire.f.j!('feature', publicPathPromises); + await Promise.all(publicPathPromises); + + const assetRequire = createWebpackRequire(); + assetRequire.lynx_aci = { + feature: '//assets.example/chunks/Card.bundle', + }; + patchLynxChunkLoading(assetRequire, 'remote', globalObject); + const assetPromises: PromiseLike[] = []; + assetRequire.f.j!('feature', assetPromises); + await Promise.all(assetPromises); + + expect(loadLazyBundle.mock.calls[0]?.[0]).toBe( + '//cdn.example/assets/async/Card.bundle', + ); + expect(loadLazyBundle.mock.calls[1]?.[0]).toBe( + '//assets.example/chunks/Card.bundle', + ); + }); + + it.each([ + [ + undefined, + 'async/Card.bundle', + 'https://cdn.example/remotes/async/Card.bundle', + ], + [ + 'auto', + 'async/Card.bundle', + 'https://cdn.example/remotes/async/Card.bundle', + ], + ['/', 'async/Card.bundle', 'https://cdn.example/async/Card.bundle'], + [ + 'assets/', + 'async/Card.bundle', + 'https://cdn.example/remotes/assets/async/Card.bundle', + ], + [ + 'assets', + 'async/Card.bundle', + 'https://cdn.example/remotes/assets/async/Card.bundle', + ], + [ + 'assets/', + '/async/Card.bundle', + 'https://cdn.example/remotes/assets/async/Card.bundle', + ], + ['/v2/', 'async/Card.bundle', 'https://cdn.example/v2/async/Card.bundle'], + ['/v2', '/async/Card.bundle', 'https://cdn.example/v2/async/Card.bundle'], + [ + 'https://assets.example/v3/', + 'async/Card.bundle', + 'https://assets.example/v3/async/Card.bundle', + ], + [ + 'https://assets.example/v3', + 'async/Card.bundle', + 'https://assets.example/v3/async/Card.bundle', + ], + [ + 'http://assets.example/v4/', + 'async/Card.bundle', + 'http://assets.example/v4/async/Card.bundle', + ], + [ + '//assets.example/v5', + 'async/Card.bundle', + '//assets.example/v5/async/Card.bundle', + ], + [ + '/ignored/', + 'https://assets.example/Card.bundle', + 'https://assets.example/Card.bundle', + ], + [ + '/ignored/', + 'http://assets.example/Card.bundle', + 'http://assets.example/Card.bundle', + ], + ])( + 'resolves split public path %s against the manifest entry', + async (publicPath, assetPath, expected) => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: assetPath }; + webpackRequire.p = publicPath; + const loadLazyBundle = rs.fn(async () => ({ + ids: ['feature'], + modules: {}, + })); + const globalObject = { + lynx: { loadLazyBundle, loadScript: rs.fn() }, + [LYNX_BUNDLE_REGISTRY]: new Map([ + ['remote', 'lynx-cache://catalog'], + [ + 'remote:remote-origin', + 'https://cdn.example/remotes/catalog.lynx.bundle?version=1', + ], + ]), + }; + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + await Promise.all(promises); + + expect(loadLazyBundle.mock.calls[0]?.[0]).toBe(expected); + }, + ); + + it('uses the manifest entry directory when Webpack auto-detects the Lynx Web client path', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: 'async/Card.bundle' }; + webpackRequire.lynx_public_path_auto = true; + webpackRequire.p = + 'http://host.example/node_modules/@lynx-js/web-core/dist/client_prod/static/js/'; + const loadLazyBundle = rs.fn(async () => ({ + ids: ['feature'], + modules: {}, + })); + const globalObject = { + lynx: { loadLazyBundle, loadScript: rs.fn() }, + [LYNX_BUNDLE_REGISTRY]: new Map([ + ['remote', 'lynx-cache://catalog'], + [ + 'remote:remote-origin', + 'https://cdn.example/remotes/catalog.lynx.bundle', + ], + ]), + }; + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + await Promise.all(promises); + + expect(loadLazyBundle.mock.calls[0]?.[0]).toBe( + 'https://cdn.example/remotes/async/Card.bundle', + ); + }); + + it('preserves a protocol-relative remote origin for root public paths', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: 'async/Card.bundle' }; + webpackRequire.p = '/'; + const loadLazyBundle = rs.fn(async () => ({ + ids: ['feature'], + modules: {}, + })); + const globalObject = { + lynx: { loadLazyBundle, loadScript: rs.fn() }, + [LYNX_BUNDLE_REGISTRY]: new Map([ + ['remote', 'lynx-cache://catalog'], + ['remote:remote-origin', '//cdn.example/remotes/catalog.lynx.bundle'], + ]), + }; + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + await Promise.all(promises); + + expect(loadLazyBundle.mock.calls[0]?.[0]).toBe( + '//cdn.example/async/Card.bundle', + ); + }); +}); diff --git a/packages/lynx/src/runtimeCore.ts b/packages/lynx/src/runtimeCore.ts new file mode 100644 index 00000000000..ae2d97db1f7 --- /dev/null +++ b/packages/lynx/src/runtimeCore.ts @@ -0,0 +1,159 @@ +import type { RemoteEntryExports } from '@module-federation/runtime-core/types'; + +export const LYNX_BUNDLE_REGISTRY = Symbol.for( + 'module-federation:lynx:bundle-registry', +); + +export const LYNX_REMOTE_ORIGIN_SUFFIX = ':remote-origin'; + +export type LynxRealm = 'background' | 'main-thread'; + +export interface LynxRuntimePluginOptions { + /** Resolved DSL layer names injected by the build adapter. */ + realmLayers?: Record; + timeout?: number; +} + +export interface LynxRuntime { + QueryComponent?(source: string, callback: (result: unknown) => void): void; + fetchBundle?(bundleUrl: string): PromiseLike; + loadScript?(sectionPath: string, options: { bundleName: string }): unknown; + loadLazyBundle?( + bundleUrl: string, + mode?: 'sync' | 'async', + host?: string, + ): PromiseLike; + getNativeApp?(): unknown; + getNativeLynx?(): Pick; + requireModuleAsync?( + moduleUrl: string, + callback: (error: unknown, value: unknown) => void, + ): void; +} + +export interface LynxGlobal { + __QueryComponent?( + source: string, + callback: (result: { + code: number; + data?: { evalResult?: unknown; url?: string }; + }) => void, + ): { evalResult: unknown } | null | undefined; + lynx?: LynxRuntime; + lynxCoreInject?: { + tt?: { + getDynamicComponentExports?(schema: string): unknown; + }; + }; + [LYNX_BUNDLE_REGISTRY]?: Map; + [name: string]: unknown; +} + +declare const lynx: LynxRuntime | undefined; +declare const __MAIN_THREAD__: boolean | undefined; + +export const getLynxRuntime = ( + globalObject: LynxGlobal, +): LynxRuntime | undefined => + globalObject.lynx ?? (typeof lynx === 'undefined' ? undefined : lynx); + +export const MAIN_THREAD_EXPOSE_SUFFIX = '__main_thread'; + +export const isRecord = (value: unknown): value is Record => + typeof value === 'object' && value !== null; + +const isRemoteEntryExports = (value: unknown): value is RemoteEntryExports => + isRecord(value) && + typeof value.get === 'function' && + typeof value.init === 'function'; + +const getDefaultExport = (value: unknown): unknown => + isRecord(value) ? value.default : undefined; + +const getRemoteEntryGlobalCandidates = ( + entryGlobalName: string, + globalObject: LynxGlobal, + alternateGlobalName?: string, +): unknown[] => { + const globalValue = globalObject[entryGlobalName]; + const alternateGlobalValue = alternateGlobalName + ? globalObject[alternateGlobalName] + : undefined; + + return [ + globalValue, + getDefaultExport(globalValue), + alternateGlobalValue, + getDefaultExport(alternateGlobalValue), + ]; +}; + +export const snapshotRemoteEntryGlobals = ( + entryGlobalName: string, + globalObject: LynxGlobal, + alternateGlobalName?: string, +): ReadonlySet => + new Set( + getRemoteEntryGlobalCandidates( + entryGlobalName, + globalObject, + alternateGlobalName, + ).filter(isRemoteEntryExports), + ); + +export const findRemoteEntryExports = ( + value: unknown, + entryGlobalName: string, + globalObject: LynxGlobal, + alternateGlobalName?: string, + previousGlobalExports: ReadonlySet = new Set(), +): RemoteEntryExports | undefined => { + const loadedExports = [value, getDefaultExport(value)].find( + isRemoteEntryExports, + ); + if (loadedExports) { + return loadedExports; + } + + return getRemoteEntryGlobalCandidates( + entryGlobalName, + globalObject, + alternateGlobalName, + ).find( + (candidate): candidate is RemoteEntryExports => + isRemoteEntryExports(candidate) && !previousGlobalExports.has(candidate), + ); +}; + +export const getLynxRealm = (lynx: LynxRuntime): LynxRealm => + typeof __MAIN_THREAD__ !== 'undefined' + ? __MAIN_THREAD__ + ? 'main-thread' + : 'background' + : typeof lynx.requireModuleAsync === 'function' || + typeof lynx.getNativeApp === 'function' + ? 'background' + : 'main-thread'; + +export const getRegistryKey = ( + entryGlobalName: string, + realm: LynxRealm, +): string => + realm === 'background' ? entryGlobalName : `${entryGlobalName}__main_thread`; + +export const getRemoteOriginKey = (entryGlobalName: string): string => + `${entryGlobalName}${LYNX_REMOTE_ORIGIN_SUFFIX}`; + +export const getBundleRegistry = ( + globalObject: LynxGlobal, +): Map => { + let registry = globalObject[LYNX_BUNDLE_REGISTRY]; + if (!registry) { + registry = new Map(); + globalObject[LYNX_BUNDLE_REGISTRY] = registry; + } + return registry; +}; + +export const toErrorMessage = (error: unknown): string => + error instanceof Error ? error.message : String(error); diff --git a/packages/lynx/src/runtimeEntryLoader.ts b/packages/lynx/src/runtimeEntryLoader.ts new file mode 100644 index 00000000000..39c121b5e2c --- /dev/null +++ b/packages/lynx/src/runtimeEntryLoader.ts @@ -0,0 +1,398 @@ +import type { + RemoteEntryExports, + RemoteEntryInitOptions, +} from '@module-federation/runtime-core/types'; + +import { + findRemoteEntryExports, + getBundleRegistry, + getRemoteOriginKey, + getRegistryKey, + isRecord, + MAIN_THREAD_EXPOSE_SUFFIX, + snapshotRemoteEntryGlobals, + toErrorMessage, + type LynxGlobal, + type LynxRealm, + type LynxRuntime, +} from './runtimeCore'; +import { loadWithTimeout } from './runtimeTimeout'; + +const DEFAULT_TIMEOUT = 30_000; +export const PREPARE_REMOTE_ENTRY_MTS = + 'rModuleFederationPrepareRemoteEntryMTS'; + +export const loadScriptForEntry = ( + lynx: LynxRuntime, + sectionPath: string, + bundleName: string, + entry: string, + globalObject: LynxGlobal, +): unknown => { + const hadEntry = Object.prototype.hasOwnProperty.call( + globalObject, + 'globDynamicComponentEntry', + ); + const previousEntry = globalObject.globDynamicComponentEntry; + globalObject.globDynamicComponentEntry = entry; + try { + return lynx.loadScript!(sectionPath, { bundleName }); + } finally { + if (hadEntry) { + globalObject.globDynamicComponentEntry = previousEntry; + } else { + delete globalObject.globDynamicComponentEntry; + } + } +}; + +const preparePairedMainThreadEntry = ( + lynx: LynxRuntime, + entry: string, + entryGlobalName: string, + bundleName: string, +): Promise => { + const nativeApp = lynx.getNativeApp?.(); + if (!isRecord(nativeApp)) { + return Promise.resolve(); + } + const callLepusMethod = nativeApp.callLepusMethod; + if (typeof callLepusMethod !== 'function') { + return Promise.resolve(); + } + + return new Promise((resolve, reject) => { + try { + callLepusMethod.call( + nativeApp, + PREPARE_REMOTE_ENTRY_MTS, + { + bundleName, + entry, + sectionPath: `${entryGlobalName}__main-thread`, + }, + (result: unknown) => { + if (result === false) { + reject( + new Error( + `Lynx remote bundle "${entryGlobalName}" did not expose its paired main-thread entry.`, + ), + ); + return; + } + resolve(); + }, + ); + } catch (error) { + reject(error); + } + }); +}; + +interface LynxBundleResponse { + code: number; + url: string; + errorMsg?: string; +} + +export const isBundleEntry = (entry: string): boolean => + entry.split(/[?#]/, 1)[0].endsWith('.lynx.bundle'); + +export const getTimeout = (timeout: number | undefined): number => + timeout !== undefined && Number.isFinite(timeout) && timeout >= 0 + ? timeout + : DEFAULT_TIMEOUT; + +const getBundleResponse = (value: unknown): LynxBundleResponse | undefined => { + if ( + !isRecord(value) || + typeof value.code !== 'number' || + typeof value.url !== 'string' + ) { + return undefined; + } + + return { + code: value.code, + url: value.url, + errorMsg: + typeof value.errorMsg === 'string' + ? value.errorMsg + : typeof value.error_msg === 'string' + ? value.error_msg + : undefined, + }; +}; + +export const loadJavaScriptEntry = ( + lynx: LynxRuntime, + entry: string, + entryGlobalName: string, + globalObject: LynxGlobal, + timeout: number, +): Promise => { + const requireModuleAsync = lynx.requireModuleAsync; + if (!requireModuleAsync) { + throw new Error( + 'Lynx federation requires globalThis.lynx.requireModuleAsync to load JavaScript remote entries in the background runtime.', + ); + } + + const previousGlobalExports = snapshotRemoteEntryGlobals( + entryGlobalName, + globalObject, + ); + + return loadWithTimeout( + timeout, + `Timed out loading Lynx remote entry "${entryGlobalName}" from "${entry}" after ${timeout}ms.`, + (resolve, reject, isSettled) => { + requireModuleAsync(entry, (error, value) => { + if (isSettled()) { + return; + } + if (error) { + reject( + new Error( + `Failed to load Lynx remote entry "${entryGlobalName}" from "${entry}": ${toErrorMessage(error)}`, + ), + ); + return; + } + + const exports = findRemoteEntryExports( + value, + entryGlobalName, + globalObject, + undefined, + previousGlobalExports, + ); + if (!exports) { + reject( + new Error( + `Lynx remote entry "${entryGlobalName}" loaded from "${entry}" but did not export a Module Federation container.`, + ), + ); + return; + } + + resolve(exports); + }); + }, + ); +}; + +const adaptContainerToRealm = ( + container: RemoteEntryExports, + realm: LynxRealm, + realmLayer: string, +): RemoteEntryExports => ({ + get: (request) => + container.get( + realm === 'background' + ? request + : `${request}${MAIN_THREAD_EXPOSE_SUFFIX}`, + ), + init: (shareScope, initScope, options) => { + if (!isRecord(options) || !Array.isArray(options.shareScopeKeys)) { + return container.init(shareScope, initScope, options); + } + + const shareScopeKeys = options.shareScopeKeys.filter( + (value): value is string => typeof value === 'string', + ); + const realmSuffix = `:${realmLayer}`; + const realmShareScopeKeys = shareScopeKeys.filter((key) => + key.endsWith(realmSuffix), + ); + const primaryShareScopeKey = realmShareScopeKeys[0]; + if (!primaryShareScopeKey) { + return container.init(shareScope, initScope, options); + } + + const narrowedOptions = Object.create( + Object.getPrototypeOf(options), + Object.getOwnPropertyDescriptors(options), + ) as RemoteEntryInitOptions; + Object.defineProperty(narrowedOptions, 'shareScopeKeys', { + configurable: true, + enumerable: true, + value: + realmShareScopeKeys.length === 1 + ? primaryShareScopeKey + : realmShareScopeKeys, + writable: true, + }); + const shareScopeMap = options.shareScopeMap; + const realmShareScope = isRecord(shareScopeMap) + ? shareScopeMap[primaryShareScopeKey] + : undefined; + return container.init( + isRecord(realmShareScope) ? realmShareScope : shareScope, + initScope, + narrowedOptions, + ); + }, +}); + +export const loadBundleEntry = ( + lynx: LynxRuntime, + entry: string, + entryGlobalName: string, + realm: LynxRealm, + realmLayer: string, + globalObject: LynxGlobal, + timeout: number, +): Promise => { + const { fetchBundle, loadScript } = lynx; + if (!fetchBundle || !loadScript) { + throw new Error( + 'Lynx federation requires globalThis.lynx.fetchBundle and globalThis.lynx.loadScript to load .lynx.bundle remote entries.', + ); + } + + let rollbackRegistry: (() => void) | undefined; + return loadWithTimeout( + timeout, + `Timed out loading Lynx remote bundle "${entryGlobalName}" from "${entry}" after ${timeout}ms.`, + (resolve, reject, isSettled) => { + Promise.resolve(fetchBundle(entry)).then( + (value) => { + if (isSettled()) { + return; + } + + const response = getBundleResponse(value); + if (!response || response.code !== 0 || !response.url) { + const details = response + ? `code ${response.code}${response.errorMsg ? `: ${response.errorMsg}` : ''}` + : 'an invalid response'; + reject( + new Error( + `Failed to fetch Lynx remote bundle "${entryGlobalName}" from "${entry}": ${details}.`, + ), + ); + return; + } + + const registry = getBundleRegistry(globalObject); + const updates = [ + [entryGlobalName, response.url], + [getRemoteOriginKey(entryGlobalName), entry], + [getRegistryKey(entryGlobalName, 'main-thread'), response.url], + ] as const; + const previous = updates.map(([key, nextValue]) => ({ + hadValue: registry.has(key), + key, + nextValue, + previousValue: registry.get(key), + })); + for (const [key, value] of updates) { + registry.set(key, value); + } + rollbackRegistry = () => { + for (const state of previous) { + if (registry.get(state.key) !== state.nextValue) { + continue; + } + if (state.hadValue) { + registry.set(state.key, state.previousValue!); + } else { + registry.delete(state.key); + } + } + }; + + const sectionPath = + realm === 'background' + ? entryGlobalName + : `${entryGlobalName}__main-thread`; + const alternateGlobalName = getRegistryKey(entryGlobalName, realm); + const previousGlobalExports = snapshotRemoteEntryGlobals( + entryGlobalName, + globalObject, + alternateGlobalName, + ); + + try { + const value = loadScriptForEntry( + lynx, + sectionPath, + response.url, + entry, + globalObject, + ); + Promise.resolve(value).then( + async (loadedValue) => { + if (isSettled()) { + return; + } + const exports = findRemoteEntryExports( + loadedValue, + entryGlobalName, + globalObject, + alternateGlobalName, + previousGlobalExports, + ); + if (!exports) { + reject( + new Error( + `Lynx remote bundle "${entryGlobalName}" loaded from "${entry}" but did not export a Module Federation container.`, + ), + ); + return; + } + try { + if (realm === 'background') { + await preparePairedMainThreadEntry( + lynx, + entry, + entryGlobalName, + response.url, + ); + } + } catch (error) { + reject( + new Error( + `Failed to prepare Lynx remote bundle "${entryGlobalName}" from "${entry}" on the main thread: ${toErrorMessage(error)}`, + ), + ); + return; + } + if (isSettled()) { + return; + } + rollbackRegistry = undefined; + resolve(adaptContainerToRealm(exports, realm, realmLayer)); + }, + (error) => + reject( + new Error( + `Failed to evaluate Lynx remote bundle "${entryGlobalName}" from "${entry}": ${toErrorMessage(error)}`, + ), + ), + ); + } catch (error) { + reject( + new Error( + `Failed to evaluate Lynx remote bundle "${entryGlobalName}" from "${entry}": ${toErrorMessage(error)}`, + ), + ); + } + }, + (error) => { + if (!isSettled()) { + reject( + new Error( + `Failed to fetch Lynx remote bundle "${entryGlobalName}" from "${entry}": ${toErrorMessage(error)}`, + ), + ); + } + }, + ); + }, + ).catch((error) => { + rollbackRegistry?.(); + throw error; + }); +}; diff --git a/packages/lynx/src/runtimeLazyBundleLoading.test.ts b/packages/lynx/src/runtimeLazyBundleLoading.test.ts new file mode 100644 index 00000000000..1d576a32d49 --- /dev/null +++ b/packages/lynx/src/runtimeLazyBundleLoading.test.ts @@ -0,0 +1,567 @@ +import { describe, expect, it, rs } from '@rstest/core'; + +import { + createLazyChunkLoadController, + type InstalledChunk, + type LynxChunk, +} from './lazyChunkLoadController'; +import { LYNX_BUNDLE_REGISTRY, patchLynxChunkLoading } from './runtimePlugin'; +import { + createGlobalObject, + createWebpackRequire, + makeSynchronousThenable, + remoteRegistry, +} from './runtimeChunkLoading.testUtils'; + +describe('patchLynxChunkLoading lazy bundle loading', () => { + it('settles overlapping loads from the first bundle containing both chunks', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { + first: 'async/first.bundle', + second: 'async/second.bundle', + }; + const resolvers = new Map void>(); + const globalObject = createGlobalObject( + (request) => + new Promise((resolve) => { + resolvers.set(request, resolve); + }), + ); + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const first: PromiseLike[] = []; + const second: PromiseLike[] = []; + webpackRequire.f.j!('first', first); + webpackRequire.f.j!('second', second); + + const firstFactory = rs.fn(); + const firstRuntime = rs.fn(); + resolvers.get('https://cdn.example/remotes/async/first.bundle')!({ + ids: ['first', 'second'], + modules: { feature: firstFactory }, + runtime: firstRuntime, + }); + await Promise.all([first[0], second[0]]); + + expect(webpackRequire.m.feature).toBe(firstFactory); + expect(firstRuntime).toHaveBeenCalledTimes(1); + + const staleRuntime = rs.fn(); + resolvers.get('https://cdn.example/remotes/async/second.bundle')!({ + ids: ['second'], + modules: { stale: rs.fn() }, + runtime: staleRuntime, + }); + await Promise.resolve(); + expect(webpackRequire.m.stale).toBeUndefined(); + expect(staleRuntime).not.toHaveBeenCalled(); + }); + + it('preserves the official synchronous lazy-bundle thenable', () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: 'async/Card.bundle' }; + const factory = rs.fn(); + const globalObject = createGlobalObject(() => + makeSynchronousThenable({ + ids: ['feature'], + modules: { factory }, + }), + ); + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + + expect(webpackRequire.m.factory).toBe(factory); + let observed = false; + promises[0].then(() => { + observed = true; + }); + expect(observed).toBe(true); + }); + + it('controller installs a synchronous bundle before returning and notifies its observer synchronously', () => { + const installedChunks: Record = {}; + const modules: Record = {}; + const factory = rs.fn(); + const chunk = { + ids: ['feature'], + modules: { factory }, + } satisfies LynxChunk; + const controller = createLazyChunkLoadController({ + chunkKey: 'feature', + installedChunks, + timeout: 5, + loadQueryComponent: () => makeSynchronousThenable(chunk), + isChunk: (value): value is LynxChunk => value === chunk, + installChunkAfterConsumes: (value, isCurrent) => { + if (isCurrent()) { + Object.assign(modules, value.modules); + for (const id of value.ids) { + installedChunks[String(id)] = 0; + } + } + }, + }); + + const promise = controller.load('async/Card.bundle'); + expect(modules.factory).toBe(factory); + let observed = false; + promise.then(() => { + observed = true; + }); + expect(observed).toBe(true); + }); + + it('controller cannot delete a later retry tuple after stale timed-out consumes reject', async () => { + const installedChunks: Record = {}; + const modules: Record = {}; + const factory = rs.fn(); + const chunk = { + ids: ['feature'], + modules: { factory }, + } satisfies LynxChunk; + const controller = (consumes: Promise) => + createLazyChunkLoadController({ + chunkKey: 'feature', + installedChunks, + timeout: 5, + loadQueryComponent: () => makeSynchronousThenable(chunk), + isChunk: (value): value is LynxChunk => value === chunk, + installChunkAfterConsumes: (value, isCurrent) => + consumes.then(() => { + if (isCurrent()) { + Object.assign(modules, value.modules); + for (const id of value.ids) { + installedChunks[String(id)] = 0; + } + } + }), + }); + let rejectExpired!: (error: Error) => void; + const expiredConsumes = new Promise((_resolve, reject) => { + rejectExpired = reject; + }); + const expired = controller(expiredConsumes).load('async/Card.bundle'); + await expect(expired).rejects.toThrow('Timed out loading Lynx lazy bundle'); + + let resolveFresh!: () => void; + const freshConsumes = new Promise((resolve) => { + resolveFresh = resolve; + }); + const fresh = controller(freshConsumes).load('async/Card.bundle'); + const freshTuple = installedChunks.feature; + rejectExpired(new Error('stale consume failed')); + await Promise.resolve(); + expect(installedChunks.feature).toBe(freshTuple); + resolveFresh(); + await expect(fresh).resolves.toBeDefined(); + expect(modules.factory).toBe(factory); + }); + + it('evicts synchronous lazy-bundle failures so they can be retried', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: 'async/Card.bundle' }; + const factory = rs.fn(); + let attempt = 0; + const globalObject = createGlobalObject(() => { + if (attempt++ === 0) { + throw new Error('synchronous decode failed'); + } + return makeSynchronousThenable({ + ids: ['feature'], + modules: { factory }, + }); + }); + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const failed: PromiseLike[] = []; + webpackRequire.f.j!('feature', failed); + await expect(failed[0]).rejects.toThrow('synchronous decode failed'); + + const retried: PromiseLike[] = []; + webpackRequire.f.j!('feature', retried); + await expect(retried[0]).resolves.toBeDefined(); + expect(webpackRequire.m.factory).toBe(factory); + }); + + it('waits for shared consumes from a synchronous lazy bundle', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: 'async/Card.bundle' }; + webpackRequire.consumesLoadingData = { + chunkMapping: { feature: ['shared-state'] }, + }; + let resolveConsume!: () => void; + const consume = new Promise((resolve) => { + resolveConsume = () => { + webpackRequire.m['shared-state'] = rs.fn(); + resolve(); + }; + }); + webpackRequire.f.consumes = (_chunkId, promises) => { + promises.push(consume); + }; + const factory = rs.fn(); + const globalObject = createGlobalObject(() => + makeSynchronousThenable({ + ids: ['feature'], + modules: { factory }, + }), + ); + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + + let observed = false; + promises[0].then(() => { + observed = true; + }); + expect(observed).toBe(false); + expect(webpackRequire.m.factory).toBeUndefined(); + resolveConsume(); + await expect(promises[0]).resolves.toBeDefined(); + expect(webpackRequire.m.factory).toBe(factory); + }); + + it('does not let a stale consume rejection cancel a retry', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: 'async/Card.bundle' }; + webpackRequire.consumesLoadingData = { + chunkMapping: { feature: ['shared-state'] }, + }; + let rejectExpired!: (error: Error) => void; + const expiredConsume = new Promise((_resolve, reject) => { + rejectExpired = reject; + }); + let resolveFresh!: () => void; + const freshConsume = new Promise((resolve) => { + resolveFresh = () => { + webpackRequire.m['shared-state'] = rs.fn(); + resolve(); + }; + }); + let consumeAttempt = 0; + webpackRequire.f.consumes = (_chunkId, promises) => { + promises.push(consumeAttempt++ === 0 ? expiredConsume : freshConsume); + }; + const factory = rs.fn(); + const globalObject = createGlobalObject(() => + makeSynchronousThenable({ + ids: ['feature'], + modules: { factory }, + }), + ); + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject, 5); + const expired: PromiseLike[] = []; + webpackRequire.f.j!('feature', expired); + await expect(expired[0]).rejects.toThrow( + 'Timed out loading Lynx lazy bundle', + ); + + const fresh: PromiseLike[] = []; + webpackRequire.f.j!('feature', fresh); + rejectExpired(new Error('stale consume failed')); + await Promise.resolve(); + resolveFresh(); + await expect(fresh[0]).resolves.toBeDefined(); + expect(webpackRequire.m.factory).toBe(factory); + }); + + it('ignores a stale lazy bundle that resolves after timeout', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: 'async/Card.bundle' }; + const resolvers: Array<(value: unknown) => void> = []; + const globalObject = createGlobalObject( + () => + new Promise((resolve) => { + resolvers.push(resolve); + }), + ); + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject, 5); + const expired: PromiseLike[] = []; + webpackRequire.f.j!('feature', expired); + await expect(expired[0]).rejects.toThrow( + 'Timed out loading Lynx lazy bundle', + ); + + const fresh: PromiseLike[] = []; + webpackRequire.f.j!('feature', fresh); + resolvers[0]({ + ids: ['feature'], + modules: { factory: rs.fn() }, + }); + await Promise.resolve(); + expect(webpackRequire.m.factory).toBeUndefined(); + + const freshFactory = rs.fn(); + resolvers[1]({ ids: ['feature'], modules: { factory: freshFactory } }); + await expect(fresh[0]).resolves.toBeDefined(); + expect(webpackRequire.m.factory).toBe(freshFactory); + }); + + it('uses the official lazy-bundle API for split chunks', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: 'async/Card.bundle' }; + const factory = rs.fn(); + const fetchBundle = rs.fn(async () => ({ + code: 0, + url: 'lynx-cache://Card', + })); + const loadLazyBundle = rs.fn(async () => ({ + ids: ['feature'], + modules: { factory }, + })); + const loadScript = rs.fn(() => ({ + ids: ['feature'], + modules: { factory }, + })); + const globalObject = { + lynx: { + fetchBundle, + loadLazyBundle, + loadScript, + requireModuleAsync: rs.fn(), + }, + [LYNX_BUNDLE_REGISTRY]: new Map([ + ['remote', 'lynx-cache://catalog'], + [ + 'remote:remote-origin', + 'https://cdn.example/remotes/catalog.lynx.bundle', + ], + ]), + }; + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + await Promise.all(promises); + + expect(loadLazyBundle).toHaveBeenCalledWith( + 'https://cdn.example/remotes/async/Card.bundle', + undefined, + 'https://cdn.example/remotes/catalog.lynx.bundle', + ); + expect(fetchBundle).not.toHaveBeenCalled(); + expect(loadScript).not.toHaveBeenCalled(); + expect(webpackRequire.m.factory).toBe(factory); + }); + + it('uses the Web native Lynx lazy-bundle API before React starts', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: 'lazy-bundle/Feature.bundle' }; + const factory = rs.fn(); + const loadLazyBundle = rs.fn(async () => ({ + ids: ['feature'], + modules: { factory }, + })); + const globalObject = { + lynx: { + getNativeLynx: () => ({ loadLazyBundle }), + loadScript: rs.fn(), + requireModuleAsync: rs.fn(), + }, + [LYNX_BUNDLE_REGISTRY]: new Map([ + ['remote', 'lynx-cache://catalog'], + [ + 'remote:remote-origin', + 'https://cdn.example/remotes/catalog.lynx.bundle', + ], + ]), + }; + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + await Promise.all(promises); + + expect(loadLazyBundle).toHaveBeenCalledWith( + 'https://cdn.example/remotes/lazy-bundle/Feature.bundle', + undefined, + 'https://cdn.example/remotes/catalog.lynx.bundle', + ); + expect(webpackRequire.m.factory).toBe(factory); + }); + + it('times out split chunks and permits retry', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: 'async/Card.bundle' }; + const loadLazyBundle = rs.fn(() => new Promise(() => undefined)); + const globalObject = { + lynx: { loadLazyBundle, loadScript: rs.fn() }, + [LYNX_BUNDLE_REGISTRY]: new Map([ + ['remote', 'lynx-cache://catalog'], + [ + 'remote:remote-origin', + 'https://cdn.example/remotes/catalog.lynx.bundle', + ], + ]), + }; + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject, 5); + for (let attempt = 0; attempt < 2; attempt += 1) { + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + await expect(Promise.all(promises)).rejects.toThrow( + 'Timed out loading Lynx lazy bundle', + ); + } + expect(loadLazyBundle).toHaveBeenCalledTimes(2); + }); + + it('rejects split chunks when no DynamicComponent API exists', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: 'async/Card.bundle' }; + const fetchBundle = rs.fn(async () => ({ + code: 0, + url: 'lynx-cache://Card', + })); + const loadScript = rs.fn(); + const globalObject = { + lynx: { fetchBundle, loadScript, requireModuleAsync: rs.fn() }, + [LYNX_BUNDLE_REGISTRY]: new Map([ + ['remote', 'lynx-cache://catalog'], + [ + 'remote:remote-origin', + 'https://cdn.example/remotes/catalog.lynx.bundle', + ], + ]), + }; + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + await expect(Promise.all(promises)).rejects.toThrow( + 'requires QueryComponent', + ); + + expect(fetchBundle).not.toHaveBeenCalled(); + expect(loadScript).not.toHaveBeenCalled(); + }); + + it('loads split chunks through QueryComponent when loadLazyBundle is not callable', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { + feature: 'async/Card.123.bundle', + }; + const factory = rs.fn(); + const chunk = { + ids: ['feature'], + modules: { factory }, + }; + const QueryComponent = rs.fn((_source, callback) => + callback({ code: 0, detail: { schema: 'Card' } }), + ); + const loadScript = rs.fn(); + const globalObject = { + lynxCoreInject: { + tt: { getDynamicComponentExports: () => chunk }, + }, + lynx: { + loadScript, + loadLazyBundle: true as never, + QueryComponent, + requireModuleAsync: rs.fn(), + }, + [LYNX_BUNDLE_REGISTRY]: new Map([ + ['remote', 'lynx-cache://catalog'], + [ + 'remote:remote-origin', + 'https://cdn.example/remotes/catalog.lynx.bundle', + ], + ]), + }; + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + await Promise.all(promises); + + expect(QueryComponent).toHaveBeenCalledWith( + 'https://cdn.example/remotes/async/Card.123.bundle', + expect.any(Function), + ); + expect(loadScript).not.toHaveBeenCalled(); + expect(webpackRequire.m.factory).toBe(factory); + }); + + it('rejects direct chunk exports from background QueryComponent', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { + feature: 'lazy-bundle/Feature.bundle', + }; + const factory = rs.fn(); + const chunk = { + ids: ['feature'], + modules: { factory }, + }; + const QueryComponent = rs.fn((_source, callback) => callback(chunk)); + const globalObject = { + lynx: { + loadScript: rs.fn(), + QueryComponent, + requireModuleAsync: rs.fn(), + }, + [LYNX_BUNDLE_REGISTRY]: new Map([ + ['remote', 'lynx-cache://catalog'], + [ + 'remote:remote-origin', + 'https://cdn.example/remotes/catalog.lynx.bundle', + ], + ]), + }; + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + await expect(Promise.all(promises)).rejects.toThrow( + 'Failed to load Lynx lazy bundle', + ); + + expect(QueryComponent).toHaveBeenCalledWith( + 'https://cdn.example/remotes/lazy-bundle/Feature.bundle', + expect.any(Function), + ); + expect(webpackRequire.m.factory).toBeUndefined(); + }); + + it('loads main-thread split chunks through the asynchronous Web QueryComponent API', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: 'async/Card.bundle' }; + const factory = rs.fn(); + const chunk = { ids: ['feature'], modules: { factory } }; + const queryComponent = rs.fn((_source, callback) => { + queueMicrotask(() => + callback({ + code: 0, + data: { evalResult: chunk, url: 'https://cdn.example/Card.bundle' }, + }), + ); + return null; + }); + const globalObject = { + __QueryComponent: queryComponent, + lynx: { loadScript: rs.fn() }, + [LYNX_BUNDLE_REGISTRY]: new Map([ + ['remote', 'lynx-cache://catalog'], + [ + 'remote:remote-origin', + 'https://cdn.example/remotes/catalog.lynx.bundle', + ], + ]), + }; + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + await Promise.all(promises); + + expect(queryComponent).toHaveBeenCalledWith( + 'https://cdn.example/remotes/async/Card.bundle', + expect.any(Function), + ); + expect(webpackRequire.m.factory).toBe(factory); + }); +}); diff --git a/packages/lynx/src/runtimePlugin.test.ts b/packages/lynx/src/runtimePlugin.test.ts new file mode 100644 index 00000000000..5010a5d802c --- /dev/null +++ b/packages/lynx/src/runtimePlugin.test.ts @@ -0,0 +1,585 @@ +import { afterEach, describe, expect, it, rs } from '@rstest/core'; +import { ModuleFederation } from '@module-federation/runtime-core'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime-core/types'; + +import lynxRuntimePlugin, { LYNX_BUNDLE_REGISTRY } from './runtimePlugin'; + +type NativeCallback = (error: unknown, value: unknown) => void; + +interface TestLynx { + fetchBundle?(entry: string): PromiseLike; + getNativeApp?(): unknown; + loadScript?(sectionPath: string, options: { bundleName: string }): unknown; + requireModuleAsync?(entry: string, callback: NativeCallback): void; +} + +const PREPARE_REMOTE_ENTRY_MTS = 'rModuleFederationPrepareRemoteEntryMTS'; + +type LoadEntryArgs = Parameters< + NonNullable +>[0]; +type GeneratePreloadAssetsArgs = Parameters< + NonNullable +>[0]; + +const remoteInfo = { + name: 'remote', + entry: 'https://example.test/remoteEntry.js', + entryGlobalName: 'remote', + type: 'lynx-js', + shareScope: 'default', +}; + +const bundleRemoteInfo = { + ...remoteInfo, + entry: 'https://example.test/remote.lynx.bundle', +}; + +const createContainer = () => ({ + get: rs.fn(), + init: rs.fn(), +}); + +const setLynx = (lynx: TestLynx): void => { + (globalThis as unknown as Record).lynx = lynx; +}; + +const loadEntry = (plugin: ModuleFederationRuntimePlugin, info = remoteInfo) => + plugin.loadEntry!({ remoteInfo: info } as LoadEntryArgs); + +afterEach(() => { + const globalRecord = globalThis as unknown as Record; + delete globalRecord.lynx; + delete globalRecord.remote; + delete globalRecord.globDynamicComponentEntry; + delete globalRecord[PREPARE_REMOTE_ENTRY_MTS]; + delete globalRecord[LYNX_BUNDLE_REGISTRY]; + const globalLoading = globalRecord.__GLOBAL_LOADING_REMOTE_ENTRY__ as + | Record | undefined> + | undefined; + if (globalLoading) { + for (const key of Object.keys(globalLoading)) { + delete globalLoading[key]; + } + } + rs.restoreAllMocks(); +}); + +describe('lynxRuntimePlugin entry loading', () => { + it('loads plain JavaScript entries in the background realm', async () => { + const container = createContainer(); + const requireModuleAsync = rs.fn( + (_entry: string, callback: NativeCallback) => callback(null, container), + ); + setLynx({ requireModuleAsync }); + + await expect(loadEntry(lynxRuntimePlugin())).resolves.toBe(container); + expect(requireModuleAsync).toHaveBeenCalledWith( + remoteInfo.entry, + expect.any(Function), + ); + }); + + it('accepts default and global container exports', async () => { + const defaultContainer = createContainer(); + setLynx({ + requireModuleAsync: (_entry, callback) => + callback(null, { default: defaultContainer }), + }); + await expect(loadEntry(lynxRuntimePlugin())).resolves.toBe( + defaultContainer, + ); + + const globalContainer = createContainer(); + setLynx({ + requireModuleAsync: (_entry, callback) => { + (globalThis as unknown as Record).remote = { + default: globalContainer, + }; + callback(null, undefined); + }, + }); + await expect(loadEntry(lynxRuntimePlugin())).resolves.toBe(globalContainer); + }); + + it('rejects a pre-existing container global for a newly loaded URL', async () => { + const staleContainer = createContainer(); + (globalThis as unknown as Record).remote = staleContainer; + setLynx({ + fetchBundle: async () => ({ code: 0, url: 'lynx-cache://new-remote' }), + loadScript: () => undefined, + }); + + await expect( + loadEntry(lynxRuntimePlugin(), bundleRemoteInfo), + ).rejects.toThrow('did not export a Module Federation container'); + }); + + it('deduplicates concurrent entry loads', async () => { + const container = createContainer(); + let finishLoad: NativeCallback | undefined; + const requireModuleAsync = rs.fn( + (_entry: string, callback: NativeCallback) => { + finishLoad = callback; + }, + ); + setLynx({ requireModuleAsync }); + + const plugin = lynxRuntimePlugin(); + const first = loadEntry(plugin); + const second = loadEntry(plugin); + finishLoad!(null, container); + + await expect(first).resolves.toBe(container); + await expect(second).resolves.toBe(container); + expect(requireModuleAsync).toHaveBeenCalledTimes(1); + }); + + it('loads bundle entries from the background section', async () => { + const container = createContainer(); + const callLepusMethod = rs.fn( + (_name: string, _payload: unknown, callback: () => void) => callback(), + ); + const fetchBundle = rs.fn(async () => ({ + code: 0, + url: 'lynx-cache://remote', + })); + const loadScript = rs.fn(async () => container); + setLynx({ + fetchBundle, + getNativeApp: () => ({ callLepusMethod }), + loadScript, + }); + + const loadedContainer = await loadEntry( + lynxRuntimePlugin(), + bundleRemoteInfo, + ); + expect(loadedContainer).not.toBe(container); + loadedContainer.get('./Card'); + expect(container.get).toHaveBeenCalledWith('./Card'); + expect(fetchBundle).toHaveBeenCalledWith(bundleRemoteInfo.entry); + expect(loadScript).toHaveBeenCalledWith('remote', { + bundleName: 'lynx-cache://remote', + }); + expect(callLepusMethod).toHaveBeenCalledWith( + PREPARE_REMOTE_ENTRY_MTS, + { + bundleName: 'lynx-cache://remote', + entry: bundleRemoteInfo.entry, + sectionPath: 'remote__main-thread', + }, + expect.any(Function), + ); + expect( + ( + globalThis as unknown as Record< + PropertyKey, + Map | undefined + > + )[LYNX_BUNDLE_REGISTRY]?.get('remote'), + ).toBe('lynx-cache://remote'); + }); + + it('prepares paired remote containers in the main-thread realm', () => { + const container = createContainer(); + const globalRecord = globalThis as unknown as Record; + globalRecord.globDynamicComponentEntry = '__Card__'; + const loadScript = rs.fn(() => { + expect(globalRecord.globDynamicComponentEntry).toBe( + bundleRemoteInfo.entry, + ); + return container; + }); + setLynx({ loadScript }); + + lynxRuntimePlugin(); + const prepare = globalRecord[PREPARE_REMOTE_ENTRY_MTS] as ( + payload: Record, + ) => unknown; + + expect(prepare).toBeTypeOf('function'); + expect( + prepare({ + bundleName: 'lynx-cache://remote', + entry: bundleRemoteInfo.entry, + sectionPath: 'remote__main-thread', + }), + ).toBe(true); + expect(loadScript).toHaveBeenCalledWith('remote__main-thread', { + bundleName: 'lynx-cache://remote', + }); + expect(globalRecord.globDynamicComponentEntry).toBe('__Card__'); + }); + + it('reports paired main-thread preparation failures immediately', async () => { + const registry = new Map(); + ( + globalThis as unknown as Record< + PropertyKey, + Map | undefined + > + )[LYNX_BUNDLE_REGISTRY] = registry; + setLynx({ + fetchBundle: async () => ({ code: 0, url: 'lynx-cache://remote' }), + getNativeApp: () => ({ + callLepusMethod: () => { + throw new Error('main-thread preparation failed'); + }, + }), + loadScript: () => createContainer(), + }); + + await expect( + loadEntry(lynxRuntimePlugin(), bundleRemoteInfo), + ).rejects.toThrow('main-thread preparation failed'); + expect(registry.size).toBe(0); + }); + + it('rejects a remote whose paired main-thread entry is missing', async () => { + const registry = new Map(); + ( + globalThis as unknown as Record< + PropertyKey, + Map | undefined + > + )[LYNX_BUNDLE_REGISTRY] = registry; + setLynx({ + fetchBundle: async () => ({ code: 0, url: 'lynx-cache://remote' }), + getNativeApp: () => ({ + callLepusMethod: ( + _name: string, + _payload: unknown, + callback: (result: boolean) => void, + ) => callback(false), + }), + loadScript: () => createContainer(), + }); + + await expect( + loadEntry(lynxRuntimePlugin(), bundleRemoteInfo), + ).rejects.toThrow('did not expose its paired main-thread entry'); + expect(registry.size).toBe(0); + }); + + it('loads bundle entries from the main-thread section', async () => { + const container = createContainer(); + const globalRecord = globalThis as unknown as Record; + globalRecord.globDynamicComponentEntry = '__Card__'; + const loadScript = rs.fn(() => { + expect(globalRecord.globDynamicComponentEntry).toBe( + bundleRemoteInfo.entry, + ); + return { default: container }; + }); + setLynx({ + fetchBundle: async () => ({ code: 0, url: 'lynx-cache://remote' }), + loadScript, + }); + + const loadedContainer = await loadEntry( + lynxRuntimePlugin(), + bundleRemoteInfo, + ); + expect(loadedContainer).not.toBe(container); + loadedContainer.get('./Card'); + expect(container.get).toHaveBeenCalledWith('./Card__main_thread'); + expect(loadScript).toHaveBeenCalledWith('remote__main-thread', { + bundleName: 'lynx-cache://remote', + }); + expect(globalRecord.globDynamicComponentEntry).toBe('__Card__'); + expect( + ( + globalThis as unknown as Record< + PropertyKey, + Map | undefined + > + )[LYNX_BUNDLE_REGISTRY]?.get('remote__main_thread'), + ).toBe('lynx-cache://remote'); + expect( + ( + globalThis as unknown as Record< + PropertyKey, + Map | undefined + > + )[LYNX_BUNDLE_REGISTRY]?.get('remote'), + ).toBe('lynx-cache://remote'); + }); + + it.each([ + ['camel-case', { errorMsg: 'not found' }, -1, 'not found'], + ['native snake-case', { error_msg: 'decode failed' }, -2, 'decode failed'], + ] as const)( + 'reports %s bundle errors', + async (_name, error, code, message) => { + const loadScript = rs.fn(); + setLynx({ + fetchBundle: async () => ({ + code, + url: 'https://example.test/remote.lynx.bundle', + ...error, + }), + loadScript, + }); + + await expect( + loadEntry(lynxRuntimePlugin(), bundleRemoteInfo), + ).rejects.toThrow(`code ${code}: ${message}`); + expect(loadScript).not.toHaveBeenCalled(); + }, + ); + + it('rolls back bundle registry mappings after evaluation fails', async () => { + const registry = new Map([ + ['remote', 'lynx-cache://previous'], + ['remote:remote-origin', 'https://previous.test/remote.lynx.bundle'], + ['remote__main_thread', 'lynx-cache://previous'], + ]); + ( + globalThis as unknown as Record< + PropertyKey, + Map | undefined + > + )[LYNX_BUNDLE_REGISTRY] = registry; + setLynx({ + fetchBundle: async () => ({ code: 0, url: 'lynx-cache://failed' }), + loadScript: async () => { + expect(registry.get('remote')).toBe('lynx-cache://failed'); + throw new Error('evaluation failed'); + }, + }); + + await expect( + loadEntry(lynxRuntimePlugin(), bundleRemoteInfo), + ).rejects.toThrow('evaluation failed'); + expect(Object.fromEntries(registry)).toEqual({ + remote: 'lynx-cache://previous', + 'remote:remote-origin': 'https://previous.test/remote.lynx.bundle', + remote__main_thread: 'lynx-cache://previous', + }); + }); + + it('evicts timed-out entry loads so they can be retried', async () => { + const container = { + get: rs.fn(async () => () => ({ default: 'loaded' })), + init: rs.fn(), + }; + const requireModuleAsync = rs + .fn<(entry: string, callback: NativeCallback) => void>() + .mockImplementationOnce(() => undefined) + .mockImplementationOnce((_entry, callback) => callback(null, container)); + setLynx({ requireModuleAsync }); + + const federation = new ModuleFederation({ + name: 'lynx-retry-host', + plugins: [lynxRuntimePlugin({ timeout: 5 })], + remotes: [remoteInfo], + }); + await expect(federation.loadRemote('remote/Card')).rejects.toThrow( + 'Timed out', + ); + await expect(federation.loadRemote('remote/Card')).resolves.toEqual({ + default: 'loaded', + }); + expect(requireModuleAsync).toHaveBeenCalledTimes(2); + }); + + it('isolates cached entries by global name and realm', async () => { + const backgroundContainer = createContainer(); + const mainContainer = createContainer(); + const backgroundFetch = rs.fn(async () => ({ + code: 0, + url: 'lynx-cache://background', + })); + setLynx({ + requireModuleAsync: () => undefined, + fetchBundle: backgroundFetch, + loadScript: () => backgroundContainer, + }); + + const plugin = lynxRuntimePlugin(); + const loadedBackground = await loadEntry(plugin, bundleRemoteInfo); + loadedBackground.get('./Card'); + expect(backgroundContainer.get).toHaveBeenCalledWith('./Card'); + + const mainFetch = rs.fn(async () => ({ + code: 0, + url: 'lynx-cache://main', + })); + setLynx({ + fetchBundle: mainFetch, + loadScript: () => mainContainer, + }); + const loadedMain = await loadEntry(plugin, bundleRemoteInfo); + loadedMain.get('./Card'); + expect(mainContainer.get).toHaveBeenCalledWith('./Card__main_thread'); + + const alternateInfo = { + ...bundleRemoteInfo, + entryGlobalName: 'alternate', + }; + await expect(loadEntry(plugin, alternateInfo)).resolves.toMatchObject({ + get: expect.any(Function), + init: expect.any(Function), + }); + expect(backgroundFetch).toHaveBeenCalledTimes(1); + expect(mainFetch).toHaveBeenCalledTimes(2); + }); + + it('preserves every share scope for the active realm', async () => { + const container = createContainer(); + setLynx({ + fetchBundle: async () => ({ code: 0, url: 'lynx-cache://remote' }), + loadScript: () => container, + }); + const loaded = await loadEntry(lynxRuntimePlugin(), bundleRemoteInfo); + const scopes = { + 'default:react:background': { defaultBackground: true }, + 'default:react:main-thread': { defaultMain: true }, + 'custom:react:background': { customBackground: true }, + 'custom:react:main-thread': { customMain: true }, + }; + + loaded.init(scopes['default:react:main-thread'] as never, [], { + version: 'test', + shareScopeKeys: Object.keys(scopes), + shareScopeMap: scopes as never, + }); + + expect(container.init).toHaveBeenCalledWith( + scopes['default:react:main-thread'], + [], + expect.objectContaining({ + shareScopeKeys: [ + 'default:react:main-thread', + 'custom:react:main-thread', + ], + }), + ); + }); + + it('filters custom DSL share-scope layers for each runtime realm', async () => { + const backgroundContainer = createContainer(); + const mainContainer = createContainer(); + const plugin = lynxRuntimePlugin({ + realmLayers: { + background: 'worker:realm', + 'main-thread': 'ui:realm', + }, + }); + const scopes = { + 'default:react:worker:realm': { worker: true }, + 'default:react:ui:realm': { ui: true }, + }; + const initOptions = { + version: 'test', + shareScopeKeys: Object.keys(scopes), + shareScopeMap: scopes as never, + }; + + setLynx({ + fetchBundle: async () => ({ code: 0, url: 'lynx-cache://worker' }), + getNativeApp: () => ({}), + loadScript: () => backgroundContainer, + }); + const background = await loadEntry(plugin, bundleRemoteInfo); + background.init(scopes['default:react:worker:realm'] as never, [], { + ...initOptions, + }); + expect(backgroundContainer.init).toHaveBeenCalledWith( + scopes['default:react:worker:realm'], + [], + expect.objectContaining({ + shareScopeKeys: 'default:react:worker:realm', + }), + ); + + setLynx({ + fetchBundle: async () => ({ code: 0, url: 'lynx-cache://ui' }), + loadScript: () => mainContainer, + }); + const main = await loadEntry(plugin, bundleRemoteInfo); + main.init(scopes['default:react:ui:realm'] as never, [], { + ...initOptions, + }); + expect(mainContainer.init).toHaveBeenCalledWith( + scopes['default:react:ui:realm'], + [], + expect.objectContaining({ + shareScopeKeys: 'default:react:ui:realm', + }), + ); + }); + + it('does not infer realm share scopes from array positions', async () => { + const container = createContainer(); + setLynx({ + fetchBundle: async () => ({ code: 0, url: 'lynx-cache://remote' }), + loadScript: () => container, + }); + const loaded = await loadEntry(lynxRuntimePlugin(), bundleRemoteInfo); + const shareScope = { shared: true }; + const options = { + version: 'test', + shareScopeKeys: ['default', 'custom'], + shareScopeMap: { + default: { defaultScope: true }, + custom: { customScope: true }, + }, + }; + + loaded.init(shareScope as never, [], options as never); + + expect(container.init).toHaveBeenCalledWith(shareScope, [], options); + }); + + it('leaves non-Lynx remote types to the runtime-core loaders', () => { + expect( + loadEntry(lynxRuntimePlugin(), { + ...remoteInfo, + type: 'module', + }), + ).toBeUndefined(); + }); + + it('leaves preload assets for non-Lynx remotes to other runtime plugins', async () => { + const generatePreloadAssets = lynxRuntimePlugin().generatePreloadAssets!; + await expect( + generatePreloadAssets({ + remoteInfo: { ...remoteInfo, type: 'module' }, + } as GeneratePreloadAssetsArgs), + ).resolves.toBeUndefined(); + await expect( + generatePreloadAssets({ + remoteInfo: bundleRemoteInfo, + } as GeneratePreloadAssetsArgs), + ).resolves.toEqual({ + cssAssets: [], + entryAssets: [], + jsAssetsWithoutEntry: [], + }); + }); + + it('loads type lynx even when the bundle URL has an opaque suffix', async () => { + const container = createContainer(); + const fetchBundle = rs.fn(async () => ({ + code: 0, + url: 'lynx-cache://remote', + })); + setLynx({ fetchBundle, loadScript: () => container }); + + await expect( + loadEntry(lynxRuntimePlugin(), { + ...bundleRemoteInfo, + entry: 'https://example.test/artifact?id=remote', + type: 'lynx', + }), + ).resolves.toMatchObject({ + get: expect.any(Function), + init: expect.any(Function), + }); + expect(fetchBundle).toHaveBeenCalledWith( + 'https://example.test/artifact?id=remote', + ); + }); +}); diff --git a/packages/lynx/src/runtimePlugin.ts b/packages/lynx/src/runtimePlugin.ts new file mode 100644 index 00000000000..845537e1029 --- /dev/null +++ b/packages/lynx/src/runtimePlugin.ts @@ -0,0 +1,152 @@ +import type { + ModuleFederationRuntimePlugin, + RemoteEntryExports, + RemoteInfo, +} from '@module-federation/runtime-core/types'; + +import { + isBundleEntry, + getTimeout, + loadBundleEntry, + loadJavaScriptEntry, + loadScriptForEntry, + PREPARE_REMOTE_ENTRY_MTS, +} from './runtimeEntryLoader'; +import { + getLynxRealm, + getLynxRuntime, + isRecord, + LYNX_BUNDLE_REGISTRY, + type LynxGlobal, + type LynxRuntimePluginOptions, +} from './runtimeCore'; +import { + patchLynxChunkLoading, + type LynxWebpackRequire, +} from './runtimeChunkLoading'; + +export { LYNX_BUNDLE_REGISTRY, patchLynxChunkLoading }; +export type { LynxRuntimePluginOptions, LynxWebpackRequire }; + +declare const __webpack_require__: LynxWebpackRequire; + +const handlesLynxRemote = ({ + entry, + type, +}: Pick): boolean => + type === 'lynx' || type === 'lynx-js' || isBundleEntry(entry); + +export default function lynxRuntimePlugin( + options: LynxRuntimePluginOptions = {}, +): ModuleFederationRuntimePlugin { + const entryCache = new Map>(); + const timeout = getTimeout(options.timeout); + const realmLayers = options.realmLayers ?? { + background: 'background', + 'main-thread': 'main-thread', + }; + const globalObject = globalThis as LynxGlobal; + const lynx = getLynxRuntime(globalObject); + if ( + lynx?.loadScript && + getLynxRealm(lynx) === 'main-thread' && + typeof globalObject[PREPARE_REMOTE_ENTRY_MTS] !== 'function' + ) { + globalObject[PREPARE_REMOTE_ENTRY_MTS] = (payload: unknown): boolean => { + if ( + !isRecord(payload) || + typeof payload.bundleName !== 'string' || + typeof payload.entry !== 'string' || + typeof payload.sectionPath !== 'string' + ) { + return false; + } + try { + loadScriptForEntry( + lynx, + payload.sectionPath, + payload.bundleName, + payload.entry, + globalObject, + ); + return true; + } catch { + return false; + } + }; + } + + return { + name: 'lynx-federation-runtime-plugin', + beforeInit(args) { + if (typeof __webpack_require__ !== 'undefined') { + patchLynxChunkLoading( + __webpack_require__, + args.options.name, + globalThis as LynxGlobal, + timeout, + ); + } + return args; + }, + loadEntry({ remoteInfo }) { + const { entry, entryGlobalName, type } = remoteInfo; + const isBundle = type === 'lynx' || isBundleEntry(entry); + if (!handlesLynxRemote(remoteInfo)) { + return undefined; + } + + const globalObject = globalThis as LynxGlobal; + const lynx = getLynxRuntime(globalObject); + + if (!lynx) { + throw new Error('Lynx federation requires the Lynx runtime API.'); + } + + const realm = getLynxRealm(lynx); + const cacheKey = JSON.stringify([entry, entryGlobalName, realm]); + const cachedEntry = entryCache.get(cacheKey); + if (cachedEntry) { + return cachedEntry; + } + + const loadPromise = isBundle + ? loadBundleEntry( + lynx, + entry, + entryGlobalName, + realm, + realmLayers[realm], + globalObject, + timeout, + ) + : loadJavaScriptEntry( + lynx, + entry, + entryGlobalName, + globalObject, + timeout, + ); + let cachedPromise: Promise; + cachedPromise = loadPromise.catch((error) => { + if (entryCache.get(cacheKey) === cachedPromise) { + entryCache.delete(cacheKey); + } + throw error; + }); + + entryCache.set(cacheKey, cachedPromise); + return cachedPromise; + }, + async generatePreloadAssets({ remoteInfo }) { + if (!handlesLynxRemote(remoteInfo)) { + return undefined; + } + return { + cssAssets: [], + jsAssetsWithoutEntry: [], + entryAssets: [], + }; + }, + }; +} diff --git a/packages/lynx/src/runtimeSectionLoading.test.ts b/packages/lynx/src/runtimeSectionLoading.test.ts new file mode 100644 index 00000000000..022f4ebb5cd --- /dev/null +++ b/packages/lynx/src/runtimeSectionLoading.test.ts @@ -0,0 +1,249 @@ +import { describe, expect, it, rs } from '@rstest/core'; + +import { + LYNX_BUNDLE_REGISTRY, + patchLynxChunkLoading, + type LynxWebpackRequire, +} from './runtimePlugin'; +import { createWebpackRequire } from './runtimeChunkLoading.testUtils'; + +describe('patchLynxChunkLoading section loading', () => { + it('installs and deduplicates section chunks', async () => { + const webpackRequire = createWebpackRequire(); + const factory = rs.fn(); + const runtime = rs.fn(); + const nestedPromises: Promise[] = []; + const loadScript = rs.fn(() => { + webpackRequire.f.j!('feature', nestedPromises); + return { + ids: ['feature'], + modules: { factory }, + runtime, + }; + }); + const globalObject = { + lynx: { requireModuleAsync: () => undefined, loadScript }, + [LYNX_BUNDLE_REGISTRY]: new Map([['remote', 'lynx-cache://remote']]), + }; + + expect(patchLynxChunkLoading(webpackRequire, 'remote', globalObject)).toBe( + true, + ); + const promises: Promise[] = []; + webpackRequire.f.j!('feature', promises); + + expect(loadScript).toHaveBeenCalledTimes(1); + expect(loadScript).toHaveBeenCalledWith('chunks/feature', { + bundleName: 'lynx-cache://remote', + }); + expect(nestedPromises[0]).toBe(promises[0]); + await expect(Promise.all(promises)).resolves.toBeDefined(); + expect(webpackRequire.m.factory).toBe(factory); + expect(runtime).toHaveBeenCalledWith(webpackRequire); + }); + + it('loads atomic chunks from sections in the already-fetched container', async () => { + const webpackRequire = createWebpackRequire( + 'async/catalog__background_Card.js', + ); + webpackRequire.lynx_aci = { feature: 'async/Card.bundle' }; + webpackRequire.lynx_chunking = 'single'; + const factory = rs.fn(); + const loadLazyBundle = rs.fn(); + const loadScript = rs.fn(() => ({ + ids: ['feature'], + modules: { factory }, + })); + const globalObject = { + lynx: { loadLazyBundle, loadScript, requireModuleAsync: rs.fn() }, + [LYNX_BUNDLE_REGISTRY]: new Map([['remote', 'lynx-cache://catalog']]), + }; + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const promises: Promise[] = []; + webpackRequire.f.j!('feature', promises); + await Promise.all(promises); + + expect(loadLazyBundle).not.toHaveBeenCalled(); + expect(loadScript).toHaveBeenCalledWith('async/catalog__background_Card', { + bundleName: 'lynx-cache://catalog', + }); + }); + + it('restores the lazy-bundle identity while deferred module factories execute', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: 'async/Card.bundle' }; + const observedEntries: unknown[] = []; + const factory = rs.fn(() => { + observedEntries.push(globalObject.globDynamicComponentEntry); + }); + const globalObject: any = { + globDynamicComponentEntry: '__Card__', + lynx: { + loadLazyBundle: async () => ({ + __lynx_dynamic_component_entry__: 'https://cdn.example/Card.bundle', + ids: ['feature'], + modules: { factory }, + }), + loadScript: rs.fn(), + }, + [LYNX_BUNDLE_REGISTRY]: new Map([ + ['remote', 'lynx-cache://catalog'], + ['remote:remote-origin', 'https://cdn.example/catalog.lynx.bundle'], + ]), + }; + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + await Promise.all(promises); + ( + webpackRequire.m.factory as ( + module: unknown, + exports: unknown, + require: unknown, + ) => unknown + )({}, {}, webpackRequire); + + expect(observedEntries).toEqual(['https://cdn.example/Card.bundle']); + expect(globalObject.globDynamicComponentEntry).toBe('__Card__'); + }); + + it('preserves CommonJS factory this while restoring bundle identity', async () => { + const webpackRequire = createWebpackRequire(); + webpackRequire.lynx_aci = { feature: 'async/Card.bundle' }; + const observedThis: unknown[] = []; + const factory = function (this: unknown) { + observedThis.push(this); + }; + const globalObject = { + lynx: { + loadLazyBundle: async () => ({ + __lynx_dynamic_component_entry__: 'https://cdn.example/Card.bundle', + ids: ['feature'], + modules: { factory }, + }), + loadScript: rs.fn(), + }, + [LYNX_BUNDLE_REGISTRY]: new Map([ + ['remote', 'lynx-cache://catalog'], + ['remote:remote-origin', 'https://cdn.example/catalog.lynx.bundle'], + ]), + }; + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const promises: PromiseLike[] = []; + webpackRequire.f.j!('feature', promises); + await Promise.all(promises); + const module = { exports: {} }; + ( + webpackRequire.m.factory as ( + this: unknown, + module: unknown, + exports: unknown, + runtimeRequire: LynxWebpackRequire, + ) => unknown + ).call(module.exports, module, module.exports, webpackRequire); + + expect(observedThis).toEqual([module.exports]); + }); + + it('preserves the original chunk handler without a registered bundle', () => { + const originalHandler = rs.fn(); + const webpackRequire = createWebpackRequire(); + webpackRequire.f.j = originalHandler; + const globalObject = { + lynx: { loadScript: rs.fn() }, + [LYNX_BUNDLE_REGISTRY]: new Map(), + }; + + expect(patchLynxChunkLoading(webpackRequire, 'remote', globalObject)).toBe( + false, + ); + const promises: Promise[] = []; + webpackRequire.f.j('feature', promises); + expect(originalHandler).toHaveBeenCalledWith('feature', promises); + }); + + it('evicts failed section loads so they can be retried', async () => { + const webpackRequire = createWebpackRequire('feature.js'); + const loadScript = rs + .fn<(sectionPath: string, options: { bundleName: string }) => unknown>() + .mockImplementationOnce(() => { + throw new Error('decode failed'); + }) + .mockImplementationOnce(() => ({ + ids: ['feature'], + modules: {}, + })); + const globalObject = { + lynx: { loadScript }, + [LYNX_BUNDLE_REGISTRY]: new Map([ + ['remote__main_thread', 'lynx-cache://remote'], + ]), + }; + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + + const failedPromises: Promise[] = []; + webpackRequire.f.j!('feature', failedPromises); + await expect(Promise.all(failedPromises)).rejects.toThrow('decode failed'); + + const retriedPromises: Promise[] = []; + webpackRequire.f.j!('feature', retriedPromises); + await expect(Promise.all(retriedPromises)).resolves.toBeDefined(); + expect(loadScript).toHaveBeenCalledTimes(2); + }); + + it('uses the main-thread container name without appending the realm twice', async () => { + const webpackRequire = createWebpackRequire('feature.js'); + const loadScript = rs.fn(() => ({ + ids: ['feature'], + modules: {}, + })); + const globalObject = { + lynx: { loadScript }, + [LYNX_BUNDLE_REGISTRY]: new Map([ + ['remote__main_thread', 'lynx-cache://remote'], + ]), + }; + + expect( + patchLynxChunkLoading( + webpackRequire, + 'remote__main_thread', + globalObject, + ), + ).toBe(true); + + const promises: Promise[] = []; + webpackRequire.f.j!('feature', promises); + await expect(Promise.all(promises)).resolves.toBeDefined(); + expect(loadScript).toHaveBeenCalledWith('feature', { + bundleName: 'lynx-cache://remote', + }); + }); + + it('replaces the Lynx require chunk handler emitted by Rspeedy', async () => { + const webpackRequire = createWebpackRequire('feature.js'); + const originalHandler = rs.fn(); + webpackRequire.f.require = originalHandler; + const loadScript = rs.fn(() => ({ + ids: ['feature'], + modules: {}, + })); + const globalObject = { + lynx: { requireModuleAsync: rs.fn(), loadScript }, + [LYNX_BUNDLE_REGISTRY]: new Map([['remote', 'lynx-cache://remote']]), + }; + + patchLynxChunkLoading(webpackRequire, 'remote', globalObject); + const promises: Promise[] = []; + webpackRequire.f.require!('feature', promises); + + await expect(Promise.all(promises)).resolves.toBeDefined(); + expect(loadScript).toHaveBeenCalledWith('feature', { + bundleName: 'lynx-cache://remote', + }); + expect(originalHandler).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/lynx/src/runtimeTimeout.ts b/packages/lynx/src/runtimeTimeout.ts new file mode 100644 index 00000000000..7f7a7ecd50a --- /dev/null +++ b/packages/lynx/src/runtimeTimeout.ts @@ -0,0 +1,35 @@ +export const loadWithTimeout = ( + timeout: number, + message: string, + start: ( + resolve: (value: T) => void, + reject: (error: unknown) => void, + isSettled: () => boolean, + ) => void, +): Promise => + new Promise((resolve, reject) => { + let settled = false; + const timer = setTimeout(() => { + settled = true; + reject(new Error(message)); + }, timeout); + + const finish = (callback: () => void): void => { + if (settled) { + return; + } + settled = true; + clearTimeout(timer); + callback(); + }; + + try { + start( + (value) => finish(() => resolve(value)), + (error) => finish(() => reject(error)), + () => settled, + ); + } catch (error) { + finish(() => reject(error)); + } + }); diff --git a/packages/lynx/src/webEncode.test.ts b/packages/lynx/src/webEncode.test.ts new file mode 100644 index 00000000000..5b02a2c07eb --- /dev/null +++ b/packages/lynx/src/webEncode.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from '@rstest/core'; + +import { getLynxWebEncodeMode } from './webEncode'; + +describe('getLynxWebEncodeMode', () => { + it('encodes background, main-thread, CSS, and data sections', async () => { + const { buffer } = await getLynxWebEncodeMode()({ + compilerOptions: { targetSdkVersion: '3.5' }, + sourceContent: { appType: 'DynamicComponent' }, + customSections: { + background: { content: 'module.exports = "background"' }, + main: { + encoding: 'JsBytecode', + content: 'module.exports = "main"', + }, + styles: { + encoding: 'CSS', + content: { ruleList: [] }, + }, + metadata: { content: { version: 1 } }, + }, + }); + + expect(Buffer.isBuffer(buffer)).toBe(true); + expect(buffer.byteLength).toBeGreaterThan(100); + }); + + it('rejects invalid JavaScript sections', async () => { + await expect( + getLynxWebEncodeMode()({ + customSections: { + main: { encoding: 'JsBytecode', content: { invalid: true } }, + }, + }), + ).rejects.toThrow('must be a string'); + }); +}); diff --git a/packages/lynx/src/webEncode.ts b/packages/lynx/src/webEncode.ts new file mode 100644 index 00000000000..75012f9fbe1 --- /dev/null +++ b/packages/lynx/src/webEncode.ts @@ -0,0 +1,71 @@ +import type { TasmJSONInfo } from '@lynx-js/web-core/encode'; + +interface ExternalBundleSection { + content: unknown; + encoding?: string; +} + +interface ExternalBundleEncodeOptions { + compilerOptions?: Record; + customSections?: Record; + sourceContent?: { appType?: string }; +} + +const isRecord = (value: unknown): value is Record => + typeof value === 'object' && value !== null; + +const getEncodeOptions = (value: unknown): ExternalBundleEncodeOptions => { + if (!isRecord(value)) { + throw new TypeError('Expected Lynx external-bundle encode options.'); + } + return value as ExternalBundleEncodeOptions; +}; + +const getJavaScript = (name: string, content: unknown): string => { + if (typeof content !== 'string') { + throw new TypeError(`Lynx JavaScript section "${name}" must be a string.`); + } + return content; +}; + +export const getLynxWebEncodeMode = () => async (value: unknown) => { + const options = getEncodeOptions(value); + const styleInfo: TasmJSONInfo['styleInfo'] = {}; + const manifest: TasmJSONInfo['manifest'] = {}; + const lepusCode: TasmJSONInfo['lepusCode'] = {}; + const customSections: TasmJSONInfo['customSections'] = {}; + let cssId = 0; + + for (const [name, section] of Object.entries(options.customSections ?? {})) { + if (section.encoding === 'CSS') { + const ruleList = isRecord(section.content) + ? section.content.ruleList + : undefined; + styleInfo[String(cssId++)] = Array.isArray(ruleList) ? ruleList : []; + } else if (section.encoding === 'JsBytecode') { + lepusCode[name] = getJavaScript(name, section.content); + } else if (typeof section.content === 'string') { + manifest[`/${name}`] = section.content; + } else { + customSections[name] = { + content: section.content as Record, + }; + } + } + + const { encode } = await import('@lynx-js/web-core/encode'); + return { + buffer: Buffer.from( + encode({ + appType: options.sourceContent?.appType ?? 'DynamicComponent', + cardType: 'react', + customSections, + elementTemplates: {}, + lepusCode, + manifest, + pageConfig: options.compilerOptions ?? {}, + styleInfo, + }), + ), + }; +}; diff --git a/packages/lynx/tsconfig.json b/packages/lynx/tsconfig.json new file mode 100644 index 00000000000..89adca0a58e --- /dev/null +++ b/packages/lynx/tsconfig.json @@ -0,0 +1,31 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "./", + "outDir": "dist", + "sourceMap": false, + "module": "es2022", + "target": "es2022", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowJs": false, + "strict": true, + "types": ["node"], + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "removeComments": true, + "declaration": true, + "paths": { + "@/*": ["./*"], + "@src/*": ["./src/*"], + "*": ["./*"] + } + }, + "include": ["src", "../../global.d.ts"], + "exclude": ["node_modules/**/*", "../node_modules"], + "references": [ + { "path": "./tsconfig.lib.json" }, + { "path": "./tsconfig.spec.json" } + ] +} diff --git a/packages/lynx/tsconfig.lib.json b/packages/lynx/tsconfig.lib.json new file mode 100644 index 00000000000..d2be5624718 --- /dev/null +++ b/packages/lynx/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["rstest.config.mts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/packages/lynx/tsconfig.spec.json b/packages/lynx/tsconfig.spec.json new file mode 100644 index 00000000000..70848594750 --- /dev/null +++ b/packages/lynx/tsconfig.spec.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["@rstest/core/globals", "@rstest/core/importMeta", "node"] + }, + "include": [ + "rstest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/packages/lynx/tsdown.config.mts b/packages/lynx/tsdown.config.mts new file mode 100644 index 00000000000..bf0b8012b1a --- /dev/null +++ b/packages/lynx/tsdown.config.mts @@ -0,0 +1,38 @@ +import { defineConfig } from 'tsdown'; + +import { + createDualFormatConfig, + packageDirFromMetaUrl, +} from '../../tools/scripts/tsdown/config-helpers.mjs'; + +const packageDir = packageDirFromMetaUrl(import.meta.url); + +export default defineConfig([ + { + ...createDualFormatConfig({ + name: 'lynx-build', + packageDir, + entry: { + index: 'src/index.ts', + plugin: 'src/plugin.ts', + reactRuntimePlugin: 'src/reactRuntimePlugin.ts', + runtimePlugin: 'src/runtimePlugin.ts', + }, + external: [ + '@lynx-js/*', + '@module-federation/*', + '@rspack/core', + '@rsbuild/core', + ], + unbundle: true, + preferNonModuleCjs: false, + }), + inlineOnly: false, + dts: { + resolver: 'tsc', + }, + outputOptions: { + exports: 'named', + }, + }, +]); diff --git a/packages/runtime-core/__tests__/load.spec.ts b/packages/runtime-core/__tests__/load.spec.ts index 042bbc1e392..ac29145fb06 100644 --- a/packages/runtime-core/__tests__/load.spec.ts +++ b/packages/runtime-core/__tests__/load.spec.ts @@ -99,6 +99,42 @@ describe('getRemoteEntry - script load error discrimination', () => { ); }); + it('evicts failed remote entry loads so a later request can retry', async () => { + let attempts = 0; + const container = { + get: rs.fn(), + init: rs.fn(), + }; + const origin = new ModuleFederation({ + name: 'retry-host', + remotes: [], + plugins: [ + { + name: 'retry-entry-loader', + loadEntry() { + attempts += 1; + if (attempts === 1) { + throw new Error('transient entry failure'); + } + return container; + }, + }, + ], + }); + const remoteInfo = getRemoteInfo({ + name: 'retry-remote', + entry: 'https://remote.test/entry.js', + }); + + await expect(getRemoteEntry({ origin, remoteInfo })).rejects.toThrow( + 'transient entry failure', + ); + await expect(getRemoteEntry({ origin, remoteInfo })).resolves.toBe( + container, + ); + expect(attempts).toBe(2); + }); + it('module entry load failure can recover through loadEntryError with getEntryUrl', async () => { const entry = createDataUrlEntry( `throw new TypeError('Failed to fetch dynamically imported module: http://localhost:4999/remoteEntry.js');`, diff --git a/packages/runtime-core/__tests__/register-remotes.spec.ts b/packages/runtime-core/__tests__/register-remotes.spec.ts index c7789536191..5b65a3495fd 100644 --- a/packages/runtime-core/__tests__/register-remotes.spec.ts +++ b/packages/runtime-core/__tests__/register-remotes.spec.ts @@ -209,4 +209,174 @@ describe('ModuleFederation', () => { expect(await nextAppModule()).toBe('hello world "@snapshot/remote2"'); expect(manifestFetch).toHaveBeenCalledTimes(2); }); + + it('clears manifest cache and loading when a manifest remote is force re-registered', async () => { + const manifestUrl = 'https://requested.example/mf-manifest.json'; + const response = new Response( + JSON.stringify({ + id: 'catalog', + name: 'catalog', + metaData: { + name: 'catalog', + publicPath: 'https://requested.example/', + type: 'app', + buildInfo: { buildVersion: '1.0.0' }, + remoteEntry: { + name: 'catalog.web.lynx.bundle', + path: '', + type: 'global', + }, + types: { name: '', path: '' }, + globalName: 'catalog', + }, + remotes: [], + shared: [], + exposes: [], + }), + { headers: { 'Content-Type': 'application/json' } }, + ); + const instance = new ModuleFederation({ + name: 'host', + remotes: [{ name: 'catalog', entry: manifestUrl }], + plugins: [ + { + name: 'manifest-fetch', + fetch: async () => response, + }, + ], + }); + const handler = instance.snapshotHandler; + + await handler.loadRemoteSnapshotInfo({ + moduleInfo: { name: 'catalog', entry: manifestUrl }, + }); + expect(handler.manifestCache.has(manifestUrl)).toBe(true); + expect(handler.manifestLoading[manifestUrl]).toBeDefined(); + + instance.registerRemotes([{ name: 'catalog', entry: manifestUrl }], { + force: true, + }); + + expect(handler.manifestCache.has(manifestUrl)).toBe(false); + expect(handler.manifestLoading[manifestUrl]).toBeUndefined(); + }); + + it('keeps the newer manifest snapshot when a stale fetch resolves after force re-registration', async () => { + const manifestUrl = 'https://requested.example/mf-manifest.json'; + const manifests = { + first: { + id: 'catalog', + name: 'catalog', + metaData: { + name: 'catalog', + publicPath: 'https://first.example/', + type: 'app', + buildInfo: { buildVersion: 'first' }, + remoteEntry: { + name: 'catalog.web.lynx.bundle', + path: '', + type: 'global', + }, + types: { name: '', path: '' }, + globalName: 'catalog-first', + }, + remotes: [], + shared: [], + exposes: [], + }, + second: { + id: 'catalog', + name: 'catalog', + metaData: { + name: 'catalog', + publicPath: 'https://second.example/', + type: 'app', + buildInfo: { buildVersion: 'second' }, + remoteEntry: { + name: 'catalog.web.lynx.bundle', + path: '', + type: 'global', + }, + types: { name: '', path: '' }, + globalName: 'catalog-second', + }, + remotes: [], + shared: [], + exposes: [], + }, + }; + let resolveFirstResponse!: (response: Response) => void; + const firstResponse = new Promise((resolve) => { + resolveFirstResponse = resolve; + }); + let resolveSecondResponse!: (response: Response) => void; + const secondResponse = new Promise((resolve) => { + resolveSecondResponse = resolve; + }); + let resolveFirstFetchStarted!: () => void; + const firstFetchStarted = new Promise((resolve) => { + resolveFirstFetchStarted = resolve; + }); + let resolveSecondFetchStarted!: () => void; + const secondFetchStarted = new Promise((resolve) => { + resolveSecondFetchStarted = resolve; + }); + let fetchCount = 0; + const manifestFetch = rs.fn(() => { + fetchCount += 1; + if (fetchCount === 1) { + resolveFirstFetchStarted(); + return firstResponse; + } + resolveSecondFetchStarted(); + return secondResponse; + }); + const instance = new ModuleFederation({ + name: 'host', + remotes: [{ name: 'catalog', entry: manifestUrl }], + plugins: [ + { + name: 'manifest-fetch', + fetch: manifestFetch, + }, + ], + }); + const handler = instance.snapshotHandler; + + const staleSnapshot = handler.loadRemoteSnapshotInfo({ + moduleInfo: { name: 'catalog', entry: manifestUrl }, + }); + await firstFetchStarted; + + instance.registerRemotes([{ name: 'catalog', entry: manifestUrl }], { + force: true, + }); + const newerSnapshot = handler.loadRemoteSnapshotInfo({ + moduleInfo: { name: 'catalog', entry: manifestUrl }, + }); + await secondFetchStarted; + resolveSecondResponse( + new Response(JSON.stringify(manifests.second), { + headers: { 'Content-Type': 'application/json' }, + }), + ); + + expect(await newerSnapshot).toMatchObject({ + remoteSnapshot: { globalName: 'catalog-second' }, + }); + + resolveFirstResponse( + new Response(JSON.stringify(manifests.first), { + headers: { 'Content-Type': 'application/json' }, + }), + ); + + expect(await staleSnapshot).toMatchObject({ + remoteSnapshot: { globalName: 'catalog-second' }, + }); + expect( + handler.manifestCache.get(manifestUrl)?.manifest.metaData.buildInfo + .buildVersion, + ).toBe('second'); + }); }); diff --git a/packages/runtime-core/__tests__/snapshot.spec.ts b/packages/runtime-core/__tests__/snapshot.spec.ts index 08862ebabf6..f1ed44dcb07 100644 --- a/packages/runtime-core/__tests__/snapshot.spec.ts +++ b/packages/runtime-core/__tests__/snapshot.spec.ts @@ -46,4 +46,175 @@ describe('snapshot', () => { }, }); }); + + it('infers an auto public path from the fetched manifest URL', async () => { + const manifestUrl = '/remote-web/mf-manifest.json'; + const resolvedManifestUrl = + 'https://example.test/remote-web/mf-manifest.json'; + const response = new Response( + JSON.stringify({ + id: 'catalog', + name: 'catalog', + metaData: { + name: 'catalog', + publicPath: 'auto', + type: 'app', + buildInfo: { buildVersion: '1.0.0' }, + remoteEntry: { + name: 'catalog.web.lynx.bundle', + path: '', + type: 'global', + }, + types: { name: '', path: '' }, + globalName: 'catalog', + }, + remotes: [], + shared: [], + exposes: [], + }), + { headers: { 'Content-Type': 'application/json' } }, + ); + Object.defineProperty(response, 'url', { value: resolvedManifestUrl }); + + const instance = new ModuleFederation({ + name: 'host', + remotes: [{ name: 'catalog', entry: manifestUrl }], + plugins: [ + { + name: 'resolved-manifest-fetch', + fetch: async () => response, + }, + ], + }); + + const { remoteSnapshot } = + await instance.snapshotHandler.loadRemoteSnapshotInfo({ + moduleInfo: { name: 'catalog', entry: manifestUrl }, + }); + + expect(remoteSnapshot).toMatchObject({ + version: manifestUrl, + publicPath: 'https://example.test/remote-web/', + remoteEntry: 'catalog.web.lynx.bundle', + }); + }); + + it('uses the requested manifest URL after parse recovery', async () => { + const manifestUrl = 'https://requested.example/mf-manifest.json'; + const resolvedManifestUrl = + 'https://redirected.example/v2/mf-manifest.json'; + const recoveredManifest = { + id: 'catalog', + name: 'catalog', + metaData: { + name: 'catalog', + publicPath: 'auto', + type: 'app', + buildInfo: { buildVersion: '1.0.0' }, + remoteEntry: { + name: 'catalog.web.lynx.bundle', + path: 'https://requested.example/', + type: 'global', + }, + types: { name: '', path: '' }, + globalName: 'catalog', + }, + remotes: [], + shared: [], + exposes: [], + }; + const response = new Response('invalid manifest'); + Object.defineProperty(response, 'url', { value: resolvedManifestUrl }); + Object.defineProperty(response, 'json', { + value: async () => { + throw new Error('invalid manifest'); + }, + }); + + const instance = new ModuleFederation({ + name: 'host', + remotes: [{ name: 'catalog', entry: manifestUrl }], + plugins: [ + { + name: 'failed-manifest-fetch', + fetch: async () => response, + }, + { + name: 'manifest-recovery', + errorLoadRemote: () => recoveredManifest, + }, + ], + }); + + const { remoteSnapshot } = + await instance.snapshotHandler.loadRemoteSnapshotInfo({ + moduleInfo: { name: 'catalog', entry: manifestUrl }, + }); + + expect(remoteSnapshot).toMatchObject({ + publicPath: 'https://requested.example/', + }); + expect(remoteSnapshot?.remoteEntry).toContain('requested.example'); + }); + + it('retries a manifest after a failed load', async () => { + const manifestUrl = 'https://retry.example/mf-manifest.json'; + const manifest = { + id: 'catalog', + name: 'catalog', + metaData: { + name: 'catalog', + publicPath: 'https://retry.example/', + type: 'app', + buildInfo: { buildVersion: '1.0.0' }, + remoteEntry: { + name: 'catalog.web.lynx.bundle', + path: '', + type: 'global', + }, + types: { name: '', path: '' }, + globalName: 'catalog', + }, + remotes: [], + shared: [], + exposes: [], + }; + let fetchCount = 0; + const instance = new ModuleFederation({ + name: 'host', + remotes: [{ name: 'catalog', entry: manifestUrl }], + plugins: [ + { + name: 'manifest-fetch', + fetch: async () => { + fetchCount += 1; + if (fetchCount === 1) { + throw new Error('temporary failure'); + } + return new Response(JSON.stringify(manifest), { + headers: { 'Content-Type': 'application/json' }, + }); + }, + }, + ], + }); + + await expect( + instance.snapshotHandler.loadRemoteSnapshotInfo({ + moduleInfo: { name: 'catalog', entry: manifestUrl }, + }), + ).rejects.toThrow('temporary failure'); + expect( + instance.snapshotHandler.manifestLoading[manifestUrl], + ).toBeUndefined(); + + await expect( + instance.snapshotHandler.loadRemoteSnapshotInfo({ + moduleInfo: { name: 'catalog', entry: manifestUrl }, + }), + ).resolves.toMatchObject({ + remoteSnapshot: { globalName: 'catalog' }, + }); + expect(fetchCount).toBe(2); + }); }); diff --git a/packages/runtime-core/src/global.ts b/packages/runtime-core/src/global.ts index 154b9829e31..51c2622c31b 100644 --- a/packages/runtime-core/src/global.ts +++ b/packages/runtime-core/src/global.ts @@ -62,7 +62,7 @@ function definePropertyGlobalVal( } function includeOwnProperty(target: typeof CurrentGlobal, key: string) { - return Object.hasOwnProperty.call(target, key); + return Object.prototype.hasOwnProperty.call(target, key); } // This section is to prevent encapsulation by certain microfrontend frameworks. Due to reuse policies, sandbox escapes. @@ -95,12 +95,16 @@ function setGlobalDefaultVal(target: typeof CurrentGlobal) { definePropertyGlobalVal(target, '__VMOK__', target.__FEDERATION__); } - target.__FEDERATION__.__GLOBAL_PLUGIN__ ??= []; - target.__FEDERATION__.__INSTANCES__ ??= []; - target.__FEDERATION__.moduleInfo ??= {}; - target.__FEDERATION__.__SHARE__ ??= {}; - target.__FEDERATION__.__MANIFEST_LOADING__ ??= {}; - target.__FEDERATION__.__PRELOADED_MAP__ ??= new Map(); + target.__FEDERATION__.__GLOBAL_PLUGIN__ = + target.__FEDERATION__.__GLOBAL_PLUGIN__ || []; + target.__FEDERATION__.__INSTANCES__ = + target.__FEDERATION__.__INSTANCES__ || []; + target.__FEDERATION__.moduleInfo = target.__FEDERATION__.moduleInfo || {}; + target.__FEDERATION__.__SHARE__ = target.__FEDERATION__.__SHARE__ || {}; + target.__FEDERATION__.__MANIFEST_LOADING__ = + target.__FEDERATION__.__MANIFEST_LOADING__ || {}; + target.__FEDERATION__.__PRELOADED_MAP__ = + target.__FEDERATION__.__PRELOADED_MAP__ || new Map(); } setGlobalDefaultVal(CurrentGlobal); diff --git a/packages/runtime-core/src/plugins/snapshot/SnapshotHandler.ts b/packages/runtime-core/src/plugins/snapshot/SnapshotHandler.ts index e3661cfdbf7..15c19bab7bb 100644 --- a/packages/runtime-core/src/plugins/snapshot/SnapshotHandler.ts +++ b/packages/runtime-core/src/plugins/snapshot/SnapshotHandler.ts @@ -71,10 +71,16 @@ export function getGlobalRemoteInfo( }; } +interface ManifestCacheRecord { + manifest: Manifest; + resolvedUrl: string; +} + export class SnapshotHandler { loadingHostSnapshot: Promise | null = null; HostInstance: ModuleFederation; - manifestCache: Map = new Map(); + manifestCache: Map = new Map(); + private manifestCacheRequests: Map = new Map(); hooks = new PluginSystem({ beforeLoadRemoteSnapshot: new AsyncHook< [ @@ -118,6 +124,22 @@ export class SnapshotHandler { this.loaderHook = HostInstance.loaderHook; } + clearManifestCache(manifestUrl: string): void { + this.manifestCacheRequests.delete(manifestUrl); + this.manifestCache.delete(manifestUrl); + delete this.manifestLoading[manifestUrl]; + } + + private clearManifestRequest( + manifestUrl: string, + manifestCacheRequest: object, + ): void { + if (this.manifestCacheRequests.get(manifestUrl) === manifestCacheRequest) { + this.manifestCacheRequests.delete(manifestUrl); + delete this.manifestLoading[manifestUrl]; + } + } + // eslint-disable-next-line max-lines-per-function async loadRemoteSnapshotInfo({ moduleInfo, @@ -291,6 +313,7 @@ export class SnapshotHandler { manifestUrl: string, moduleInfo: Remote, extraOptions: Record, + manifestCacheRequest: object, resourceOptions?: { initiator: ResourceLoadInitiator; id: string; @@ -298,11 +321,12 @@ export class SnapshotHandler { ): Promise { const getManifest = async (): Promise => { const remoteInfo = getRemoteInfo(moduleInfo); - let manifestJson: Manifest | undefined = - this.manifestCache.get(manifestUrl); - if (manifestJson) { - return manifestJson; + const cachedManifest = this.manifestCache.get(manifestUrl); + if (cachedManifest) { + return cachedManifest.manifest; } + let manifestJson: Manifest | undefined; + let resolvedUrl = manifestUrl; try { let res = await this.loaderHook.lifecycle.fetch.emit( manifestUrl, @@ -320,6 +344,7 @@ export class SnapshotHandler { res = await fetch(manifestUrl, {}); } manifestJson = (await res.json()) as Manifest; + resolvedUrl = res.url || manifestUrl; } catch (err) { manifestJson = (await this.HostInstance.remoteHandler.hooks.lifecycle.errorLoadRemote.emit( @@ -334,7 +359,6 @@ export class SnapshotHandler { )) as Manifest | undefined; if (!manifestJson) { - delete this.manifestLoading[manifestUrl]; error( RUNTIME_003, runtimeDescMap, @@ -383,7 +407,16 @@ export class SnapshotHandler { optionsToMFContext(this.HostInstance.options), ); } - this.manifestCache.set(manifestUrl, manifestJson); + if ( + manifestCacheRequest === this.manifestCacheRequests.get(manifestUrl) + ) { + this.manifestCache.set(manifestUrl, { + manifest: manifestJson, + resolvedUrl, + }); + } else { + return this.manifestCache.get(manifestUrl)?.manifest ?? manifestJson; + } return manifestJson; }; @@ -399,15 +432,18 @@ export class SnapshotHandler { id: string; }, ): Promise { - const asyncLoadProcess = async () => { + const asyncLoadProcess = async (manifestCacheRequest: object) => { const manifestJson = await this.getManifestJson( manifestUrl, moduleInfo, extraOptions, + manifestCacheRequest, resourceOptions, ); const remoteSnapshot = generateSnapshotFromManifest(manifestJson, { version: manifestUrl, + resolvedManifestUrl: + this.manifestCache.get(manifestUrl)?.resolvedUrl ?? manifestUrl, }); const { remoteSnapshot: remoteSnapshotRes } = @@ -423,7 +459,14 @@ export class SnapshotHandler { }; if (!this.manifestLoading[manifestUrl]) { - this.manifestLoading[manifestUrl] = asyncLoadProcess().then((res) => res); + const manifestCacheRequest = {}; + this.manifestCacheRequests.set(manifestUrl, manifestCacheRequest); + this.manifestLoading[manifestUrl] = asyncLoadProcess( + manifestCacheRequest, + ).catch((loadError) => { + this.clearManifestRequest(manifestUrl, manifestCacheRequest); + throw loadError; + }); } return this.manifestLoading[manifestUrl]; } diff --git a/packages/runtime-core/src/remote/index.ts b/packages/runtime-core/src/remote/index.ts index 3ce06056593..a298cf58af3 100644 --- a/packages/runtime-core/src/remote/index.ts +++ b/packages/runtime-core/src/remote/index.ts @@ -6,12 +6,7 @@ import { GlobalModuleInfo, } from '@module-federation/sdk'; import { RUNTIME_004, runtimeDescMap } from '@module-federation/error-codes'; -import { - Global, - getInfoWithoutType, - globalLoading, - CurrentGlobal, -} from '../global'; +import { getInfoWithoutType, globalLoading, CurrentGlobal } from '../global'; import { Options, UserOptions, @@ -177,7 +172,7 @@ export class RemoteHandler { globalSnapshot: GlobalModuleInfo; }, ], - Promise + Promise >('generatePreloadAssets'), afterPreloadRemote: new AsyncHook< [ @@ -711,8 +706,7 @@ export class RemoteHandler { delete CurrentGlobal.__FEDERATION__.moduleInfo[globalSnapshotKey]; if ('entry' in remote) { - host.snapshotHandler.manifestCache.delete(remote.entry); - delete Global.__FEDERATION__.__MANIFEST_LOADING__[remote.entry]; + host.snapshotHandler.clearManifestCache(remote.entry); } const { hostGlobalSnapshot } = getGlobalRemoteInfo(remote, host); diff --git a/packages/runtime-core/src/utils/load.ts b/packages/runtime-core/src/utils/load.ts index a624f9a109f..db59166068e 100644 --- a/packages/runtime-core/src/utils/load.ts +++ b/packages/runtime-core/src/utils/load.ts @@ -353,7 +353,8 @@ export async function getRemoteEntry(params: { const loadEntryHook = origin.remoteHandler.hooks.lifecycle.loadEntry; const loaderHook = origin.loaderHook; - globalLoading[uniqueKey] = loadEntryHook + let loadingPromise: Promise; + loadingPromise = loadEntryHook .emit({ origin, loaderHook, @@ -432,7 +433,14 @@ export async function getRemoteEntry(params: { error: err, }); throw err; + }) + .catch((err) => { + if (globalLoading[uniqueKey] === loadingPromise) { + delete globalLoading[uniqueKey]; + } + throw err; }); + globalLoading[uniqueKey] = loadingPromise; } return globalLoading[uniqueKey]; diff --git a/packages/sdk/__tests__/generateSnapshotFromManifest.spec.ts b/packages/sdk/__tests__/generateSnapshotFromManifest.spec.ts index c1662388843..bbdbea8e562 100644 --- a/packages/sdk/__tests__/generateSnapshotFromManifest.spec.ts +++ b/packages/sdk/__tests__/generateSnapshotFromManifest.spec.ts @@ -45,6 +45,25 @@ describe('generateSnapshotFromManifest', () => { expect(remoteSnapshot.publicPath).toBe('http://localhost:2006/ssr/'); }); + it('infers publicPath from a resolved manifest url without changing version', () => { + const manifestWithAutoPublicPath = JSON.parse( + JSON.stringify(manifest.devAppManifest), + ); + manifestWithAutoPublicPath.metaData.publicPath = 'auto'; + const remoteSnapshot = generateSnapshotFromManifest( + manifestWithAutoPublicPath, + { + version: 'https://requested.example/mf-manifest.json', + resolvedManifestUrl: 'https://redirected.example/v2/mf-manifest.json', + }, + ); + + expect(remoteSnapshot.version).toBe( + 'https://requested.example/mf-manifest.json', + ); + expect(remoteSnapshot.publicPath).toBe('https://redirected.example/v2/'); + }); + it('return basic app snapshot with only manifest params in dev with getPublicPath', () => { const remoteSnapshot = generateSnapshotFromManifest( manifest.devAppManifestWithGetPublicPath, diff --git a/packages/sdk/rstest.config.ts b/packages/sdk/rstest.config.ts new file mode 100644 index 00000000000..70f19fb797c --- /dev/null +++ b/packages/sdk/rstest.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from '@rstest/core'; +import path from 'path'; + +export default defineConfig({ + source: { + define: { + __DEV__: true, + __TEST__: true, + __BROWSER__: false, + __VERSION__: '"unknown"', + }, + }, + testEnvironment: 'jsdom', + include: [ + path.resolve(__dirname, '__tests__/generateSnapshotFromManifest.spec.ts'), + ], + globals: true, + testTimeout: 10000, +}); diff --git a/packages/sdk/src/generateSnapshotFromManifest.ts b/packages/sdk/src/generateSnapshotFromManifest.ts index 8f4829b4757..7b150ff6399 100644 --- a/packages/sdk/src/generateSnapshotFromManifest.ts +++ b/packages/sdk/src/generateSnapshotFromManifest.ts @@ -13,6 +13,7 @@ interface IOptions { remotes?: Record; overrides?: Record; version?: string; + resolvedManifestUrl?: string; } export const simpleJoinRemoteEntry = (rPath: string, rName: string): string => { @@ -62,7 +63,13 @@ export function generateSnapshotFromManifest( manifest: Manifest, options: IOptions = {}, ): ProviderModuleInfo { - const { remotes = {}, overrides = {}, version } = options; + const { + remotes = {}, + overrides = {}, + version, + resolvedManifestUrl, + } = options; + const publicPathUrl = resolvedManifestUrl || version; let remoteSnapshot: ProviderModuleInfo; const getPublicPath = (): string => { @@ -70,10 +77,10 @@ export function generateSnapshotFromManifest( if ( (manifest.metaData.publicPath === 'auto' || manifest.metaData.publicPath === '') && - version + publicPathUrl ) { // use same implementation as publicPath auto runtime module implements - return inferAutoPublicPath(version); + return inferAutoPublicPath(publicPathUrl); } return manifest.metaData.publicPath; } else { diff --git a/packages/webpack-bundler-runtime/src/init.ts b/packages/webpack-bundler-runtime/src/init.ts index d8a7e9e2d7c..4103c5b557e 100644 --- a/packages/webpack-bundler-runtime/src/init.ts +++ b/packages/webpack-bundler-runtime/src/init.ts @@ -43,7 +43,9 @@ export function init({ webpackRequire }: { webpackRequire: WebpackRequire }) { sharedArgs.forEach((sharedArg) => { shared.push([sharedName, sharedArg]); if ('get' in sharedArg) { - sharedArg.treeShaking ||= {}; + if (!sharedArg.treeShaking) { + sharedArg.treeShaking = {}; + } sharedArg.treeShaking.get = sharedArg.get; sharedArg.get = bundlerRuntime!.getSharedFallbackGetter({ shareKey: sharedName, @@ -132,7 +134,9 @@ export function init({ webpackRequire }: { webpackRequire: WebpackRequire }) { }; }; - initOptions.plugins ||= []; + if (!initOptions.plugins) { + initOptions.plugins = []; + } initOptions.plugins.push(treeShakingSharePlugin()); } return runtime!.init(initOptions); diff --git a/packages/webpack-bundler-runtime/src/updateOptions.ts b/packages/webpack-bundler-runtime/src/updateOptions.ts index 9ce36bcc80b..9132b46df59 100644 --- a/packages/webpack-bundler-runtime/src/updateOptions.ts +++ b/packages/webpack-bundler-runtime/src/updateOptions.ts @@ -177,7 +177,9 @@ export function updateRemoteOptions(options: RemotesOptions) { } if (!idToRemoteMap[moduleId] && remoteInfos[data.remoteName]) { const items = remoteInfos[data.remoteName]; - idToRemoteMap[moduleId] ||= []; + if (!idToRemoteMap[moduleId]) { + idToRemoteMap[moduleId] = []; + } items.forEach((item) => { if (!idToRemoteMap[moduleId].includes(item)) { idToRemoteMap[moduleId].push(item); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1be98937950..7d996a58830 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,12 @@ settings: overrides: '@changesets/assemble-release-plan': workspace:* + '@lynx-js/lynx-bundle-rslib-config': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/lynx-bundle-rslib-config@e22ca09 + '@lynx-js/react': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09 + '@lynx-js/react-rsbuild-plugin': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react-rsbuild-plugin@e22ca09 + '@lynx-js/rspeedy': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/rspeedy@e22ca09 + '@lynx-js/template-webpack-plugin': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09 + '@lynx-js/web-core': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-core@e22ca09 ajv: 8.18.0 eslint>ajv: 6.14.0 '@eslint/eslintrc>ajv': 6.14.0 @@ -57,7 +63,7 @@ importers: version: 0.14.1 sharp: specifier: ^0.35.0 - version: 0.35.0 + version: 0.35.3(@types/node@20.19.5) storybook: specifier: 8.6.17 version: 8.6.17(prettier@3.8.1) @@ -121,10 +127,10 @@ importers: version: 0.5.15(react-refresh@0.14.2)(type-fest@2.19.0)(webpack-dev-server@5.2.3(webpack-cli@5.1.4)(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)))(webpack-hot-middleware@2.26.1)(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) '@rollup/plugin-alias': specifier: 5.1.1 - version: 5.1.1(rollup@4.62.2) + version: 5.1.1(rollup@4.59.0) '@rollup/plugin-replace': specifier: 6.0.1 - version: 6.0.1(rollup@4.62.2) + version: 6.0.1(rollup@4.59.0) '@rslib/core': specifier: ^0.23.2 version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@20.19.5))(@module-federation/runtime-tools@2.8.2)(core-js@3.36.1)(typescript@6.0.3) @@ -217,7 +223,7 @@ importers: version: 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@6.0.3) autoprefixer: specifier: 10.4.20 - version: 10.4.20(postcss@8.5.26) + version: 10.4.20(postcss@8.5.24) babel-jest: specifier: 29.7.0 version: 29.7.0(@babel/core@7.29.0) @@ -319,16 +325,16 @@ importers: version: 10.2.0 postcss-calc: specifier: 9.0.1 - version: 9.0.1(postcss@8.5.26) + version: 9.0.1(postcss@8.5.24) postcss-custom-properties: specifier: 13.3.12 - version: 13.3.12(postcss@8.5.26) + version: 13.3.12(postcss@8.5.24) postcss-import: specifier: 15.1.0 - version: 15.1.0(postcss@8.5.26) + version: 15.1.0(postcss@8.5.24) postcss-url: specifier: 10.1.3 - version: 10.1.3(postcss@8.5.26) + version: 10.1.3(postcss@8.5.24) prettier: specifier: 3.8.1 version: 3.8.1 @@ -388,7 +394,7 @@ importers: version: 2.8.1 tsup: specifier: 7.3.0 - version: 7.3.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(postcss@8.5.26)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@6.0.3))(typescript@6.0.3) + version: 7.3.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(postcss@8.5.24)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@6.0.3))(typescript@6.0.3) turbo: specifier: ^2.9.14 version: 2.10.2 @@ -406,7 +412,7 @@ importers: version: 4.2.3(typescript@6.0.3)(vite@7.3.5(@types/node@20.19.5)(jiti@2.6.1)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0)) vitest: specifier: 3.2.6 - version: 3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@20.19.5)(jiti@2.6.1)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0) + version: 3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@20.19.5)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) vue-tsc: specifier: ^2.2.10 version: 2.2.12(typescript@6.0.3) @@ -632,6 +638,58 @@ importers: specifier: 7.0.2 version: 7.0.2 + apps/lynx-module-federation-demo: + dependencies: + '@lynx-js/react': + specifier: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09 + version: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09(@lynx-js/types@4.0.0)(@types/react@18.3.28) + '@module-federation/runtime': + specifier: workspace:* + version: link:../../packages/runtime + devDependencies: + '@lynx-js/lynx-bundle-rslib-config': + specifier: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/lynx-bundle-rslib-config@e22ca09 + version: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/lynx-bundle-rslib-config@e22ca09(tslib@2.8.1) + '@lynx-js/qrcode-rsbuild-plugin': + specifier: 0.6.0 + version: 0.6.0(@lynx-js/rspeedy@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/rspeedy@e22ca09(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@module-federation/runtime-tools@packages+runtime-tools)(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(core-js@3.49.0)(esbuild@0.28.1)(typescript@6.0.3)(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4))) + '@lynx-js/react-rsbuild-plugin': + specifier: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react-rsbuild-plugin@e22ca09 + version: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react-rsbuild-plugin@e22ca09(@lynx-js/react@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09(@lynx-js/types@4.0.0)(@types/react@18.3.28))(tslib@2.8.1) + '@lynx-js/rspeedy': + specifier: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/rspeedy@e22ca09 + version: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/rspeedy@e22ca09(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@module-federation/runtime-tools@packages+runtime-tools)(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(core-js@3.49.0)(esbuild@0.28.1)(typescript@6.0.3)(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + '@lynx-js/types': + specifier: 4.0.0 + version: 4.0.0 + '@lynx-js/web-core': + specifier: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-core@e22ca09 + version: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-core@e22ca09(@lynx-js/css-serializer@0.1.6)(tslib@2.8.1) + '@lynx-js/web-elements': + specifier: 0.12.6 + version: 0.12.6(tslib@2.8.1) + '@module-federation/lynx': + specifier: workspace:* + version: link:../../packages/lynx + '@module-federation/runtime-tools': + specifier: workspace:* + version: link:../../packages/runtime-tools + '@playwright/test': + specifier: 1.57.0 + version: 1.57.0 + '@rsbuild/core': + specifier: 2.1.4 + version: 2.1.4(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0) + '@rspack-canary/core': + specifier: 2.1.5-canary-54a0d8f3-20260715194831 + version: 2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23) + '@rspack/core': + specifier: npm:@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831 + version: '@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23)' + '@types/react': + specifier: 18.3.28 + version: 18.3.28 + apps/manifest-demo/3009-webpack-provider: dependencies: antd: @@ -843,7 +901,7 @@ importers: version: 0.80.0(@babel/core@7.29.0) '@react-native/eslint-config': specifier: 0.80.0 - version: 0.80.0(eslint@8.57.1)(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2)))(prettier@2.8.8)(typescript@7.0.2) + version: 0.80.0(eslint@8.57.1)(jest@29.7.0(@types/node@26.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)))(prettier@2.8.8)(typescript@7.0.2) '@react-native/gradle-plugin': specifier: 0.80.0 version: 0.80.0 @@ -882,7 +940,7 @@ importers: version: 8.57.1 jest: specifier: ^29.6.3 - version: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2)) + version: 29.7.0(@types/node@26.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)) nodemon: specifier: ^3.1.9 version: 3.1.14 @@ -937,7 +995,7 @@ importers: version: 0.80.0(@babel/core@7.29.0) '@react-native/eslint-config': specifier: 0.80.0 - version: 0.80.0(eslint@8.57.1)(jest@29.7.0(@types/node@26.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)))(prettier@2.8.8)(typescript@7.0.2) + version: 0.80.0(eslint@8.57.1)(jest@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2)))(prettier@2.8.8)(typescript@7.0.2) '@react-native/metro-config': specifier: 0.80.0 version: 0.80.0(@babel/core@7.29.0) @@ -973,7 +1031,7 @@ importers: version: 8.57.1 jest: specifier: ^29.6.3 - version: 29.7.0(@types/node@26.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)) + version: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2)) nodemon: specifier: ^3.1.9 version: 3.1.14 @@ -1028,7 +1086,7 @@ importers: version: 0.80.0(@babel/core@7.29.0) '@react-native/eslint-config': specifier: 0.80.0 - version: 0.80.0(eslint@8.57.1)(jest@29.7.0(@types/node@26.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)))(prettier@2.8.8)(typescript@7.0.2) + version: 0.80.0(eslint@8.57.1)(jest@29.7.0(@types/node@26.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)))(prettier@2.8.8)(typescript@7.0.2) '@react-native/metro-config': specifier: 0.80.0 version: 0.80.0(@babel/core@7.29.0) @@ -1064,7 +1122,7 @@ importers: version: 8.57.1 jest: specifier: ^29.6.3 - version: 29.7.0(@types/node@26.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)) + version: 29.7.0(@types/node@26.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)) nodemon: specifier: ^3.1.9 version: 3.1.14 @@ -1110,7 +1168,7 @@ importers: version: 2.59.0(typescript@7.0.2) '@modern-js/app-tools': specifier: 3.5.0 - version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) + version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) '@modern-js/eslint-config': specifier: 2.59.0 version: 2.59.0(typescript@7.0.2) @@ -1171,7 +1229,7 @@ importers: version: 2.59.0(typescript@7.0.2) '@modern-js/app-tools': specifier: 3.5.0 - version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) + version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) '@modern-js/eslint-config': specifier: 2.59.0 version: 2.59.0(typescript@7.0.2) @@ -1225,7 +1283,7 @@ importers: version: 1.4.5(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(webpack-hot-middleware@2.26.1) '@rslib/core': specifier: ^0.9.0 - version: 0.9.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(typescript@5.9.3) + version: 0.9.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(typescript@5.9.3) '@types/react': specifier: ^18.3.11 version: 18.3.28 @@ -1268,7 +1326,7 @@ importers: version: 2.59.0(typescript@7.0.2) '@modern-js/app-tools': specifier: 3.5.0 - version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) + version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) '@modern-js/eslint-config': specifier: 2.59.0 version: 2.59.0(typescript@7.0.2) @@ -1326,7 +1384,7 @@ importers: version: 1.4.5(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(webpack-hot-middleware@2.26.1) '@rslib/core': specifier: ^0.9.0 - version: 0.9.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(typescript@5.9.3) + version: 0.9.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(typescript@5.9.3) '@types/react': specifier: ^18.3.11 version: 18.3.28 @@ -1360,7 +1418,7 @@ importers: version: 2.59.0(typescript@7.0.2) '@modern-js/app-tools': specifier: 3.5.0 - version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) + version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) '@modern-js/eslint-config': specifier: 2.59.0 version: 2.59.0(typescript@7.0.2) @@ -1421,7 +1479,7 @@ importers: version: 2.59.0(typescript@7.0.2) '@modern-js/app-tools': specifier: 3.5.0 - version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) + version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) '@modern-js/eslint-config': specifier: 2.59.0 version: 2.59.0(typescript@7.0.2) @@ -1482,7 +1540,7 @@ importers: version: 2.59.0(typescript@7.0.2) '@modern-js/app-tools': specifier: 3.5.0 - version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) + version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) '@modern-js/eslint-config': specifier: 2.59.0 version: 2.59.0(typescript@7.0.2) @@ -1543,7 +1601,7 @@ importers: version: 2.59.0(typescript@7.0.2) '@modern-js/app-tools': specifier: 3.5.0 - version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) + version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) '@modern-js/eslint-config': specifier: 2.59.0 version: 2.59.0(typescript@7.0.2) @@ -1604,7 +1662,7 @@ importers: version: 2.59.0(typescript@7.0.2) '@modern-js/app-tools': specifier: 3.5.0 - version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) + version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) '@modern-js/eslint-config': specifier: 2.59.0 version: 2.59.0(typescript@7.0.2) @@ -1880,7 +1938,7 @@ importers: devDependencies: '@rslib/core': specifier: ^0.23.2 - version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) + version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) apps/react-ts-host: dependencies: @@ -2066,7 +2124,7 @@ importers: version: 18.3.7(@types/react@18.3.28) tailwindcss: specifier: ^3.4.3 - version: 3.4.13(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@5.9.3)) + version: 3.4.13(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@5.9.3)) typescript: specifier: ^5.4.5 version: 5.9.3 @@ -2115,7 +2173,7 @@ importers: version: 18.3.7(@types/react@18.3.28) tailwindcss: specifier: ^3.4.3 - version: 3.4.13(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)) + version: 3.4.13(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)) typescript: specifier: 7.0.2 version: 7.0.2 @@ -2146,7 +2204,7 @@ importers: version: 2.0.1(@rsbuild/core@2.1.4(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(vue@3.5.30(typescript@7.0.2)) tailwindcss: specifier: ^3.4.3 - version: 3.4.13(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)) + version: 3.4.13(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)) typescript: specifier: 7.0.2 version: 7.0.2 @@ -2266,7 +2324,7 @@ importers: version: 0.5.1 tailwindcss: specifier: ^3.4.3 - version: 3.4.13(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)) + version: 3.4.13(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)) typescript: specifier: 7.0.2 version: 7.0.2 @@ -2413,7 +2471,7 @@ importers: version: 2.1.0(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23)) '@rslib/core': specifier: ^0.23.2 - version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) + version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) '@types/react': specifier: ^18.3.11 version: 18.3.28 @@ -2431,10 +2489,10 @@ importers: version: 8.6.17(prettier@3.8.1) storybook-addon-rslib: specifier: ^1.0.1 - version: 1.0.3(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(@rslib/core@0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2))(storybook-builder-rsbuild@1.0.3(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(@types/react@18.3.28)(storybook@8.6.17(prettier@3.8.1))(tslib@2.8.1)(typescript@7.0.2))(typescript@7.0.2) + version: 1.0.3(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(@rslib/core@0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2))(storybook-builder-rsbuild@1.0.3(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(@types/react@18.3.28)(storybook@8.6.17(prettier@3.8.1))(tslib@2.8.1)(typescript@7.0.2))(typescript@7.0.2) storybook-react-rsbuild: specifier: ^1.0.1 - version: 1.0.3(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(storybook@8.6.17(prettier@3.8.1))(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.25.5)(webpack-cli@5.1.4)) + version: 1.0.3(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(storybook@8.6.17(prettier@3.8.1))(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.25.5)(webpack-cli@5.1.4)) apps/rstest-federation-host: devDependencies: @@ -2715,10 +2773,10 @@ importers: version: 2.59.0(typescript@7.0.2) '@modern-js/app-tools': specifier: 3.5.0 - version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@3.14.2)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) + version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@3.14.2)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) '@modern-js/plugin-server': specifier: 2.68.0 - version: 2.68.0(@babel/traverse@7.29.8)(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.68.0(@babel/traverse@7.29.7)(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@modern-js/server-runtime': specifier: 3.0.1 version: 3.0.1(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2785,13 +2843,13 @@ importers: version: 2.59.0(typescript@7.0.2) '@modern-js/app-tools': specifier: 3.5.0 - version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) + version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) '@modern-js/eslint-config': specifier: 2.59.0 version: 2.59.0(typescript@7.0.2) '@modern-js/plugin-server': specifier: 2.68.0 - version: 2.68.0(@babel/traverse@7.29.8)(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.68.0(@babel/traverse@7.29.7)(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@modern-js/tsconfig': specifier: 3.0.1 version: 3.0.1 @@ -2852,13 +2910,13 @@ importers: version: 2.59.0(typescript@7.0.2) '@modern-js/app-tools': specifier: 3.5.0 - version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@3.14.2)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) + version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@3.14.2)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) '@modern-js/eslint-config': specifier: 2.59.0 version: 2.59.0(typescript@7.0.2) '@modern-js/plugin-server': specifier: 2.68.0 - version: 2.68.0(@babel/traverse@7.29.8)(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.68.0(@babel/traverse@7.29.7)(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@modern-js/server-runtime': specifier: 3.0.1 version: 3.0.1(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2928,13 +2986,13 @@ importers: version: 2.59.0(typescript@7.0.2) '@modern-js/app-tools': specifier: 3.5.0 - version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) + version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))) '@modern-js/eslint-config': specifier: 2.59.0 version: 2.59.0(typescript@7.0.2) '@modern-js/plugin-server': specifier: 2.68.0 - version: 2.68.0(@babel/traverse@7.29.8)(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.68.0(@babel/traverse@7.29.7)(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@modern-js/tsconfig': specifier: 3.0.1 version: 3.0.1 @@ -3072,13 +3130,13 @@ importers: version: 18.3.0 '@vitejs/plugin-react': specifier: ^4.3.3 - version: 4.7.0(vite@5.4.21(@types/node@26.2.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)) + version: 4.7.0(vite@5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)) '@vitejs/plugin-vue': specifier: ^5.0.4 - version: 5.2.4(vite@5.4.21(@types/node@26.2.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0))(vue@3.5.30(typescript@7.0.2)) + version: 5.2.4(vite@5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0))(vue@3.5.30(typescript@7.0.2)) '@vitejs/plugin-vue-jsx': specifier: ^4.0.0 - version: 4.2.0(vite@5.4.21(@types/node@26.2.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0))(vue@3.5.30(typescript@7.0.2)) + version: 4.2.0(vite@5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0))(vue@3.5.30(typescript@7.0.2)) hono: specifier: 4.13.1 version: 4.13.1 @@ -3099,7 +3157,7 @@ importers: version: 7.0.2 vite: specifier: ^5.4.21 - version: 5.4.21(@types/node@26.2.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) + version: 5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) packages/bridge/bridge-react-webpack-plugin: dependencies: @@ -3112,7 +3170,7 @@ importers: version: 7.0.2 vite: specifier: ^5.4.21 - version: 5.4.21(@types/node@26.2.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) + version: 5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) packages/bridge/bridge-shared: devDependencies: @@ -3161,16 +3219,16 @@ importers: version: 18.3.28 '@vitejs/plugin-vue': specifier: ^5.0.4 - version: 5.2.4(vite@5.4.21(@types/node@26.2.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0))(vue@3.5.30(typescript@7.0.2)) + version: 5.2.4(vite@5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0))(vue@3.5.30(typescript@7.0.2)) '@vitejs/plugin-vue-jsx': specifier: ^4.0.0 - version: 4.2.0(vite@5.4.21(@types/node@26.2.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0))(vue@3.5.30(typescript@7.0.2)) + version: 4.2.0(vite@5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0))(vue@3.5.30(typescript@7.0.2)) typescript: specifier: 7.0.2 version: 7.0.2 vite: specifier: ^5.4.21 - version: 5.4.21(@types/node@26.2.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) + version: 5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) vue: specifier: ^3.5.30 version: 3.5.30(typescript@7.0.2) @@ -3225,7 +3283,7 @@ importers: version: 2.59.0(typescript@7.0.2) '@modern-js/app-tools': specifier: 2.70.8 - version: 2.70.8(@rspack/core@1.7.9(@swc/helpers@0.5.19))(@swc/core@1.15.41(@swc/helpers@0.5.19))(encoding@0.1.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.62.2)(styled-components@6.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.19))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(type-fest@2.19.0)(typescript@7.0.2)(webpack-cli@5.1.4)(webpack-dev-server@5.2.3(webpack-cli@5.1.4)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.19))(esbuild@0.18.20)(webpack-cli@5.1.4)))(webpack-hot-middleware@2.26.1) + version: 2.70.8(@rspack/core@1.7.9(@swc/helpers@0.5.19))(@swc/core@1.15.41(@swc/helpers@0.5.19))(encoding@0.1.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(styled-components@6.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.19))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(type-fest@2.19.0)(typescript@7.0.2)(webpack-cli@5.1.4)(webpack-dev-server@5.2.3(webpack-cli@5.1.4)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.19))(esbuild@0.18.20)(webpack-cli@5.1.4)))(webpack-hot-middleware@2.26.1) '@modern-js/eslint-config': specifier: 2.59.0 version: 2.59.0(typescript@7.0.2) @@ -3279,7 +3337,7 @@ importers: version: 7.0.2 vitest: specifier: 3.2.6 - version: 3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@20.19.5)(jiti@2.7.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0) + version: 3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@20.19.5)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) packages/cli: dependencies: @@ -3332,7 +3390,7 @@ importers: devDependencies: '@rslib/core': specifier: ^0.23.2 - version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) + version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) '@types/glob': specifier: 7.2.0 version: 7.2.0 @@ -3511,7 +3569,53 @@ importers: devDependencies: '@rslib/core': specifier: ^0.23.2 - version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) + version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) + typescript: + specifier: 7.0.2 + version: 7.0.2 + + packages/lynx: + dependencies: + '@lynx-js/react': + specifier: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09 + version: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09(@lynx-js/types@4.0.0)(@types/react@19.2.14) + '@lynx-js/template-webpack-plugin': + specifier: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09 + version: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09(tslib@2.8.1) + '@lynx-js/web-core': + specifier: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-core@e22ca09 + version: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-core@e22ca09(@lynx-js/css-serializer@0.1.6)(tslib@2.8.1) + '@module-federation/runtime-core': + specifier: workspace:* + version: link:../runtime-core + '@module-federation/sdk': + specifier: workspace:* + version: link:../sdk + devDependencies: + '@lynx-js/cache-events-webpack-plugin': + specifier: 0.2.0 + version: 0.2.0 + '@lynx-js/chunk-loading-webpack-plugin': + specifier: 0.4.0 + version: 0.4.0 + '@lynx-js/css-serializer': + specifier: 0.1.6 + version: 0.1.6 + '@lynx-js/tasm': + specifier: 0.0.39 + version: 0.0.39 + '@module-federation/runtime-tools': + specifier: workspace:* + version: link:../runtime-tools + '@rsbuild/core': + specifier: 2.1.4 + version: 2.1.4(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0) + '@rspack/core': + specifier: npm:@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831 + version: '@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23)' + '@rstest/core': + specifier: ^0.10.6 + version: 0.10.6(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0)(jsdom@20.0.3) typescript: specifier: 7.0.2 version: 7.0.2 @@ -3725,10 +3829,10 @@ importers: devDependencies: '@modern-js/app-tools': specifier: 2.70.5 - version: 2.70.5(@rspack/core@1.7.9(@swc/helpers@0.5.17))(@swc/core@1.15.41(@swc/helpers@0.5.17))(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rollup@4.62.2)(styled-components@6.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.2.0)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(type-fest@2.19.0)(typescript@7.0.2)(webpack-cli@5.1.4)(webpack-dev-server@5.2.3(webpack-cli@5.1.4)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.25.5)(webpack-cli@5.1.4)))(webpack-hot-middleware@2.26.1) + version: 2.70.5(@rspack/core@1.7.9(@swc/helpers@0.5.17))(@swc/core@1.15.41(@swc/helpers@0.5.17))(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rollup@4.59.0)(styled-components@6.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.1.0)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(type-fest@2.19.0)(typescript@7.0.2)(webpack-cli@5.1.4)(webpack-dev-server@5.2.3(webpack-cli@5.1.4)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.25.5)(webpack-cli@5.1.4)))(webpack-hot-middleware@2.26.1) '@modern-js/module-tools': specifier: 2.70.5 - version: 2.70.5(@types/node@26.2.0)(typescript@7.0.2) + version: 2.70.5(@types/node@26.1.0)(typescript@7.0.2) '@modern-js/runtime': specifier: 2.70.5 version: 2.70.5(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.25.5)(webpack-cli@5.1.4)))(react@19.2.7) @@ -3746,7 +3850,7 @@ importers: version: 1.4.5(@rsbuild/core@1.3.21)(webpack-hot-middleware@2.26.1) '@rslib/core': specifier: 0.23.2 - version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) + version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) '@rstest/core': specifier: ^0.10.6 version: 0.10.6(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(jsdom@20.0.3) @@ -3801,10 +3905,10 @@ importers: devDependencies: '@modern-js/app-tools': specifier: 3.5.0 - version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.0.6(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.17))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.0.6(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.2.0)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.28.1)(webpack-cli@5.1.4)) + version: 3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.0.6(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.17))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.0.6(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.1.0)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.28.1)(webpack-cli@5.1.4)) '@modern-js/module-tools': specifier: 2.70.5 - version: 2.70.5(@types/node@26.2.0)(typescript@7.0.2) + version: 2.70.5(@types/node@26.1.0)(typescript@7.0.2) '@modern-js/runtime': specifier: 3.5.0 version: 3.5.0(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.0.6(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) @@ -3822,7 +3926,7 @@ importers: version: 1.4.5(@rsbuild/core@2.1.4(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(webpack-hot-middleware@2.26.1) '@rslib/core': specifier: 0.23.2 - version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) + version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) '@rspack/core': specifier: 2.0.6 version: 2.0.6(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.17) @@ -4082,7 +4186,7 @@ importers: version: 2.1.4(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0) '@rslib/core': specifier: ^0.23.2 - version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) + version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) '@rstest/core': specifier: ^0.10.6 version: 0.10.6(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(jsdom@20.0.3) @@ -4153,7 +4257,7 @@ importers: devDependencies: '@rslib/core': specifier: ^0.23.2 - version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) + version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) '@rspress/core': specifier: 2.0.14 version: 2.0.14(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(@types/mdast@4.0.4)(@types/react@18.3.28)(core-js@3.49.0)(micromark-util-types@2.0.2)(micromark@4.0.2) @@ -4190,7 +4294,7 @@ importers: version: 2.1.4(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0) '@rslib/core': specifier: ^0.23.2 - version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@6.0.3) + version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@6.0.3) '@rstest/core': specifier: 0.11.6 version: 0.11.6(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(jsdom@20.0.3) @@ -4253,10 +4357,10 @@ importers: version: link:../sdk '@nx/react': specifier: '>= 16.0.0' - version: 22.5.4(@babel/core@7.29.7)(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@swc/helpers@0.5.23)(@types/babel__core@7.20.5)(@zkochan/js-yaml@0.0.7)(esbuild@0.25.5)(eslint@9.39.3(jiti@2.7.0))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(vite@7.3.6(@types/node@26.2.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))(vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.2.0)(jiti@2.7.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))(vue-tsc@2.2.12(typescript@7.0.2))(webpack-cli@5.1.4) + version: 22.5.4(@babel/core@7.29.7)(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@swc/helpers@0.5.23)(@types/babel__core@7.20.5)(@zkochan/js-yaml@0.0.7)(esbuild@0.25.5)(eslint@9.39.3(jiti@2.7.0))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(vite@7.3.5(@types/node@26.1.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))(vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.1.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0))(vue-tsc@2.2.12(typescript@7.0.2))(webpack-cli@5.1.4) '@nx/webpack': specifier: '>= 16.0.0' - version: 22.5.4(@babel/traverse@7.29.8)(@rspack/core@1.6.8(@swc/helpers@0.5.23))(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(html-webpack-plugin@5.6.6(@rspack/core@1.6.8(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(webpack-cli@5.1.4)))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(typescript@7.0.2)(vue-template-compiler@2.7.16)(webpack-cli@5.1.4) + version: 22.5.4(@babel/traverse@7.29.7)(@rspack/core@1.6.8(@swc/helpers@0.5.23))(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(html-webpack-plugin@5.6.6(@rspack/core@1.6.8(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(webpack-cli@5.1.4)))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(typescript@7.0.2)(vue-template-compiler@2.7.16)(webpack-cli@5.1.4) storybook: specifier: '>= 8.2.0' version: 8.6.17(prettier@3.8.1) @@ -4266,7 +4370,7 @@ importers: version: link:../utilities '@nx/module-federation': specifier: '>= 16.0.0' - version: 22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@swc/helpers@0.5.23)(esbuild@0.25.5)(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(vue-tsc@2.2.12(typescript@7.0.2))(webpack-cli@5.1.4) + version: 22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@swc/helpers@0.5.23)(esbuild@0.25.5)(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(vue-tsc@2.2.12(typescript@7.0.2))(webpack-cli@5.1.4) '@rsbuild/core': specifier: 2.1.4 version: 2.1.4(@module-federation/runtime-tools@2.2.2(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.49.0) @@ -4305,7 +4409,7 @@ importers: version: 18.3.1 tsup: specifier: 7.3.0 - version: 7.3.0(@swc/core@1.15.41(@swc/helpers@0.5.23))(postcss@8.5.26)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(typescript@7.0.2) + version: 7.3.0(@swc/core@1.15.41(@swc/helpers@0.5.23))(postcss@8.5.24)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(typescript@7.0.2) typescript: specifier: 7.0.2 version: 7.0.2 @@ -4537,7 +4641,7 @@ importers: dependencies: '@hono/node-server': specifier: 2.0.11 - version: 2.0.11(hono@4.13.0) + version: 2.0.11(hono@4.13.1) '@hono/zod-validator': specifier: 0.7.4 version: 0.7.4(hono@4.13.1)(zod@4.1.12) @@ -4580,7 +4684,7 @@ importers: version: 20.19.5 '@vercel/nft': specifier: ^1.1.1 - version: 1.3.2(encoding@0.1.13)(rollup@4.62.2) + version: 1.3.2(encoding@0.1.13)(rollup@4.59.0) pino-pretty: specifier: ^13.1.2 version: 13.1.3 @@ -4879,6 +4983,10 @@ packages: resolution: {integrity: sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==} hasBin: true + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -4929,10 +5037,6 @@ packages: resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} engines: {node: '>=6.9.0'} - '@babel/generator@7.29.8': - resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} - engines: {node: '>=6.9.0'} - '@babel/generator@8.0.0-rc.2': resolution: {integrity: sha512-oCQ1IKPwkzCeJzAPb7Fv8rQ9k5+1sG8mf2uoHiMInPYvkRfrDJxbTIbH51U+jstlkghus0vAi3EBvkfvEsYNLQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5111,11 +5215,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.29.8': - resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/parser@8.0.0-rc.2': resolution: {integrity: sha512-29AhEtcq4x8Dp3T72qvUMZHx0OMXCj4Jy/TEReQa+KWLln524Cj1fWb3QFi0l/xSpptQBR6y9RNEXuxpFvwiUQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5171,7 +5270,6 @@ packages: '@babel/plugin-proposal-object-rest-spread@7.12.1': resolution: {integrity: sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead. peerDependencies: '@babel/core': ^7.0.0-0 @@ -5797,10 +5895,6 @@ packages: resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.8': - resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} - engines: {node: '>=6.9.0'} - '@babel/types@7.29.0': resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} @@ -5809,10 +5903,6 @@ packages: resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.8': - resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} - engines: {node: '>=6.9.0'} - '@babel/types@8.0.0-rc.2': resolution: {integrity: sha512-91gAaWRznDwSX4E2tZ1YjBuIfnQVOFDCQ2r0Toby0gu4XEbyF623kXLMA8d4ZbCu+fINcrudkmEcwSUHgDDkNw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6179,27 +6269,24 @@ packages: '@emnapi/core@1.11.1': resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + '@emnapi/core@1.11.2': + resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} + '@emnapi/core@1.11.3': resolution: {integrity: sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==} - '@emnapi/core@1.9.0': - resolution: {integrity: sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==} - '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} '@emnapi/runtime@1.11.1': resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + '@emnapi/runtime@1.11.2': + resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} + '@emnapi/runtime@1.11.3': resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} - '@emnapi/runtime@1.9.0': - resolution: {integrity: sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==} - - '@emnapi/wasi-threads@1.2.0': - resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} - '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} @@ -7443,8 +7530,8 @@ packages: peerDependencies: react: '>= 16 || ^19.0.0-rc' - '@hono/node-server@1.19.17': - resolution: {integrity: sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==} + '@hono/node-server@1.19.13': + resolution: {integrity: sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==} engines: {node: '>=18.14.1'} peerDependencies: hono: ^4 @@ -7477,7 +7564,6 @@ packages: '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} @@ -7485,7 +7571,6 @@ packages: '@humanwhocodes/object-schema@2.0.3': resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead '@humanwhocodes/retry@0.4.3': resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} @@ -7517,8 +7602,8 @@ packages: cpu: [arm64] os: [darwin] - '@img/sharp-darwin-arm64@0.35.0': - resolution: {integrity: sha512-ZgaYEwaj+lx/5n4W8GmZ2IYz0PQHjN5eqRcfijWGB+2Aq7ZInZGa0qJyAn6DEtyLuWHRSrmWOqT9q3qqTBvmUQ==} + '@img/sharp-darwin-arm64@0.35.3': + resolution: {integrity: sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [darwin] @@ -7529,14 +7614,14 @@ packages: cpu: [x64] os: [darwin] - '@img/sharp-darwin-x64@0.35.0': - resolution: {integrity: sha512-c1z9LFpKB0slQW3RchwBE8iSVzGp70TNjUUO9k4BZwwW4HH7JBGHeIy4b+kk4n/kcBASb9evKCE3/7Slmslgiw==} + '@img/sharp-darwin-x64@0.35.3': + resolution: {integrity: sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==} engines: {node: '>=20.9.0'} cpu: [x64] os: [darwin] - '@img/sharp-freebsd-wasm32@0.35.0': - resolution: {integrity: sha512-Li2KTev0H90kEtnJHkI9xQojXt1AqWmFBMXiPw5kqd1jQgP7gi5HVK/qC5Rmh/59NuAwUuPzzPITmX22NomYYQ==} + '@img/sharp-freebsd-wasm32@0.35.3': + resolution: {integrity: sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==} engines: {node: '>=20.9.0'} os: [freebsd] @@ -7545,8 +7630,8 @@ packages: cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.3.0': - resolution: {integrity: sha512-EKbmBKtyTH+GPFDRw2TgK2oV6hyxxlJVIar4hoTYSNmIwipgMFdxPQqR392GmfdsPGWga0mCFN1cCKjRb9cljw==} + '@img/sharp-libvips-darwin-arm64@1.3.2': + resolution: {integrity: sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==} cpu: [arm64] os: [darwin] @@ -7555,8 +7640,8 @@ packages: cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.3.0': - resolution: {integrity: sha512-Pl2OmOvrJ42adUllESxBsG54PfXLo1OYg9i3c5/5Ln/qJ0gZuTM9YMhQJPIbXqwidLRc/c2zuHt4RsrymmNv7A==} + '@img/sharp-libvips-darwin-x64@1.3.2': + resolution: {integrity: sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==} cpu: [x64] os: [darwin] @@ -7565,8 +7650,8 @@ packages: cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm64@1.3.0': - resolution: {integrity: sha512-C0SqjoFKnszqa44EQ7xoaT48nnO0lOyXEULfXMWi8krrjOPGYkeK30Okzla6ATbBYsyZ0ySinK0FVkpv3DwzfQ==} + '@img/sharp-libvips-linux-arm64@1.3.2': + resolution: {integrity: sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==} cpu: [arm64] os: [linux] @@ -7575,8 +7660,8 @@ packages: cpu: [arm] os: [linux] - '@img/sharp-libvips-linux-arm@1.3.0': - resolution: {integrity: sha512-A8UpHoUDW4DwnXoV6+q3C1s7QLRAHtPDEjWuNZjwHMyoCNZnm0GeNN8ls9f/bsEYTRQRW96C/n34XJQHJ2fT7A==} + '@img/sharp-libvips-linux-arm@1.3.2': + resolution: {integrity: sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==} cpu: [arm] os: [linux] @@ -7585,8 +7670,8 @@ packages: cpu: [ppc64] os: [linux] - '@img/sharp-libvips-linux-ppc64@1.3.0': - resolution: {integrity: sha512-WOpkVxAjFd369iaIzEgNRreFD+gWdUMIGD5zplhNKNeqS6mm5dac3q2AFyCBmzYoAdouzZvRBgxy4z8QHZb4/A==} + '@img/sharp-libvips-linux-ppc64@1.3.2': + resolution: {integrity: sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==} cpu: [ppc64] os: [linux] @@ -7595,8 +7680,8 @@ packages: cpu: [riscv64] os: [linux] - '@img/sharp-libvips-linux-riscv64@1.3.0': - resolution: {integrity: sha512-DRWw0mOHusrCCuw2rqP87oLg6PGlkomVDFqw2hIwsSfwWpu4k3XLcBPaKKl6ct/GtL/cwNkgwjV/tc0Mqht3VA==} + '@img/sharp-libvips-linux-riscv64@1.3.2': + resolution: {integrity: sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==} cpu: [riscv64] os: [linux] @@ -7605,8 +7690,8 @@ packages: cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-s390x@1.3.0': - resolution: {integrity: sha512-9APy+nFWhHS+kzLgWZfLcyrUd7YqnAQVa4BPOo4xkoHpdoktOAPG4cEr9+Jpl0TtqfVmcMJimNL5qNTyyOHZNA==} + '@img/sharp-libvips-linux-s390x@1.3.2': + resolution: {integrity: sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==} cpu: [s390x] os: [linux] @@ -7615,8 +7700,8 @@ packages: cpu: [x64] os: [linux] - '@img/sharp-libvips-linux-x64@1.3.0': - resolution: {integrity: sha512-y9RNUYDe2A1UAdhLyfeOodGRszQdaEoe4nfOpp/sNVPl2CWIcUyFaDoCh4vPLPxu19803j2naLqZup2WxDXCLA==} + '@img/sharp-libvips-linux-x64@1.3.2': + resolution: {integrity: sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==} cpu: [x64] os: [linux] @@ -7625,8 +7710,8 @@ packages: cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.3.0': - resolution: {integrity: sha512-cC1wkC0Mlucd0KSiGrLkJnB/ZqPvZCntc/Lk7ZnYO5ZSbF2euNek4Xvxafojq+wN1q/W0eprdpUIjUr/EV2PBg==} + '@img/sharp-libvips-linuxmusl-arm64@1.3.2': + resolution: {integrity: sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==} cpu: [arm64] os: [linux] @@ -7635,8 +7720,8 @@ packages: cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.3.0': - resolution: {integrity: sha512-LiYMhUZicB1QG//+RvmYZpXJO8fYRENfp+MZUCnG9aw+AKvGAy9gPaCnuwsPcBFs8EV66M0NNxj9VHcNklE8zw==} + '@img/sharp-libvips-linuxmusl-x64@1.3.2': + resolution: {integrity: sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==} cpu: [x64] os: [linux] @@ -7646,8 +7731,8 @@ packages: cpu: [arm64] os: [linux] - '@img/sharp-linux-arm64@0.35.0': - resolution: {integrity: sha512-4+4XHLNT5wDT0roYlHTEmH9lDKt0acf9Tv+3hM3iceOirkxrR404/3WjAYZ9F9CkHrxeRcGLJXbi4vluMZ9O+A==} + '@img/sharp-linux-arm64@0.35.3': + resolution: {integrity: sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] @@ -7658,8 +7743,8 @@ packages: cpu: [arm] os: [linux] - '@img/sharp-linux-arm@0.35.0': - resolution: {integrity: sha512-VVlpEWwizEFIOom0zdoeKuO5nuTswzVE5uHcBNvHzmeHUpNFajY3HFfbQ+zIH4E2kVaZ/yVxmsShW56TtEy4uA==} + '@img/sharp-linux-arm@0.35.3': + resolution: {integrity: sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==} engines: {node: '>=20.9.0'} cpu: [arm] os: [linux] @@ -7670,8 +7755,8 @@ packages: cpu: [ppc64] os: [linux] - '@img/sharp-linux-ppc64@0.35.0': - resolution: {integrity: sha512-N3hzbEpUTJC8pWpPVJvgzGxM+so/MAXc8O2s/53B0LL9ZGpfXpME7Wizkc5d/8fRBlBtkDjzoZGDCqqNDHqLEw==} + '@img/sharp-linux-ppc64@0.35.3': + resolution: {integrity: sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==} engines: {node: '>=20.9.0'} cpu: [ppc64] os: [linux] @@ -7682,8 +7767,8 @@ packages: cpu: [riscv64] os: [linux] - '@img/sharp-linux-riscv64@0.35.0': - resolution: {integrity: sha512-l6vmKVPnbS0RhVMbyxP5meAARsbhCnBN4fy31qz0+3a6Rv4jEqfzDrT89y6ZPkCi0AJGnwp2En528yXo401Hpw==} + '@img/sharp-linux-riscv64@0.35.3': + resolution: {integrity: sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==} engines: {node: '>=20.9.0'} cpu: [riscv64] os: [linux] @@ -7694,8 +7779,8 @@ packages: cpu: [s390x] os: [linux] - '@img/sharp-linux-s390x@0.35.0': - resolution: {integrity: sha512-MYlMiPFiv/EKPAHnp3yNZ9AAWFsxga9c5Bkc6wkar6bqzHLlkGVJHRm0u1ei+VXnZxp3Mz9MG9ZIsI8vSOf3sQ==} + '@img/sharp-linux-s390x@0.35.3': + resolution: {integrity: sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==} engines: {node: '>=20.9.0'} cpu: [s390x] os: [linux] @@ -7706,8 +7791,8 @@ packages: cpu: [x64] os: [linux] - '@img/sharp-linux-x64@0.35.0': - resolution: {integrity: sha512-TYaItB5oj1ioXjhyn2xrR208vf+YuIIcHptQWRRaBmFhvIvL9D72DXN8w75xup0KXA8UdEAhQ9Qb2S49FD/9Cw==} + '@img/sharp-linux-x64@0.35.3': + resolution: {integrity: sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] @@ -7718,8 +7803,8 @@ packages: cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.35.0': - resolution: {integrity: sha512-DSTb6ijQzqe6DdAaOBVqJ/SYf1vO8EW5bK6X6LRXufEBebf2722VCdvBUtZ3rtV0x2ApfPNDy/p7LrrjaWjiyQ==} + '@img/sharp-linuxmusl-arm64@0.35.3': + resolution: {integrity: sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] @@ -7730,8 +7815,8 @@ packages: cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-x64@0.35.0': - resolution: {integrity: sha512-K7ykQ+26Rt6+4BTU80AuGgTPIYX86UxiAKT4rcXX/WNTo7k1ZxpKz+TguHnwVpCqQK3B5PK0vZ0ZBe6nz/ib1w==} + '@img/sharp-linuxmusl-x64@0.35.3': + resolution: {integrity: sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] @@ -7741,12 +7826,12 @@ packages: engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-wasm32@0.35.0': - resolution: {integrity: sha512-9woLIFORERCr+6cWu87dQ22J34EExkhc73U1kZW0c+RclQqWetoodByp4dWZ/hN8/KVmTRAx2HOnUwib8AwZdA==} + '@img/sharp-wasm32@0.35.3': + resolution: {integrity: sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==} engines: {node: '>=20.9.0'} - '@img/sharp-webcontainers-wasm32@0.35.0': - resolution: {integrity: sha512-t+kie1TOyaDM6Dho+f+y0VqIUNhYQaKCUahuZVi0E0frgdiaOaPsDxDW3wfKacUdaNBCnK/ZDBMg33ydvHj8uA==} + '@img/sharp-webcontainers-wasm32@0.35.3': + resolution: {integrity: sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==} engines: {node: '>=20.9.0'} cpu: [wasm32] @@ -7756,8 +7841,8 @@ packages: cpu: [arm64] os: [win32] - '@img/sharp-win32-arm64@0.35.0': - resolution: {integrity: sha512-M5eKxug0dabbaWgFKvPa3odNs2OpaP+81NASfGKkt4GcYXpNhSu7CaeYxWkLNV6vHmUp4hnCxnxrUyhUJhXbKA==} + '@img/sharp-win32-arm64@0.35.3': + resolution: {integrity: sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [win32] @@ -7768,8 +7853,8 @@ packages: cpu: [ia32] os: [win32] - '@img/sharp-win32-ia32@0.35.0': - resolution: {integrity: sha512-z0+pZ03QCDvdVN0Ez9IX/yjWC19ikMlXrmdYMwYNLTh2BLPx3hXWPvyqWfquZ0BTO9O6GVOjIVoTcyyacMnWlQ==} + '@img/sharp-win32-ia32@0.35.3': + resolution: {integrity: sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==} engines: {node: ^20.9.0} cpu: [ia32] os: [win32] @@ -7780,8 +7865,8 @@ packages: cpu: [x64] os: [win32] - '@img/sharp-win32-x64@0.35.0': - resolution: {integrity: sha512-feNnlz5ZHKr0MY1LPHvZQyJeBkbo4ctsn0D8FvA53VTw5TC63rfEL2UrWbkSBR19htSE7Mw78xYVwdJqoMWVHw==} + '@img/sharp-win32-x64@0.35.3': + resolution: {integrity: sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==} engines: {node: '>=20.9.0'} cpu: [x64] os: [win32] @@ -8154,6 +8239,172 @@ packages: '@loadable/component': ^5.0.1 react: ^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@lynx-js/cache-events-webpack-plugin@0.2.0': + resolution: {integrity: sha512-f903x00MMmWB9yIyvACAoe4im0T58JHpKnypV/ccI2gp477RJoCgXPne2x8Obgo3fTOIMHpmNMdnxFW1ej9olQ==} + engines: {node: '>=18'} + + '@lynx-js/chunk-loading-webpack-plugin@0.4.0': + resolution: {integrity: sha512-ud56B1ZzX+ejthk8v7MjpTAOQpr7jC9ea//tgW3hrx36jGzZAmI9EIPoCqkLvUIoFqMXr2lsanh0WYMZ8LUBnw==} + engines: {node: '>=18'} + + '@lynx-js/chunk-loading-webpack-plugin@0.4.1': + resolution: {integrity: sha512-vZb2HiZdkVdexs2OhRVZE3FkSUPYRvdnaAKG8y47WW2NlVFj6PHzqEhkqfO1L3dm5CIEsPgOMwzgSs3BHZZ8ig==} + engines: {node: '>=18'} + + '@lynx-js/css-extract-webpack-plugin@0.9.0': + resolution: {integrity: sha512-bWNaFeCBCa091BTZuFuAxQaqr/u/bE6xAiY5T6O8FBr5mIglBbLePQOftp/vsB92D30INjp8EwqB184PC+IXzg==} + version: 0.9.0 + engines: {node: '>=18'} + peerDependencies: + '@lynx-js/template-webpack-plugin': ^0.13.0 + + '@lynx-js/css-serializer@0.1.6': + resolution: {integrity: sha512-AgYrhsNljp+xBqO2UUGFTfHR+zPBiH9XE8eD13PDkcTDXWA9uKE/cZ6glZUE3Ze0lUDEtp0cSPCOVlHTMEyKIg==} + + '@lynx-js/debug-metadata-rsbuild-plugin@0.2.0': + resolution: {integrity: sha512-Lz/O04rpevr44sVd38Ue4YauUWgA+QwLB3RDL9V873zi1SrTq/fquD/9zIZOtY/+8qKtkdsVQPUBSsW3Vr5QwQ==} + engines: {node: '>=18'} + + '@lynx-js/debug-metadata@0.1.0': + resolution: {integrity: sha512-3N+Wgc/kIc4/aG5KpdPWKsC9MsNngygeyg9JS6IOayS8AQJ37xjVEzlz1U3aYzT9al0RJKL7w4poa0tGaXBv1g==} + engines: {node: '>=18'} + hasBin: true + + '@lynx-js/internal-preact@10.29.1-20260519060144-e6da44c': + resolution: {integrity: sha512-h2pRrt5oLK9LT1NkjyprtzJij/AdZGvv3CN6B5edL4chixqvITbxoCSdYy667Hl6+uKOBuxmCNZoDddgRfcLnw==} + + '@lynx-js/lynx-bundle-rslib-config@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/lynx-bundle-rslib-config@e22ca09': + resolution: {integrity: sha512-5fF4m6cLfU74iCmzBJ9/vN8ejKBae/tE3iSp8E4VFkBMMXBUGWVy7IzJqjDm9v1oyLb3VtkaKbFzP6a8Yg5Eeg==, tarball: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/lynx-bundle-rslib-config@e22ca09} + version: 0.6.0 + engines: {node: ^20.19.0 || >=22.12.0} + + '@lynx-js/qrcode-rsbuild-plugin@0.6.0': + resolution: {integrity: sha512-AjRhjQH5xEJKo5VUIIsjdk6zc6qXMf4QsGvKTDuwBjuGr01sDkLLpAt+wbu9GCJvDlGA7ygNTEsxaPJPYHPmIw==} + version: 0.6.0 + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@lynx-js/rspeedy': ^0.16.0 + + '@lynx-js/react-alias-rsbuild-plugin@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react-alias-rsbuild-plugin@e22ca09': + resolution: {integrity: sha512-MgJfdq5u5/Vh7g0CcmIutjzuUFyhg8uDBQFtYQkh1yNjy8P0RcyTAx/MLDb6keitlx+qn57lS0UVMvPwRv4CSw==, tarball: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react-alias-rsbuild-plugin@e22ca09} + version: 0.18.0 + engines: {node: '>=18'} + + '@lynx-js/react-refresh-webpack-plugin@0.4.1': + resolution: {integrity: sha512-L03dbY40PTR/+O4M5Orshn5q+59CdAqVPJOD9mKK4CMg0Vt+2jAOjow29x9tJZ8rjWQTJY2JdhOjIDBYpGXD5w==} + version: 0.4.1 + engines: {node: '>=18'} + peerDependencies: + '@lynx-js/react-webpack-plugin': ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 + + '@lynx-js/react-rsbuild-plugin@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react-rsbuild-plugin@e22ca09': + resolution: {integrity: sha512-z/BddxZZoCD8bWk4FiXOiVq9XxrvQopni5VD+0qRd9+WN+lPgV+QO0y/FyWNOwoBx2P3tMHgJo9lsdDjGmFG0g==, tarball: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react-rsbuild-plugin@e22ca09} + version: 0.18.0 + engines: {node: '>=18'} + peerDependencies: + '@lynx-js/react': ^0.123.0 + peerDependenciesMeta: + '@lynx-js/react': + optional: true + + '@lynx-js/react-webpack-plugin@0.10.0': + resolution: {integrity: sha512-gyGsqbMyCoIfL94/vcc5OOSGNP7OJyVzfaOywmMqWX/1reUa8USawtEKMHJzvGb7AJ4UrAaW9NYFQNyIdW7kIg==} + version: 0.10.0 + engines: {node: '>=18'} + peerDependencies: + '@lynx-js/react': '*' + '@lynx-js/template-webpack-plugin': ^0.13.0 + peerDependenciesMeta: + '@lynx-js/react': + optional: true + + '@lynx-js/react@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09': + resolution: {integrity: sha512-nTEizIt7cyec+8sZypu6uF7+Uv9HE7QJd4551tgp08tgmP5ApbQMUxzV/CX6ckgq4mQR8dZQDWfzi5KA5sw49A==, tarball: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09} + version: 0.123.0 + peerDependencies: + '@lynx-js/types': '*' + '@types/react': ^18 + peerDependenciesMeta: + '@lynx-js/types': + optional: true + + '@lynx-js/rspeedy@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/rspeedy@e22ca09': + resolution: {integrity: sha512-U35ChEo6R9X2gi1xHB08YSSmtDTjYTAiPDlxtyySSUd1aocCSlkMAuNZvzSx1/tCtJqJNzdd3baDG2YtoMf4PA==, tarball: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/rspeedy@e22ca09} + version: 0.16.0 + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + typescript: 5.1.6 - 6.0.x + peerDependenciesMeta: + typescript: + optional: true + + '@lynx-js/runtime-wrapper-webpack-plugin@0.2.2': + resolution: {integrity: sha512-q/T89kpxkZIQgSk2yhTXFYlH1XqE2UzPr/vKhWOk4PBZ8v9gyUco80xCvn4YyA6iSYrd7bO65MBK73gkqIlsng==} + engines: {node: '>=18'} + + '@lynx-js/tasm@0.0.39': + resolution: {integrity: sha512-FNIV6Cc2K0wCKOHVMfpr3M6kpIZqHHNI02GpVl+h0ClyyK44jxEO+lf58gLFD5E3o0hJ1cp3H2OBIxSUJGkPDw==} + hasBin: true + + '@lynx-js/template-webpack-plugin@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09': + resolution: {integrity: sha512-hFnAnbKtHQqFRAdoc7C52SsSLOvhzjFVnZF+9GvoJuffBBg/fmAkw2gl+cAXTU6p0OtduEwNrb25ek5s0COElQ==, tarball: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09} + version: 0.13.0 + engines: {node: ^18.14 || >=19.4} + + '@lynx-js/types@4.0.0': + resolution: {integrity: sha512-FBtBV8wt+9/CAUbTdZwvbhGjJEnyzKLWVpq5gaTHTbhDztbhdP0avZ0nHD6I5HSgAhzYNB9oDrqMt/r6YqBkng==} + + '@lynx-js/use-sync-external-store@1.5.0': + resolution: {integrity: sha512-iXwLiGUBgfWLozCIh3ICe07x534p9CVlFS3/iGEiFOce58MLX6/PbAvWcE8qEhYaOKiQNe9hqUnS2RbyCqoqGg==} + version: 1.5.0 + peerDependencies: + '@lynx-js/react': '*' + + '@lynx-js/web-core@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-core@e22ca09': + resolution: {integrity: sha512-e/1o5piSAfgzj08E/MwYvUayb9Ngmdbj/Myc6wq0j9fpWEW27nxdW78gpSM+icfxaaBPWlnZK2DzNhjEQyvwGQ==, tarball: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-core@e22ca09} + version: 0.22.2 + peerDependencies: + '@lynx-js/css-serializer': 0.1.6 + '@lynx-js/lynx-core': 0.1.4 + tslib: ^2.5.0 + peerDependenciesMeta: + '@lynx-js/css-serializer': + optional: true + '@lynx-js/lynx-core': + optional: true + tslib: + optional: true + + '@lynx-js/web-elements@0.12.6': + resolution: {integrity: sha512-JcavrTn9SeapEEGS9uqsCrFZK1hJDgeblT6kewG+LijWu5r4YkZw7yGCvInLH9qhFAablJQ+EZHpwa91GAT2dw==} + peerDependencies: + tslib: ^2.5.0 + + '@lynx-js/web-rsbuild-server-middleware@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-rsbuild-server-middleware@e22ca09': + resolution: {integrity: sha512-n87lhgt4/so/3jxlBGlzVUJhkiaD6A57a+TnCcHQAuqoK9N9YLUj/WhlYXDoNGKmYd02FpG7CXVitNgTrBynhA==, tarball: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-rsbuild-server-middleware@e22ca09} + version: 0.22.2 + + '@lynx-js/web-worker-rpc@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-worker-rpc@e22ca09': + resolution: {integrity: sha512-YQmh+RarVbpfrNTJT2NN1VamFFLKvoVmXCqO1STsMIdubO2MS9T1eBAbXTxkEWe6SSdYKFMcdJZIzkd0bEoWCA==, tarball: https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-worker-rpc@e22ca09} + version: 0.22.2 + + '@lynx-js/webpack-dev-transport@0.3.0': + resolution: {integrity: sha512-vpa0X/eqb50DhNhXEEfCi6gEd/ti32bFJtkt9zkPv6dTTsd2HJkYnCiKSUWpHpqPbnRbVLf/UacAY6h1uTtZeA==} + engines: {node: '>=18'} + + '@lynx-js/webpack-runtime-globals@0.0.6': + resolution: {integrity: sha512-VzpJc/w7v38/SHaZ+f3WBVBrsgXOG13YX9mRRxRrCgJQa+wZ6waRz7OIjntuTYJk2p7rQ0wKRGzsMgMfRBs/3g==} + engines: {node: '>=18'} + + '@lynx-js/webpack-runtime-globals@0.0.7': + resolution: {integrity: sha512-HGWCIX8FMeoeCfUZpijrZO20UVp8wVHj7f+aEiMbx94H2iqIVxLzrCLZudBfvo4+WZ3kdgM8CqhU1EERKhLi0w==} + engines: {node: '>=18'} + + '@lynx-js/websocket@0.0.4': + resolution: {integrity: sha512-yXuMiTALLNvkDz8hG+0KdkBodnyJDvyfr8bdIq6zMP9X8JAoBC/czc1HIhUgYEJ3Zee5F/vGKjNxcoSx2t1E6w==} + engines: {node: '>=18'} + '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -11761,251 +12012,126 @@ packages: cpu: [arm] os: [android] - '@rollup/rollup-android-arm-eabi@4.62.2': - resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} - cpu: [arm] - os: [android] - '@rollup/rollup-android-arm64@4.59.0': resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} cpu: [arm64] os: [android] - '@rollup/rollup-android-arm64@4.62.2': - resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} - cpu: [arm64] - os: [android] - '@rollup/rollup-darwin-arm64@4.59.0': resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-arm64@4.62.2': - resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} - cpu: [arm64] - os: [darwin] - '@rollup/rollup-darwin-x64@4.59.0': resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} cpu: [x64] os: [darwin] - '@rollup/rollup-darwin-x64@4.62.2': - resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} - cpu: [x64] - os: [darwin] - '@rollup/rollup-freebsd-arm64@4.59.0': resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-arm64@4.62.2': - resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} - cpu: [arm64] - os: [freebsd] - '@rollup/rollup-freebsd-x64@4.59.0': resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.62.2': - resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} - cpu: [x64] - os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-gnueabihf@4.62.2': - resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} - cpu: [arm] - os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.59.0': resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.62.2': - resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} - cpu: [arm] - os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.59.0': resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.62.2': - resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} - cpu: [arm64] - os: [linux] - '@rollup/rollup-linux-arm64-musl@4.59.0': resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.62.2': - resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} - cpu: [arm64] - os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.59.0': resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.62.2': - resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} - cpu: [loong64] - os: [linux] - '@rollup/rollup-linux-loong64-musl@4.59.0': resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loong64-musl@4.62.2': - resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} - cpu: [loong64] - os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.59.0': resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.62.2': - resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} - cpu: [ppc64] - os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.59.0': resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.62.2': - resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} - cpu: [ppc64] - os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.59.0': resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.62.2': - resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} - cpu: [riscv64] - os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.59.0': resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.62.2': - resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} - cpu: [riscv64] - os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.59.0': resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.62.2': - resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} - cpu: [s390x] - os: [linux] - '@rollup/rollup-linux-x64-gnu@4.59.0': resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.62.2': - resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} - cpu: [x64] - os: [linux] - '@rollup/rollup-linux-x64-musl@4.59.0': resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.62.2': - resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} - cpu: [x64] - os: [linux] - '@rollup/rollup-openbsd-x64@4.59.0': resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openbsd-x64@4.62.2': - resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} - cpu: [x64] - os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.59.0': resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-openharmony-arm64@4.62.2': - resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} - cpu: [arm64] - os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.59.0': resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.62.2': - resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} - cpu: [arm64] - os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.59.0': resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.62.2': - resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} - cpu: [ia32] - os: [win32] - '@rollup/rollup-win32-x64-gnu@4.59.0': resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.62.2': - resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} - cpu: [x64] - os: [win32] - '@rollup/rollup-win32-x64-msvc@4.59.0': resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.62.2': - resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} - cpu: [x64] - os: [win32] - '@rsbuild/core@1.0.1-rc.4': resolution: {integrity: sha512-JlouV5M+azv9YP6hD11rHeUns8Yk9sQN9QmMCKhutG75j1TCEKmrL0O7UmE89+uKlJTnL5Pyzy29TLO5ncIRjg==} engines: {node: '>=16.7.0'} @@ -12157,6 +12283,14 @@ packages: '@rsbuild/core': optional: true + '@rsbuild/plugin-css-minimizer@2.0.0': + resolution: {integrity: sha512-gSBkvOJP18JiaUuCyu4dPlEWY5aNTCJD3etsWqht9nO1l5cOBdLtq0vSu1SlLmhVS2b6fXlSs9+thMoHvSXwqw==} + peerDependencies: + '@rsbuild/core': ^1.0.0 || ^2.0.0-0 + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@rsbuild/plugin-css-minimizer@2.0.1': resolution: {integrity: sha512-QZUEBLTYXwtCUVziK3XHaPv+3wLLGvO6X27sIwt12eyuIMSA5WbeuLD9y1hwsXu27jZBofP6s/mdV5H5bA/FOg==} peerDependencies: @@ -12380,10 +12514,43 @@ packages: peerDependencies: '@rsbuild/core': ^1.3.21 + '@rsdoctor/client@1.5.18': + resolution: {integrity: sha512-W3Hpfe8Z1SV/umj7YRnfgTMfddEqYzxejBtwfcPshLjes79+Ggh0IvdH//X1FDs5pjHmydeVlK91dqF0deqqNg==} + + '@rsdoctor/core@1.5.18': + resolution: {integrity: sha512-MBryu0+E/DPuaUV6HsRV25NUTQTXnrYCKKdVrkJgJYnAYjz3wl6iaSA+KfdnUJRxx61BKfFggy0Qwuj/XoLhoQ==} + + '@rsdoctor/graph@1.5.18': + resolution: {integrity: sha512-aYfiMkN4s5QkxTKXlFdaVyeLdoOHkMjTMQYRg5UcyOvPb0YCWyWYm2PyO7gnCoKxrpd9LsCse/kAU9WPFLvbUw==} + + '@rsdoctor/rspack-plugin@1.5.18': + resolution: {integrity: sha512-oB0kKiFS0bs48Z/t7FhRVvv/cOTWM8eQegVKO8dfprf3gS+kTo+yT9Qfe4CRNYd9xiLO/Y10GMOzFa2G5/LX3A==} + peerDependencies: + '@rspack/core': '*' + peerDependenciesMeta: + '@rspack/core': + optional: true + + '@rsdoctor/sdk@1.5.18': + resolution: {integrity: sha512-Kg9YWSV3P0jzQutqd3dIXkHmDHvDUiymDJEq1lvXtnfzBRT0vdrgrz39fvuw9UoS9nU3zGoAEYCj/0NqBsnWOw==} + + '@rsdoctor/types@1.5.18': + resolution: {integrity: sha512-0US5SIlZiRl6BvsO8AySAjpprQ1cyUMpT+o0vsrWdrB+fjtApKmis74NV2utb34EMzFVfV3DFRUu8pNSib3osA==} + peerDependencies: + '@rspack/core': '*' + webpack: 5.x + peerDependenciesMeta: + '@rspack/core': + optional: true + webpack: + optional: true + + '@rsdoctor/utils@1.5.18': + resolution: {integrity: sha512-+4FUMtxzoDGi4JEuocelxrrTA1s/DysHCxC76V4C9+OaCOYgcoEt8FuRJ6KN8+gU07XCB4OmYNfJu6W1IpW3hA==} + '@rslib/core@0.12.4': resolution: {integrity: sha512-GF+TIacQgtfvKK5r5g08IO795DnorpiHqcERsBua38iB0KsePpOGPAO+/E5YJncZ5regc72y1CByIELEeikgQA==} engines: {node: '>=18.12.0'} - deprecated: deprecated due to bug, please consider using 0.12.2 or >= 0.13.2 hasBin: true peerDependencies: '@microsoft/api-extractor': ^7 @@ -12420,6 +12587,80 @@ packages: typescript: optional: true + '@rspack-canary/binding-darwin-arm64@2.1.5-canary-54a0d8f3-20260715194831': + resolution: {integrity: sha512-RJFGBPuNFfv5QnsMw9DDxZzyvFRRfB0D062CrV/jr8fZRtMokd2Fv5fDBP8Z7YS7N7FY95Nv9RTlGNPpAxUP6A==} + cpu: [arm64] + os: [darwin] + + '@rspack-canary/binding-darwin-x64@2.1.5-canary-54a0d8f3-20260715194831': + resolution: {integrity: sha512-77hL1FfeEitPJBO4R//DZm6TonQ+GHKmQGUt7ZSXGRwkI9FtutH53mtooRFyTs+FO7GqWMgVqt2k9n5tmyr2wQ==} + cpu: [x64] + os: [darwin] + + '@rspack-canary/binding-linux-arm64-gnu@2.1.5-canary-54a0d8f3-20260715194831': + resolution: {integrity: sha512-DJA3ZnYdJNK+X3WJnHXdBvA/g8HTeznUplLWtLWRy5DLu1msW/Iswkts/1BPYBzQ6kbpUhB13i7RGW5OQV3GHg==} + cpu: [arm64] + os: [linux] + + '@rspack-canary/binding-linux-arm64-musl@2.1.5-canary-54a0d8f3-20260715194831': + resolution: {integrity: sha512-ZLsDbohCGoA5p77yEp0SJARvmB/FJdbv0/U3ZQRVYxJl5MUHQeEoNMxHaKVZdTUpELCJ030JyOspAnkD2sbOpw==} + cpu: [arm64] + os: [linux] + + '@rspack-canary/binding-linux-riscv64-gnu@2.1.5-canary-54a0d8f3-20260715194831': + resolution: {integrity: sha512-R57VctExS703p9EwG5RF//f60ZQ+obAYGPbmOxRtG/zn3Rqs++L0iqmdJVxUZLl1IaH1+0aMZJ45gjpuFw4aUw==} + cpu: [riscv64] + os: [linux] + + '@rspack-canary/binding-linux-riscv64-musl@2.1.5-canary-54a0d8f3-20260715194831': + resolution: {integrity: sha512-x3zuNssCW6wELSDx5q4vlMy6NAypPmu1y7Oma6OIQ1T/XuY78IvkmN9fQ+mFkb9kfjeMkehqkbidWnBfYNDMyw==} + cpu: [riscv64] + os: [linux] + + '@rspack-canary/binding-linux-x64-gnu@2.1.5-canary-54a0d8f3-20260715194831': + resolution: {integrity: sha512-IqEEG8AweqxTWj6dTh1+M8AeHLTB8iGXXPHoyyiB5dbFX4X1md/oMlRfZmINEg9QAxlFDGzdQ074l7JLa2mMbg==} + cpu: [x64] + os: [linux] + + '@rspack-canary/binding-linux-x64-musl@2.1.5-canary-54a0d8f3-20260715194831': + resolution: {integrity: sha512-ScZaPuu/VxAidHcxLjRr+D0CH7jR8phfv2c277VLnwXqpppvTL9tmA2UVarsBqLRbRuRuoxQb3YfVxvzsczKYg==} + cpu: [x64] + os: [linux] + + '@rspack-canary/binding-wasm32-wasi@2.1.5-canary-54a0d8f3-20260715194831': + resolution: {integrity: sha512-iNkFwwuaWtJi3mkPgY89azxM3CHwcXq/IfQjZpbMEoGNvIRyFIMjzZWoP7xlD6yDjf6EW3ouvqWi7H+DT0/Iwg==} + cpu: [wasm32] + + '@rspack-canary/binding-win32-arm64-msvc@2.1.5-canary-54a0d8f3-20260715194831': + resolution: {integrity: sha512-QDiWagMTDXDGzrl/JiUCDOjUKQJBs7oQ77bsUoaPn6sgNoDcudOhOjS+LxyLT6h1RfJTMUJKeXKhHtLgB2pyRA==} + cpu: [arm64] + os: [win32] + + '@rspack-canary/binding-win32-ia32-msvc@2.1.5-canary-54a0d8f3-20260715194831': + resolution: {integrity: sha512-QAcyX3p5RT3Vo+cN533AHHoqTWMJjCZxsCZg6NH23QuRoW583Vof4YrWSyYH6cbwP5f4PaEeiNIe9RsyG5Qykg==} + cpu: [ia32] + os: [win32] + + '@rspack-canary/binding-win32-x64-msvc@2.1.5-canary-54a0d8f3-20260715194831': + resolution: {integrity: sha512-b2pZdy/H+muI28zoP6OMpdtcnH18nMenXsC//KZD7uYBqebwxIsfFvONv43v0D01j0BthcPn7ap763NDrIDBfQ==} + cpu: [x64] + os: [win32] + + '@rspack-canary/binding@2.1.5-canary-54a0d8f3-20260715194831': + resolution: {integrity: sha512-qPwDAwoaXWehRYGSidS6Fgcn0oZj6sUWqKPWEkc5Ff8dQtib35iMWtyy5wcTTAp+yn1dW7nFszDWVyJNIXYBeA==} + + '@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831': + resolution: {integrity: sha512-p1aHWGtzMvcl8UyaYQUEOJWZ+N/iAlk0U62q8aZV1AabseLIvEuHFL85/JbfAkLtKzkhc6ZeeVokLBFrDj738g==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@module-federation/runtime-tools': ^0.24.1 || ^2.0.0 + '@swc/helpers': ^0.5.23 + peerDependenciesMeta: + '@module-federation/runtime-tools': + optional: true + '@swc/helpers': + optional: true + '@rspack/binding-darwin-arm64@0.7.5': resolution: {integrity: sha512-mNBIm36s1BA7v4SL/r4f3IXIsjyH5CZX4eXMRPE52lBc3ClVuUB7d/8zk8dkyjJCMAj8PsZSnAJ3cfXnn7TN4g==} cpu: [arm64] @@ -13346,6 +13587,59 @@ packages: '@rspack/core': optional: true + '@rspack/resolver-binding-darwin-arm64@0.2.8': + resolution: {integrity: sha512-nTnK17kmxXEvR+WpOIZPSIzUFYeWCHoffgU9tvOLOwuTBH41kWnSQXXWu+AiMVwvJ6wdRO6Vo30hPhlXEG7Pyw==} + cpu: [arm64] + os: [darwin] + + '@rspack/resolver-binding-darwin-x64@0.2.8': + resolution: {integrity: sha512-Aqr4TK2rA6XVYUOmM5YCtYyCMZhOIR53P4cOGgGARg99A7OuMBMzUL4r1n0M0Fx35v6/sSx1OBe+odHmPxksEg==} + cpu: [x64] + os: [darwin] + + '@rspack/resolver-binding-linux-arm64-gnu@0.2.8': + resolution: {integrity: sha512-wGvkxm2G4mNTztslaOzLzx5JuySQSy5DcOWEZxHcjJJzp5L3ODbYLK18HtUc6cvmaVOmjaGrrYPrqJJ0hHTVFg==} + cpu: [arm64] + os: [linux] + + '@rspack/resolver-binding-linux-arm64-musl@0.2.8': + resolution: {integrity: sha512-EqRJ9zLQsLAvyDKJKVZ45BSqRIMS12f5HtJdy3KkAHU14ZmsGv8e5IKkwUZN5CNBRad8xVlOMMx3dOfF4whJzg==} + cpu: [arm64] + os: [linux] + + '@rspack/resolver-binding-linux-x64-gnu@0.2.8': + resolution: {integrity: sha512-eXbeotNCTntL4/+mxJRVCxK63YeWzTfp0F3POeHJFSs6Nt0f2J/mZNFlasJmd6xm7zvE80h/HWOwbwjRBLcElA==} + cpu: [x64] + os: [linux] + + '@rspack/resolver-binding-linux-x64-musl@0.2.8': + resolution: {integrity: sha512-KWFHlOWGkT+eMngoUgPGXrDi+rU04VCh9jyk0U6Ot2RTWvhGxwKykjmLS+CWZI/EBrzr9A6g2U3jzKTMNz9oCw==} + cpu: [x64] + os: [linux] + + '@rspack/resolver-binding-wasm32-wasi@0.2.8': + resolution: {integrity: sha512-I6GIhgICFViE88jejIV74oiiWHnpLpQ5ogaZM1ozM9KDnfqcHoX0IVEyrIh5KqA8iLDyhuoFSW+Hf0qN7VTBBQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rspack/resolver-binding-win32-arm64-msvc@0.2.8': + resolution: {integrity: sha512-ZXCt3qUfDAEbtc2sHpvxM7lNFZM+DxfblgXUIl3Jy6BuEZbHe1i6z+t9c34ayHoGTVbVSNCtaYuG/MaWdSnPHw==} + cpu: [arm64] + os: [win32] + + '@rspack/resolver-binding-win32-ia32-msvc@0.2.8': + resolution: {integrity: sha512-2LRymjDK8MpUERD8CL0PPae5y2crU5TAg4T4EzpeL5jLARVq6izsEruiWzB6Y+D15vUYlvmgs2370GXVSB861w==} + cpu: [ia32] + os: [win32] + + '@rspack/resolver-binding-win32-x64-msvc@0.2.8': + resolution: {integrity: sha512-hzRpfbtvv4M4EVrKKIAaHDs5wT8lVcbSUjtwPs5u4IeLEix45nQPQ6ZQjmE4lIH0GP/3L3XQhZroYmTcH/xdsQ==} + cpu: [x64] + os: [win32] + + '@rspack/resolver@0.2.8': + resolution: {integrity: sha512-FBWqdHhzS8mcf/WN4Ktzr7EaeaN+hsxbN98EweegX3924beZuY6H70CSFWCv1fIHAieCUv/9XCjKggHvhCsLwA==} + '@rspress/core@2.0.14': resolution: {integrity: sha512-k59i08zwBGgHrjHw8CK1m4CeTrKPvZRmV54bxubQl6AdDdmhJK6WrNg3UthwWmd38scKtqF40ATXDE8RMiNcNA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13533,6 +13827,9 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@socket.io/component-emitter@3.1.2': + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@storybook/addon-docs@8.6.18': resolution: {integrity: sha512-55ADer0yNmmeR928Y3UAv3r4i7bJSd9LwywsQ+lRol/FNe0ZcwLEz31xL+jVsqQFNnDh/imsDIp8aYapGMtfEQ==} peerDependencies: @@ -14400,6 +14697,9 @@ packages: '@types/cookie@0.4.1': resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} + '@types/cors@2.8.19': + resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + '@types/cross-spawn@6.0.6': resolution: {integrity: sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==} @@ -14544,18 +14844,15 @@ packages: '@types/estree@0.0.51': resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - '@types/express-serve-static-core@4.19.8': resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} - '@types/express-serve-static-core@4.19.9': - resolution: {integrity: sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg==} - '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} @@ -14708,8 +15005,8 @@ packages: '@types/node@22.19.15': resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==} - '@types/node@26.2.0': - resolution: {integrity: sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==} + '@types/node@26.1.0': + resolution: {integrity: sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -14833,6 +15130,9 @@ packages: '@types/stylis@4.2.0': resolution: {integrity: sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==} + '@types/tapable@2.3.0': + resolution: {integrity: sha512-oMnbAXeVo+KUnje3hzdORXUbfnzTfqD0H92mLl19NE5hFqH9Q4ktq+xehNSxcNeeLm1COopYwa0zeP6Iz+oIXg==} + '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} @@ -15250,7 +15550,6 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@unhead/react@2.1.15': resolution: {integrity: sha512-5hfAaZ3XJq9JkspRzZdSPsMrXXA8v/SKiEOxZcN9L40o44byF/50bcQuOLgSSCAx8802mI5VG32KZXWTtsLu9Q==} @@ -15727,7 +16026,6 @@ packages: '@xmldom/xmldom@0.8.11': resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} engines: {node: '>=10.0.0'} - deprecated: this version has critical issues, please update to the latest version '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -15782,7 +16080,6 @@ packages: abab@2.0.6: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} - deprecated: Use your platform's native atob() and btoa() methods instead abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} @@ -15849,11 +16146,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - acorn@8.18.0: - resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} - engines: {node: '>=0.4.0'} - hasBin: true - address@1.2.2: resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} engines: {node: '>= 10.0.0'} @@ -15866,10 +16158,6 @@ packages: resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} engines: {node: '>=12.0'} - adm-zip@0.5.18: - resolution: {integrity: sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==} - engines: {node: '>=12.0'} - adm-zip@0.6.0: resolution: {integrity: sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==} engines: {node: '>=14.0'} @@ -16074,12 +16362,10 @@ packages: are-we-there-yet@2.0.0: resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} engines: {node: '>=10'} - deprecated: This package is no longer supported. are-we-there-yet@4.0.2: resolution: {integrity: sha512-ncSWAawFhKMJDTdoAeOV+jyW1VCMj5QIAwULIBV0SSR7B/RLPPEQiknKcg/RIIZlUQrxELpsxMiTUoAQ4sIUyg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - deprecated: This package is no longer supported. arg@4.1.0: resolution: {integrity: sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==} @@ -16486,6 +16772,9 @@ packages: resolution: {integrity: sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==} engines: {node: '>= 10.0.0'} + background-only@0.0.1: + resolution: {integrity: sha512-YXR2zshAf3qs3jnpApQaDUG0x4L6YWpSZfLDhdeiCFxfp/n8YwfoAQ1hAigEF3VpXOMOJeZYFWtBbiFv/v2Qfg==} + bail@1.0.5: resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==} @@ -16540,6 +16829,10 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + base64id@2.0.0: + resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} + engines: {node: ^4.5.0 || >= 5.9} + base@0.11.2: resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} engines: {node: '>=0.10.0'} @@ -16654,10 +16947,6 @@ packages: resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} engines: {node: 18 || 20 || >=22} - brace-expansion@5.0.9: - resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} - engines: {node: 20 || >=22} - braces@2.3.2: resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==} engines: {node: '>=0.10.0'} @@ -16695,6 +16984,9 @@ packages: browserify-zlib@0.2.0: resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==} + browserslist-load-config@1.0.3: + resolution: {integrity: sha512-boNaPS4KlW6AITZQ60G+1oDJLuxauljDd7QNQFOYpRtldzcTDknMZ8awbwI0BT/8h1/Y/CG4k/tDOLip9lAGcg==} + browserslist-to-es-version@1.4.1: resolution: {integrity: sha512-1bYCrck5Qh5HUy7P+iDuK39v757/ry5PnQo20vf4sHGeUrYKL2N2OF05U9ARSGt06TpFDQiTv9MT+eitYgWWxA==} hasBin: true @@ -17578,7 +17870,6 @@ packages: cron-parser@4.9.0: resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} engines: {node: '>=12.0.0'} - deprecated: v4 is no longer maintained, upgrade to v5 cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} @@ -17778,6 +18069,9 @@ packages: csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -18038,7 +18332,6 @@ packages: debug@4.1.1: resolution: {integrity: sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==} - deprecated: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797) peerDependencies: supports-color: '*' peerDependenciesMeta: @@ -18095,6 +18388,10 @@ packages: babel-plugin-macros: optional: true + deep-eql@4.1.4: + resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + engines: {node: '>=6'} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -18259,8 +18556,8 @@ packages: resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} engines: {node: '>=0.3.1'} - diff@8.0.4: - resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + diff@8.0.3: + resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} engines: {node: '>=0.3.1'} diffie-hellman@5.0.3: @@ -18340,7 +18637,6 @@ packages: domexception@4.0.0: resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} engines: {node: '>=12'} - deprecated: Use your platform's native DOMException instead domhandler@4.3.1: resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} @@ -18502,6 +18798,14 @@ packages: endent@2.1.0: resolution: {integrity: sha512-r8VyPX7XL8U01Xgnb1CjZ3XV+z90cXIJ9JPE/R9SEC9vpw2P6CfsRPJmp20DppC5N7ZAMCmjYkJIa744Iyg96w==} + engine.io-parser@5.2.3: + resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} + engines: {node: '>=10.0.0'} + + engine.io@6.6.9: + resolution: {integrity: sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==} + engines: {node: '>=10.2.0'} + enhanced-resolve@5.16.1: resolution: {integrity: sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==} engines: {node: '>=10.13.0'} @@ -19093,7 +19397,6 @@ packages: eslint@8.57.1: resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true eslint@9.26.0: @@ -19477,6 +19780,10 @@ packages: resolution: {integrity: sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==} engines: {node: '>= 10.4.0'} + filesize@11.0.22: + resolution: {integrity: sha512-RlCVs9CY+oSsRnNZn95J9vDXjNjOwddKyTFjOYtA4yxYVIxBnwiVVGJX+TFhsmu3uUf81JDGyijtYL9xgawlTw==} + engines: {node: '>= 10.8.0'} + fill-range@4.0.0: resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==} engines: {node: '>=0.10.0'} @@ -19591,6 +19898,15 @@ packages: resolution: {integrity: sha512-Ik/6OCk9RQQ0T5Xw+hKNLWrjSMtv51dD4GRmJjbD5a58TIEpI5a5iXagKVl3Z5UuyslMCA8Xwnu76jQob62Yhg==} engines: {node: '>=10'} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + follow-redirects@1.16.0: resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} engines: {node: '>=4.0'} @@ -19641,8 +19957,8 @@ packages: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} engines: {node: '>= 14.17'} - form-data@2.5.6: - resolution: {integrity: sha512-Ogz/E85h9tlfJzpI6TuFpGcHZFhLrb9Gw8wq9v40CxSCPnv7ahKr6Xgtkn0KYCDQJ8DNn5VoMO8EXr9V5PadyA==} + form-data@2.5.5: + resolution: {integrity: sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==} engines: {node: '>= 0.12'} form-data@4.0.5: @@ -19729,10 +20045,6 @@ packages: resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} engines: {node: '>=14.14'} - fs-extra@11.3.6: - resolution: {integrity: sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==} - engines: {node: '>=14.14'} - fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -19796,12 +20108,10 @@ packages: gauge@3.0.2: resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} engines: {node: '>=10'} - deprecated: This package is no longer supported. gauge@5.0.2: resolution: {integrity: sha512-pMaFftXPtiGIHCJHdcUUx9Rby/rFT/Kkt3fIIGCs+9PMDIljSyRiqraTlxNtBReJRDfUefpa263RQ3vnp5G/LQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - deprecated: This package is no longer supported. generator-function@2.0.1: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} @@ -19826,10 +20136,6 @@ packages: resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} - get-east-asian-width@1.6.0: - resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} - engines: {node: '>=18'} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -19901,7 +20207,6 @@ packages: git-raw-commits@4.0.0: resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} engines: {node: '>=16'} - deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead. hasBin: true git-up@8.1.1: @@ -19935,13 +20240,11 @@ packages: glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@11.1.0: resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} engines: {node: 20 || >=22} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@13.0.6: @@ -19950,25 +20253,20 @@ packages: glob@7.1.6: resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@7.2.0: resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@9.3.5: resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} engines: {node: '>=16 || 14 >=14.17'} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-directory@4.0.1: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} @@ -20243,10 +20541,6 @@ packages: resolution: {integrity: sha512-5IAMJOXfpA5nT+K0MNjClchzz0IhBHs2Szl7WFAhrFOsbtQsYmNynFyJRg/a3IPsmCfxcrf8txUGiNShXpK5Rg==} engines: {node: '>=16.0.0'} - hono@4.13.0: - resolution: {integrity: sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ==} - engines: {node: '>=16.9.0'} - hono@4.13.1: resolution: {integrity: sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==} engines: {node: '>=16.9.0'} @@ -20579,7 +20873,6 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.1: resolution: {integrity: sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==} @@ -20638,7 +20931,6 @@ packages: intersection-observer@0.12.2: resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==} - deprecated: The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019. invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} @@ -20731,10 +21023,6 @@ packages: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} - is-core-module@2.16.2: - resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} - engines: {node: '>= 0.4'} - is-data-descriptor@1.0.1: resolution: {integrity: sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==} engines: {node: '>= 0.4'} @@ -21358,8 +21646,8 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-yaml@3.15.1: - resolution: {integrity: sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==} + js-yaml@3.15.0: + resolution: {integrity: sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==} hasBin: true js-yaml@4.2.0: @@ -21434,6 +21722,9 @@ packages: resolution: {integrity: sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==} engines: {node: '>= 0.4'} + json-stream-stringify@3.0.1: + resolution: {integrity: sha512-vuxs3G1ocFDiAQ/SX0okcZbtqXwgj1g71qE9+vrjJ2EkjKQlEFDAcUNRxRU8O+GekV4v5cM2qXP0Wyt/EMDBiQ==} + json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} @@ -21465,9 +21756,6 @@ packages: jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - jsonfile@6.2.1: - resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} - jsonify@0.0.1: resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==} @@ -21541,6 +21829,9 @@ packages: launch-editor@2.13.1: resolution: {integrity: sha512-lPSddlAAluRKJ7/cjRFoXUFzaX7q/YKI7yPHuEvSJVqoXvFnJov1/Ud87Aa4zULIbA9Nja4mSPK8l0z/7eV2wA==} + launch-editor@2.14.1: + resolution: {integrity: sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==} + layout-base@1.0.2: resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} @@ -21625,6 +21916,10 @@ packages: resolution: {integrity: sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lines-and-columns@2.0.4: + resolution: {integrity: sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + linkify-it@5.0.0: resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} @@ -22528,8 +22823,8 @@ packages: mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - modern-tar@0.7.7: - resolution: {integrity: sha512-t9VmxaqrmANnEOBhpSDI6HD192Ge48k8vmWqQQL7hSFEqHEYwZbbsu49+aKLWZeRvFs3j1pMhXOqqF4kPlvjkQ==} + modern-tar@0.7.6: + resolution: {integrity: sha512-sweCIVXzx1aIGTCdzcMlSZt1h8k5Tmk08VNAuRk3IU28XamGiOH5ypi11g6De2CH7PhYqSSnGy2A/EFhbWnVKg==} engines: {node: '>=18.0.0'} moment@2.30.1: @@ -22609,6 +22904,11 @@ packages: resolution: {integrity: sha512-/pULofvsF8mOVcl/nUeVXL/GYOEvc7eJWSIxa+K4OYUolvXa5zwSgevsn4eoHs1xvh/BO3vx/PZiD9+Ow2ZVuw==} engines: {node: '>=18.19'} + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + nanoid@3.3.18: resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -22725,7 +23025,6 @@ packages: node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} - deprecated: Use your platform's native DOMException instead node-exports-info@1.6.0: resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} @@ -22865,12 +23164,10 @@ packages: npmlog@5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} - deprecated: This package is no longer supported. npmlog@7.0.1: resolution: {integrity: sha512-uJ0YFk/mCQpLBt+bxN88AKd+gyqZvZDbtiNxk6Waqcj2aPRyfVx8ITawkyQynxUagInjdYT1+qj4NfA5KJJUxg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - deprecated: This package is no longer supported. nprogress@0.2.0: resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} @@ -23292,7 +23589,6 @@ packages: path-match@1.2.4: resolution: {integrity: sha512-UWlehEdqu36jmh4h5CWJ7tARp1OEVKGHKm6+dg9qMq5RKUTV5WJrGgaZ3dN2m7WFAXDbjlHzvJvL/IUpy84Ktw==} - deprecated: This package is archived and no longer maintained. For support, visit https://github.com/expressjs/express/discussions path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -23377,10 +23673,6 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - picomatch@4.0.5: - resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} - engines: {node: '>=12'} - pidtree@0.6.0: resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} engines: {node: '>=0.10'} @@ -23962,18 +24254,10 @@ packages: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.23: - resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.24: resolution: {integrity: sha512-8RyVklq0owXUTa4xlpzu4l9AaVKIdQvAcOHZWaMh98HgySsUtxRVf/chRe3dsSLqb6i40BzGRzEUddRaI+9TSw==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.26: - resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.8: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} @@ -24269,8 +24553,8 @@ packages: pvtsutils@1.3.6: resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} - pvutils@1.2.0: - resolution: {integrity: sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==} + pvutils@1.1.5: + resolution: {integrity: sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==} engines: {node: '>=16.0.0'} qs@6.14.2: @@ -25268,7 +25552,6 @@ packages: recharts@2.12.4: resolution: {integrity: sha512-dM4skmk4fDKEDjL9MNunxv6zcTxePGVEzRnLDXALRpfJ85JoQ0P0APJ/CoJlmnQI0gPjBlOkjzrwrfQrRST3KA==} engines: {node: '>=14'} - deprecated: 1.x and 2.x branches are no longer active. Bump to Recharts v3 to receive latest features and bugfixes. See https://github.com/recharts/recharts/wiki/3.0-migration-guide peerDependencies: react: ^16.0.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 @@ -25516,7 +25799,6 @@ packages: resolve-url@0.2.1: resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} - deprecated: https://github.com/lydell/resolve-url#deprecated resolve.exports@2.0.3: resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} @@ -25527,11 +25809,6 @@ packages: engines: {node: '>= 0.4'} hasBin: true - resolve@1.22.12: - resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} - engines: {node: '>= 0.4'} - hasBin: true - resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true @@ -25574,17 +25851,14 @@ packages: rimraf@2.6.3: resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} - deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} - deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@6.0.1: @@ -25647,11 +25921,6 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - rollup@4.62.2: - resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - roughjs@4.6.6: resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} @@ -25730,6 +25999,10 @@ packages: rslog@1.3.2: resolution: {integrity: sha512-1YyYXBvN0a2b1MSIDLwDTqqgjDzRKxUg/S/+KO6EAgbtZW1B3fdLHAMhEEtvk1patJYMqcRvlp3HQwnxj7AdGQ==} + rslog@2.3.0: + resolution: {integrity: sha512-g2wW/ermwzGTLlzGdJBDRc4YKz2/yPBjf57w9g5CvhtpZ91Xq95OaWku0oNHYi+XsuV6v8QrCo2oJJR/pCUR8g==} + engines: {node: ^20.19.0 || >=22.12.0} + rspack-manifest-plugin@5.0.3: resolution: {integrity: sha512-DCLSu5KE/ReIOhK2JTCQSI0JIgJ40E2i+2noqINtfhu12+UsK29dgMITEHIpYNR0JggcmmgZIDxPxm9dOV/2vQ==} engines: {node: '>=14'} @@ -26290,9 +26563,14 @@ packages: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - sharp@0.35.0: - resolution: {integrity: sha512-BqvG5XbwPZ4NV0DK90d86leEECMsoa8bO0nqnKWlBDYxri4GJ7c4EDInaF6q20lTh/mATmnDIKWJFfXnoVfH5g==} + sharp@0.35.3: + resolution: {integrity: sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==} engines: {node: '>=20.9.0'} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} @@ -26313,6 +26591,10 @@ packages: shell-exec@1.0.2: resolution: {integrity: sha512-jyVd+kU2X+mWKMmGhx4fpWbPsjvD53k9ivqetutVW/BQ+WIZoDoP4d8vUMGezV6saZsiNoW2f9GIhg9Dondohg==} + shell-quote@1.10.0: + resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==} + engines: {node: '>= 0.4'} + shell-quote@1.8.3: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} @@ -26415,6 +26697,17 @@ packages: resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==} engines: {node: '>=0.10.0'} + socket.io-adapter@2.5.8: + resolution: {integrity: sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==} + + socket.io-parser@4.2.7: + resolution: {integrity: sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==} + engines: {node: '>=10.0.0'} + + socket.io@4.8.1: + resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==} + engines: {node: '>=10.2.0'} + sockjs@0.3.24: resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} @@ -26450,7 +26743,6 @@ packages: source-map-resolve@0.5.3: resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} - deprecated: See https://github.com/lydell/source-map-resolve#deprecated source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} @@ -26463,7 +26755,6 @@ packages: source-map-url@0.4.1: resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==} - deprecated: See https://github.com/lydell/source-map-url#deprecated source-map@0.5.7: resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} @@ -26484,7 +26775,6 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} - deprecated: The work that was done in this beta branch won't be included in future versions space-separated-tokens@1.1.5: resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==} @@ -26648,7 +26938,6 @@ packages: stream-to-promise@2.2.0: resolution: {integrity: sha512-HAGUASw8NT0k8JvIVutB2Y/9iBk7gpgEyAudXwNJmZERdMITGdajOa4VJfD/kNiA3TppQpTP4J+CtcHwdzKBAw==} - deprecated: Deprecated. Use node:stream/promises and node:stream/consumers instead. streamroller@3.1.5: resolution: {integrity: sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==} @@ -26699,10 +26988,6 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} - string-width@8.2.2: - resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==} - engines: {node: '>=20'} - string.prototype.includes@2.0.1: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} @@ -27012,6 +27297,10 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + tar-fs@2.1.4: resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} @@ -27025,12 +27314,10 @@ packages: tar@4.4.18: resolution: {integrity: sha512-ZuOtqqmkV9RE1+4odd+MhBpibmCxNP6PJhH/h2OqNuotTX7/XHPZQJv2pKvWMplFH9SIZZhitehh6vBH6LO8Pg==} engines: {node: '>=4.5'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me tar@7.5.11: resolution: {integrity: sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==} @@ -27177,10 +27464,6 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinyglobby@0.2.17: - resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} - engines: {node: '>=12.0.0'} - tinypool@0.8.4: resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} engines: {node: '>=14.0.0'} @@ -27189,6 +27472,10 @@ packages: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} + tinypool@2.1.0: + resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@2.0.0: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} @@ -27300,7 +27587,6 @@ packages: trim@0.0.1: resolution: {integrity: sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==} - deprecated: Use String.prototype.trim() instead trough@1.0.5: resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==} @@ -27435,7 +27721,6 @@ packages: tsconfck@2.1.2: resolution: {integrity: sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==} engines: {node: ^14.13.1 || ^16 || >=18} - deprecated: unmaintained hasBin: true peerDependencies: typescript: ^4.3.5 || ^5.0.0 @@ -27505,7 +27790,6 @@ packages: tsup@7.3.0: resolution: {integrity: sha512-Ja1eaSRrE+QarmATlNO5fse2aOACYMBX+IZRKy1T+gpyH+jXgRrl5l4nHIQJQ1DoDgEjHDTw8cpE085UdBZuWQ==} engines: {node: '>=18'} - deprecated: Breaking node 16 hasBin: true peerDependencies: '@swc/core': ^1 @@ -27553,6 +27837,10 @@ packages: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} + type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + type-fest@0.16.0: resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} engines: {node: '>=10'} @@ -27906,7 +28194,6 @@ packages: urix@0.1.0: resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} - deprecated: Please see https://github.com/lydell/urix#deprecated url-join@4.0.1: resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} @@ -27992,17 +28279,14 @@ packages: uuid@3.3.2: resolution: {integrity: sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-compile-cache-lib@3.0.1: @@ -28151,46 +28435,6 @@ packages: yaml: optional: true - vite@7.3.6: - resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - vitest@3.2.6: resolution: {integrity: sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -28278,6 +28522,9 @@ packages: walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + wasm-feature-detect@1.8.0: + resolution: {integrity: sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==} + watchpack@2.5.1: resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} engines: {node: '>=10.13.0'} @@ -28406,10 +28653,6 @@ packages: resolution: {integrity: sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==} engines: {node: '>=10.13.0'} - webpack-sources@3.5.1: - resolution: {integrity: sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==} - engines: {node: '>=10.13.0'} - webpack-subresource-integrity@5.1.0: resolution: {integrity: sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==} engines: {node: '>= 12'} @@ -28444,7 +28687,6 @@ packages: whatwg-encoding@2.0.0: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'} - deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-fetch@3.6.20: resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} @@ -28605,18 +28847,6 @@ packages: utf-8-validate: optional: true - ws@8.21.2: - resolution: {integrity: sha512-54dMVAo4WIe6SKy3vBgN+9bJZqqQ8IMRevAkOLQALhi49qkkQDQfWdAZ8KQlXiEabw88ARXXdUrlvtbKQX+aKw==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.21.3: resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} engines: {node: '>=10.0.0'} @@ -28739,10 +28969,6 @@ packages: resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} - yargs@18.1.0: - resolution: {integrity: sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} - yauzl-clone@1.0.4: resolution: {integrity: sha512-igM2RRCf3k8TvZoxR2oguuw4z1xasOnA31joCqHIyLkeWrvAc2Jgay5ISQ2ZplinkoGaJ6orCz56Ey456c5ESA==} engines: {node: '>=6'} @@ -29127,6 +29353,12 @@ snapshots: dependencies: default-browser-id: 3.0.0 + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -29146,13 +29378,13 @@ snapshots: '@babel/core@7.12.9': dependencies: '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.8 + '@babel/generator': 7.29.7 '@babel/helper-module-transforms': 7.29.7(@babel/core@7.12.9) '@babel/helpers': 7.29.7 - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.7 '@babel/template': 7.29.7 '@babel/traverse': 7.29.7 - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 convert-source-map: 1.9.0 debug: 4.4.3(supports-color@8.1.1) gensync: 1.0.0-beta.2 @@ -29236,16 +29468,8 @@ snapshots: '@babel/generator@7.29.7': dependencies: - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/generator@7.29.8': - dependencies: - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 @@ -29261,11 +29485,11 @@ snapshots: '@babel/helper-annotate-as-pure@7.27.3': dependencies: - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 '@babel/helper-annotate-as-pure@7.29.7': dependencies: - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 '@babel/helper-compilation-targets@7.28.6': dependencies: @@ -29365,42 +29589,42 @@ snapshots: '@babel/helper-member-expression-to-functions@7.28.5': dependencies: '@babel/traverse': 7.29.7 - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color '@babel/helper-member-expression-to-functions@7.29.7': dependencies: '@babel/traverse': 7.29.7 - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.28.6': dependencies: '@babel/traverse': 7.29.7 - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.28.6(supports-color@5.5.0)': dependencies: '@babel/traverse': 7.29.7(supports-color@5.5.0) - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.29.7': dependencies: '@babel/traverse': 7.29.7 - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.29.7(supports-color@5.5.0)': dependencies: '@babel/traverse': 7.29.7(supports-color@5.5.0) - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color @@ -29442,11 +29666,11 @@ snapshots: '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 '@babel/helper-optimise-call-expression@7.29.7': dependencies: - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 '@babel/helper-plugin-utils@7.10.4': {} @@ -29502,14 +29726,14 @@ snapshots: '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: '@babel/traverse': 7.29.7 - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.29.7': dependencies: '@babel/traverse': 7.29.7 - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color @@ -29533,7 +29757,7 @@ snapshots: dependencies: '@babel/template': 7.29.7 '@babel/traverse': 7.29.7 - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color @@ -29545,7 +29769,7 @@ snapshots: '@babel/helpers@7.29.7': dependencies: '@babel/template': 7.29.7 - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 '@babel/parser@7.29.2': dependencies: @@ -29555,10 +29779,6 @@ snapshots: dependencies: '@babel/types': 7.29.7 - '@babel/parser@7.29.8': - dependencies: - '@babel/types': 7.29.8 - '@babel/parser@8.0.0-rc.2': dependencies: '@babel/types': 8.0.0-rc.2 @@ -31054,8 +31274,8 @@ snapshots: '@babel/template@7.29.7': dependencies: '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@babel/traverse@7.29.0': dependencies: @@ -31093,18 +31313,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/traverse@7.29.8': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.8 - '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.8 - '@babel/template': 7.29.7 - '@babel/types': 7.29.8 - debug: 4.4.3(supports-color@8.1.1) - transitivePeerDependencies: - - supports-color - '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -31115,11 +31323,6 @@ snapshots: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@babel/types@7.29.8': - dependencies: - '@babel/helper-string-parser': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - '@babel/types@8.0.0-rc.2': dependencies: '@babel/helper-string-parser': 8.0.0-rc.3 @@ -31184,7 +31387,7 @@ snapshots: outdent: 0.5.0 prettier: 2.8.8 resolve-from: 5.0.0 - semver: 7.8.5 + semver: 7.6.3 '@changesets/changelog-git@0.2.1': dependencies: @@ -31221,7 +31424,7 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@changesets/cli@2.30.0(@types/node@26.2.0)': + '@changesets/cli@2.30.0(@types/node@26.1.0)': dependencies: '@changesets/apply-release-plan': 7.1.0 '@changesets/assemble-release-plan': link:packages/assemble-release-plan @@ -31237,7 +31440,7 @@ snapshots: '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@changesets/write': 0.4.0 - '@inquirer/external-editor': 1.0.3(@types/node@26.2.0) + '@inquirer/external-editor': 1.0.3(@types/node@26.1.0) '@manypkg/get-packages': 1.1.3 ansi-colors: 4.1.3 enquirer: 2.4.1 @@ -31488,7 +31691,7 @@ snapshots: '@commitlint/is-ignored@19.8.1': dependencies: '@commitlint/types': 19.8.1 - semver: 7.8.5 + semver: 7.6.3 '@commitlint/lint@19.8.1': dependencies: @@ -31611,13 +31814,9 @@ snapshots: dependencies: postcss-selector-parser: 6.1.2 - '@csstools/utilities@1.0.0(postcss@8.5.23)': - dependencies: - postcss: 8.5.23 - - '@csstools/utilities@1.0.0(postcss@8.5.26)': + '@csstools/utilities@1.0.0(postcss@8.5.24)': dependencies: - postcss: 8.5.26 + postcss: 8.5.24 '@ctrl/tinycolor@3.6.1': {} @@ -31629,7 +31828,7 @@ snapshots: combined-stream: 1.0.8 extend: 3.0.2 forever-agent: 0.6.1 - form-data: 4.0.6 + form-data: 4.0.5 http-signature: 1.4.0 is-typedarray: 1.0.0 isstream: 0.1.2 @@ -31673,18 +31872,18 @@ snapshots: dependencies: '@emnapi/wasi-threads': 1.2.2 tslib: 2.8.1 + optional: true - '@emnapi/core@1.11.3': + '@emnapi/core@1.11.2': dependencies: - '@emnapi/wasi-threads': 1.2.3 + '@emnapi/wasi-threads': 1.2.2 tslib: 2.8.1 optional: true - '@emnapi/core@1.9.0': + '@emnapi/core@1.11.3': dependencies: - '@emnapi/wasi-threads': 1.2.0 + '@emnapi/wasi-threads': 1.2.3 tslib: 2.8.1 - optional: true '@emnapi/runtime@1.10.0': dependencies: @@ -31696,19 +31895,14 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.11.3': - dependencies: - tslib: 2.8.1 - - '@emnapi/runtime@1.9.0': + '@emnapi/runtime@1.11.2': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.2.0': + '@emnapi/runtime@1.11.3': dependencies: tslib: 2.8.1 - optional: true '@emnapi/wasi-threads@1.2.1': dependencies: @@ -31718,11 +31912,11 @@ snapshots: '@emnapi/wasi-threads@1.2.2': dependencies: tslib: 2.8.1 + optional: true '@emnapi/wasi-threads@1.2.3': dependencies: tslib: 2.8.1 - optional: true '@emotion/babel-plugin@11.13.5': dependencies: @@ -32480,7 +32674,7 @@ snapshots: minimatch: 3.1.5 p-limit: 3.1.0 resolve-from: 5.0.0 - semver: 7.8.5 + semver: 7.6.3 transitivePeerDependencies: - supports-color @@ -32579,11 +32773,11 @@ snapshots: dependencies: react: 19.0.0-rc-cd22717c-20241013 - '@hono/node-server@1.19.17(hono@4.13.1)': + '@hono/node-server@1.19.13(hono@4.13.1)': dependencies: hono: 4.13.1 - '@hono/node-server@2.0.11(hono@4.13.0)': + '@hono/node-server@2.0.11(hono@4.13.1)': dependencies: hono: 4.13.1 @@ -32638,9 +32832,9 @@ snapshots: '@img/sharp-libvips-darwin-arm64': 1.2.4 optional: true - '@img/sharp-darwin-arm64@0.35.0': + '@img/sharp-darwin-arm64@0.35.3': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.3.0 + '@img/sharp-libvips-darwin-arm64': 1.3.2 optional: true '@img/sharp-darwin-x64@0.34.5': @@ -32648,74 +32842,74 @@ snapshots: '@img/sharp-libvips-darwin-x64': 1.2.4 optional: true - '@img/sharp-darwin-x64@0.35.0': + '@img/sharp-darwin-x64@0.35.3': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.3.0 + '@img/sharp-libvips-darwin-x64': 1.3.2 optional: true - '@img/sharp-freebsd-wasm32@0.35.0': + '@img/sharp-freebsd-wasm32@0.35.3': dependencies: - '@img/sharp-wasm32': 0.35.0 + '@img/sharp-wasm32': 0.35.3 optional: true '@img/sharp-libvips-darwin-arm64@1.2.4': optional: true - '@img/sharp-libvips-darwin-arm64@1.3.0': + '@img/sharp-libvips-darwin-arm64@1.3.2': optional: true '@img/sharp-libvips-darwin-x64@1.2.4': optional: true - '@img/sharp-libvips-darwin-x64@1.3.0': + '@img/sharp-libvips-darwin-x64@1.3.2': optional: true '@img/sharp-libvips-linux-arm64@1.2.4': optional: true - '@img/sharp-libvips-linux-arm64@1.3.0': + '@img/sharp-libvips-linux-arm64@1.3.2': optional: true '@img/sharp-libvips-linux-arm@1.2.4': optional: true - '@img/sharp-libvips-linux-arm@1.3.0': + '@img/sharp-libvips-linux-arm@1.3.2': optional: true '@img/sharp-libvips-linux-ppc64@1.2.4': optional: true - '@img/sharp-libvips-linux-ppc64@1.3.0': + '@img/sharp-libvips-linux-ppc64@1.3.2': optional: true '@img/sharp-libvips-linux-riscv64@1.2.4': optional: true - '@img/sharp-libvips-linux-riscv64@1.3.0': + '@img/sharp-libvips-linux-riscv64@1.3.2': optional: true '@img/sharp-libvips-linux-s390x@1.2.4': optional: true - '@img/sharp-libvips-linux-s390x@1.3.0': + '@img/sharp-libvips-linux-s390x@1.3.2': optional: true '@img/sharp-libvips-linux-x64@1.2.4': optional: true - '@img/sharp-libvips-linux-x64@1.3.0': + '@img/sharp-libvips-linux-x64@1.3.2': optional: true '@img/sharp-libvips-linuxmusl-arm64@1.2.4': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.3.0': + '@img/sharp-libvips-linuxmusl-arm64@1.3.2': optional: true '@img/sharp-libvips-linuxmusl-x64@1.2.4': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.3.0': + '@img/sharp-libvips-linuxmusl-x64@1.3.2': optional: true '@img/sharp-linux-arm64@0.34.5': @@ -32723,9 +32917,9 @@ snapshots: '@img/sharp-libvips-linux-arm64': 1.2.4 optional: true - '@img/sharp-linux-arm64@0.35.0': + '@img/sharp-linux-arm64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.3.0 + '@img/sharp-libvips-linux-arm64': 1.3.2 optional: true '@img/sharp-linux-arm@0.34.5': @@ -32733,9 +32927,9 @@ snapshots: '@img/sharp-libvips-linux-arm': 1.2.4 optional: true - '@img/sharp-linux-arm@0.35.0': + '@img/sharp-linux-arm@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.3.0 + '@img/sharp-libvips-linux-arm': 1.3.2 optional: true '@img/sharp-linux-ppc64@0.34.5': @@ -32743,9 +32937,9 @@ snapshots: '@img/sharp-libvips-linux-ppc64': 1.2.4 optional: true - '@img/sharp-linux-ppc64@0.35.0': + '@img/sharp-linux-ppc64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.3.0 + '@img/sharp-libvips-linux-ppc64': 1.3.2 optional: true '@img/sharp-linux-riscv64@0.34.5': @@ -32753,9 +32947,9 @@ snapshots: '@img/sharp-libvips-linux-riscv64': 1.2.4 optional: true - '@img/sharp-linux-riscv64@0.35.0': + '@img/sharp-linux-riscv64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.3.0 + '@img/sharp-libvips-linux-riscv64': 1.3.2 optional: true '@img/sharp-linux-s390x@0.34.5': @@ -32763,9 +32957,9 @@ snapshots: '@img/sharp-libvips-linux-s390x': 1.2.4 optional: true - '@img/sharp-linux-s390x@0.35.0': + '@img/sharp-linux-s390x@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.3.0 + '@img/sharp-libvips-linux-s390x': 1.3.2 optional: true '@img/sharp-linux-x64@0.34.5': @@ -32773,9 +32967,9 @@ snapshots: '@img/sharp-libvips-linux-x64': 1.2.4 optional: true - '@img/sharp-linux-x64@0.35.0': + '@img/sharp-linux-x64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.3.0 + '@img/sharp-libvips-linux-x64': 1.3.2 optional: true '@img/sharp-linuxmusl-arm64@0.34.5': @@ -32783,9 +32977,9 @@ snapshots: '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 optional: true - '@img/sharp-linuxmusl-arm64@0.35.0': + '@img/sharp-linuxmusl-arm64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.3.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.2 optional: true '@img/sharp-linuxmusl-x64@0.34.5': @@ -32793,42 +32987,42 @@ snapshots: '@img/sharp-libvips-linuxmusl-x64': 1.2.4 optional: true - '@img/sharp-linuxmusl-x64@0.35.0': + '@img/sharp-linuxmusl-x64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.3.0 + '@img/sharp-libvips-linuxmusl-x64': 1.3.2 optional: true '@img/sharp-wasm32@0.34.5': dependencies: - '@emnapi/runtime': 1.9.0 + '@emnapi/runtime': 1.11.3 optional: true - '@img/sharp-wasm32@0.35.0': + '@img/sharp-wasm32@0.35.3': dependencies: '@emnapi/runtime': 1.11.3 optional: true - '@img/sharp-webcontainers-wasm32@0.35.0': + '@img/sharp-webcontainers-wasm32@0.35.3': dependencies: - '@img/sharp-wasm32': 0.35.0 + '@img/sharp-wasm32': 0.35.3 optional: true '@img/sharp-win32-arm64@0.34.5': optional: true - '@img/sharp-win32-arm64@0.35.0': + '@img/sharp-win32-arm64@0.35.3': optional: true '@img/sharp-win32-ia32@0.34.5': optional: true - '@img/sharp-win32-ia32@0.35.0': + '@img/sharp-win32-ia32@0.35.3': optional: true '@img/sharp-win32-x64@0.34.5': optional: true - '@img/sharp-win32-x64@0.35.0': + '@img/sharp-win32-x64@0.35.3': optional: true '@inquirer/external-editor@1.0.3(@types/node@20.19.5)': @@ -32838,12 +33032,12 @@ snapshots: optionalDependencies: '@types/node': 20.19.5 - '@inquirer/external-editor@1.0.3(@types/node@26.2.0)': + '@inquirer/external-editor@1.0.3(@types/node@26.1.0)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 26.2.0 + '@types/node': 26.1.0 '@internationalized/date@3.12.2': dependencies: @@ -32879,7 +33073,7 @@ snapshots: camelcase: 5.3.1 find-up: 4.1.0 get-package-type: 0.1.0 - js-yaml: 3.15.1 + js-yaml: 3.15.0 resolve-from: 5.0.0 '@istanbuljs/schema@0.1.3': {} @@ -32963,7 +33157,7 @@ snapshots: - supports-color - ts-node - '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2))': + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -32977,7 +33171,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)) + jest-config: 29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -33415,6 +33609,182 @@ snapshots: lodash: 4.18.1 react: 18.3.1 + '@lynx-js/cache-events-webpack-plugin@0.2.0': + dependencies: + '@lynx-js/webpack-runtime-globals': 0.0.7 + + '@lynx-js/chunk-loading-webpack-plugin@0.4.0': + dependencies: + '@lynx-js/webpack-runtime-globals': 0.0.6 + + '@lynx-js/chunk-loading-webpack-plugin@0.4.1': + dependencies: + '@lynx-js/webpack-runtime-globals': 0.0.7 + + '@lynx-js/css-extract-webpack-plugin@0.9.0(@lynx-js/template-webpack-plugin@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09(tslib@2.8.1))': + dependencies: + '@lynx-js/template-webpack-plugin': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09(tslib@2.8.1) + + '@lynx-js/css-serializer@0.1.6': + dependencies: + css-tree: 3.2.1 + + '@lynx-js/debug-metadata-rsbuild-plugin@0.2.0': + dependencies: + '@lynx-js/debug-metadata': 0.1.0 + + '@lynx-js/debug-metadata@0.1.0': {} + + '@lynx-js/internal-preact@10.29.1-20260519060144-e6da44c': {} + + '@lynx-js/lynx-bundle-rslib-config@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/lynx-bundle-rslib-config@e22ca09(tslib@2.8.1)': + dependencies: + '@lynx-js/css-serializer': 0.1.6 + '@lynx-js/runtime-wrapper-webpack-plugin': 0.2.2 + '@lynx-js/tasm': 0.0.39 + '@lynx-js/web-core': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-core@e22ca09(@lynx-js/css-serializer@0.1.6)(tslib@2.8.1) + transitivePeerDependencies: + - '@lynx-js/lynx-core' + - tslib + + '@lynx-js/qrcode-rsbuild-plugin@0.6.0(@lynx-js/rspeedy@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/rspeedy@e22ca09(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@module-federation/runtime-tools@packages+runtime-tools)(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(core-js@3.49.0)(esbuild@0.28.1)(typescript@6.0.3)(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)))': + dependencies: + '@lynx-js/rspeedy': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/rspeedy@e22ca09(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@module-federation/runtime-tools@packages+runtime-tools)(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(core-js@3.49.0)(esbuild@0.28.1)(typescript@6.0.3)(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + + '@lynx-js/react-alias-rsbuild-plugin@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react-alias-rsbuild-plugin@e22ca09': {} + + '@lynx-js/react-refresh-webpack-plugin@0.4.1(@lynx-js/react-webpack-plugin@0.10.0(@lynx-js/react@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09(@lynx-js/types@4.0.0)(@types/react@18.3.28))(@lynx-js/template-webpack-plugin@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09(tslib@2.8.1)))': + dependencies: + '@lynx-js/react-webpack-plugin': 0.10.0(@lynx-js/react@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09(@lynx-js/types@4.0.0)(@types/react@18.3.28))(@lynx-js/template-webpack-plugin@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09(tslib@2.8.1)) + + '@lynx-js/react-rsbuild-plugin@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react-rsbuild-plugin@e22ca09(@lynx-js/react@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09(@lynx-js/types@4.0.0)(@types/react@18.3.28))(tslib@2.8.1)': + dependencies: + '@lynx-js/css-extract-webpack-plugin': 0.9.0(@lynx-js/template-webpack-plugin@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09(tslib@2.8.1)) + '@lynx-js/react-alias-rsbuild-plugin': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react-alias-rsbuild-plugin@e22ca09 + '@lynx-js/react-refresh-webpack-plugin': 0.4.1(@lynx-js/react-webpack-plugin@0.10.0(@lynx-js/react@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09(@lynx-js/types@4.0.0)(@types/react@18.3.28))(@lynx-js/template-webpack-plugin@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09(tslib@2.8.1))) + '@lynx-js/react-webpack-plugin': 0.10.0(@lynx-js/react@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09(@lynx-js/types@4.0.0)(@types/react@18.3.28))(@lynx-js/template-webpack-plugin@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09(tslib@2.8.1)) + '@lynx-js/runtime-wrapper-webpack-plugin': 0.2.2 + '@lynx-js/template-webpack-plugin': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09(tslib@2.8.1) + '@lynx-js/use-sync-external-store': 1.5.0(@lynx-js/react@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09(@lynx-js/types@4.0.0)(@types/react@18.3.28)) + background-only: 0.0.1 + tiny-invariant: 1.3.3 + optionalDependencies: + '@lynx-js/react': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09(@lynx-js/types@4.0.0)(@types/react@18.3.28) + transitivePeerDependencies: + - '@lynx-js/lynx-core' + - tslib + + '@lynx-js/react-webpack-plugin@0.10.0(@lynx-js/react@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09(@lynx-js/types@4.0.0)(@types/react@18.3.28))(@lynx-js/template-webpack-plugin@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09(tslib@2.8.1))': + dependencies: + '@lynx-js/debug-metadata': 0.1.0 + '@lynx-js/template-webpack-plugin': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09(tslib@2.8.1) + '@lynx-js/webpack-runtime-globals': 0.0.7 + tiny-invariant: 1.3.3 + optionalDependencies: + '@lynx-js/react': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09(@lynx-js/types@4.0.0)(@types/react@18.3.28) + + '@lynx-js/react@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09(@lynx-js/types@4.0.0)(@types/react@18.3.28)': + dependencies: + '@types/react': 18.3.28 + preact: '@lynx-js/internal-preact@10.29.1-20260519060144-e6da44c' + optionalDependencies: + '@lynx-js/types': 4.0.0 + + '@lynx-js/react@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09(@lynx-js/types@4.0.0)(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + preact: '@lynx-js/internal-preact@10.29.1-20260519060144-e6da44c' + optionalDependencies: + '@lynx-js/types': 4.0.0 + + '@lynx-js/rspeedy@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/rspeedy@e22ca09(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@module-federation/runtime-tools@packages+runtime-tools)(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(core-js@3.49.0)(esbuild@0.28.1)(typescript@6.0.3)(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4))': + dependencies: + '@lynx-js/cache-events-webpack-plugin': 0.2.0 + '@lynx-js/chunk-loading-webpack-plugin': 0.4.1 + '@lynx-js/debug-metadata-rsbuild-plugin': 0.2.0 + '@lynx-js/web-rsbuild-server-middleware': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-rsbuild-server-middleware@e22ca09 + '@lynx-js/webpack-dev-transport': 0.3.0 + '@lynx-js/websocket': 0.0.4 + '@rsbuild/core': 2.1.4(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0) + '@rsbuild/plugin-css-minimizer': 2.0.0(@rsbuild/core@2.1.4(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0))(esbuild@0.28.1)(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + '@rsdoctor/rspack-plugin': 1.5.18(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@rsbuild/core@2.1.4(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0))(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + - '@module-federation/runtime-tools' + - '@parcel/css' + - '@rspack/core' + - '@swc/css' + - bufferutil + - clean-css + - core-js + - csso + - esbuild + - lightningcss + - supports-color + - utf-8-validate + - webpack + + '@lynx-js/runtime-wrapper-webpack-plugin@0.2.2': + dependencies: + '@lynx-js/webpack-runtime-globals': 0.0.7 + + '@lynx-js/tasm@0.0.39': {} + + '@lynx-js/template-webpack-plugin@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/template-webpack-plugin@e22ca09(tslib@2.8.1)': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@lynx-js/css-serializer': 0.1.6 + '@lynx-js/tasm': 0.0.39 + '@lynx-js/web-core': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-core@e22ca09(@lynx-js/css-serializer@0.1.6)(tslib@2.8.1) + '@lynx-js/webpack-runtime-globals': 0.0.7 + '@rspack/lite-tapable': 1.1.0 + css-tree: 3.2.1 + object.groupby: 1.0.3 + tinypool: 2.1.0 + transitivePeerDependencies: + - '@lynx-js/lynx-core' + - tslib + + '@lynx-js/types@4.0.0': + dependencies: + csstype: 3.1.3 + + '@lynx-js/use-sync-external-store@1.5.0(@lynx-js/react@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09(@lynx-js/types@4.0.0)(@types/react@18.3.28))': + dependencies: + '@lynx-js/react': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/react@e22ca09(@lynx-js/types@4.0.0)(@types/react@18.3.28) + + '@lynx-js/web-core@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-core@e22ca09(@lynx-js/css-serializer@0.1.6)(tslib@2.8.1)': + dependencies: + '@lynx-js/web-elements': 0.12.6(tslib@2.8.1) + '@lynx-js/web-worker-rpc': https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-worker-rpc@e22ca09 + wasm-feature-detect: 1.8.0 + optionalDependencies: + '@lynx-js/css-serializer': 0.1.6 + tslib: 2.8.1 + + '@lynx-js/web-elements@0.12.6(tslib@2.8.1)': + dependencies: + dompurify: 3.4.11 + markdown-it: 14.1.1 + tslib: 2.8.1 + + '@lynx-js/web-rsbuild-server-middleware@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-rsbuild-server-middleware@e22ca09': {} + + '@lynx-js/web-worker-rpc@https://pkg.pr.new/ScriptedAlchemy/lynx-stack/@lynx-js/web-worker-rpc@e22ca09': {} + + '@lynx-js/webpack-dev-transport@0.3.0': {} + + '@lynx-js/webpack-runtime-globals@0.0.6': {} + + '@lynx-js/webpack-runtime-globals@0.0.7': {} + + '@lynx-js/websocket@0.0.4': + dependencies: + eventemitter3: 5.0.4 + '@manypkg/find-root@1.1.0': dependencies: '@babel/runtime': 7.28.2 @@ -33440,7 +33810,7 @@ snapshots: nopt: 5.0.0 npmlog: 5.0.1 rimraf: 3.0.2 - semver: 7.8.5 + semver: 7.6.3 tar: 6.2.1 transitivePeerDependencies: - encoding @@ -33453,7 +33823,7 @@ snapshots: https-proxy-agent: 7.0.6 node-fetch: 2.7.0(encoding@0.1.13) nopt: 8.1.0 - semver: 7.8.5 + semver: 7.6.3 tar: 7.5.11 transitivePeerDependencies: - encoding @@ -33609,11 +33979,11 @@ snapshots: - '@types/node' optional: true - '@microsoft/api-extractor-model@7.33.4(@types/node@26.2.0)': + '@microsoft/api-extractor-model@7.33.4(@types/node@26.1.0)': dependencies: '@microsoft/tsdoc': 0.16.0 '@microsoft/tsdoc-config': 0.18.1 - '@rushstack/node-core-library': 5.20.3(@types/node@26.2.0) + '@rushstack/node-core-library': 5.20.3(@types/node@26.1.0) transitivePeerDependencies: - '@types/node' optional: true @@ -33627,10 +33997,10 @@ snapshots: '@rushstack/rig-package': 0.7.2 '@rushstack/terminal': 0.22.3(@types/node@20.19.5) '@rushstack/ts-command-line': 5.3.3(@types/node@20.19.5) - diff: 8.0.4 + diff: 8.0.3 lodash: 4.17.23 minimatch: 10.2.3 - resolve: 1.22.12 + resolve: 1.22.11 semver: 7.5.4 source-map: 0.6.1 typescript: 5.8.2 @@ -33647,10 +34017,10 @@ snapshots: '@rushstack/rig-package': 0.7.2 '@rushstack/terminal': 0.22.3(@types/node@22.19.15) '@rushstack/ts-command-line': 5.3.3(@types/node@22.19.15) - diff: 8.0.4 + diff: 8.0.3 lodash: 4.17.23 minimatch: 10.2.3 - resolve: 1.22.12 + resolve: 1.22.11 semver: 7.5.4 source-map: 0.6.1 typescript: 5.8.2 @@ -33658,19 +34028,19 @@ snapshots: - '@types/node' optional: true - '@microsoft/api-extractor@7.57.7(@types/node@26.2.0)': + '@microsoft/api-extractor@7.57.7(@types/node@26.1.0)': dependencies: - '@microsoft/api-extractor-model': 7.33.4(@types/node@26.2.0) + '@microsoft/api-extractor-model': 7.33.4(@types/node@26.1.0) '@microsoft/tsdoc': 0.16.0 '@microsoft/tsdoc-config': 0.18.1 - '@rushstack/node-core-library': 5.20.3(@types/node@26.2.0) + '@rushstack/node-core-library': 5.20.3(@types/node@26.1.0) '@rushstack/rig-package': 0.7.2 - '@rushstack/terminal': 0.22.3(@types/node@26.2.0) - '@rushstack/ts-command-line': 5.3.3(@types/node@26.2.0) - diff: 8.0.4 + '@rushstack/terminal': 0.22.3(@types/node@26.1.0) + '@rushstack/ts-command-line': 5.3.3(@types/node@26.1.0) + diff: 8.0.3 lodash: 4.17.23 minimatch: 10.2.3 - resolve: 1.22.12 + resolve: 1.22.11 semver: 7.5.4 source-map: 0.6.1 typescript: 5.8.2 @@ -33683,7 +34053,7 @@ snapshots: '@microsoft/tsdoc': 0.16.0 ajv: 8.18.0 jju: 1.4.0 - resolve: 1.22.12 + resolve: 1.22.11 optional: true '@microsoft/tsdoc@0.16.0': @@ -33691,7 +34061,7 @@ snapshots: '@modelcontextprotocol/sdk@1.27.1(zod@3.25.76)': dependencies: - '@hono/node-server': 1.19.17(hono@4.13.1) + '@hono/node-server': 1.19.13(hono@4.13.1) ajv: 8.18.0 ajv-formats: 3.0.1(ajv@8.18.0) content-type: 1.0.5 @@ -33783,7 +34153,7 @@ snapshots: '@swc/helpers': 0.5.1 redux: 4.2.1 - '@modern-js/app-tools@2.70.5(@rspack/core@1.7.9(@swc/helpers@0.5.17))(@swc/core@1.15.41(@swc/helpers@0.5.17))(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rollup@4.62.2)(styled-components@6.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.2.0)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(type-fest@2.19.0)(typescript@7.0.2)(webpack-cli@5.1.4)(webpack-dev-server@5.2.3(webpack-cli@5.1.4)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.25.5)(webpack-cli@5.1.4)))(webpack-hot-middleware@2.26.1)': + '@modern-js/app-tools@2.70.5(@rspack/core@1.7.9(@swc/helpers@0.5.17))(@swc/core@1.15.41(@swc/helpers@0.5.17))(encoding@0.1.13)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rollup@4.59.0)(styled-components@6.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.1.0)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(type-fest@2.19.0)(typescript@7.0.2)(webpack-cli@5.1.4)(webpack-dev-server@5.2.3(webpack-cli@5.1.4)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.25.5)(webpack-cli@5.1.4)))(webpack-hot-middleware@2.26.1)': dependencies: '@babel/parser': 7.29.2 '@babel/traverse': 7.29.0 @@ -33796,7 +34166,7 @@ snapshots: '@modern-js/plugin-v2': 2.70.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@modern-js/prod-server': 2.70.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@modern-js/rsbuild-plugin-esbuild': 2.70.5(@swc/core@1.15.41(@swc/helpers@0.5.17))(webpack-cli@5.1.4) - '@modern-js/server': 2.70.5(@babel/traverse@7.29.0)(@rsbuild/core@1.7.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.2.0)(typescript@7.0.2))(tsconfig-paths@4.2.0) + '@modern-js/server': 2.70.5(@babel/traverse@7.29.0)(@rsbuild/core@1.7.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.1.0)(typescript@7.0.2))(tsconfig-paths@4.2.0) '@modern-js/server-core': 2.70.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@modern-js/server-utils': 2.70.5(@babel/traverse@7.29.0)(@rsbuild/core@1.7.3) '@modern-js/types': 2.70.5 @@ -33810,11 +34180,11 @@ snapshots: esbuild-register: 3.6.0(esbuild@0.25.5) flatted: 3.4.2 mlly: 1.8.1 - ndepe: 0.1.13(encoding@0.1.13)(rollup@4.62.2) + ndepe: 0.1.13(encoding@0.1.13)(rollup@4.59.0) pkg-types: 1.3.1 std-env: 3.10.0 optionalDependencies: - ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.2.0)(typescript@7.0.2) + ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.1.0)(typescript@7.0.2) tsconfig-paths: 4.2.0 transitivePeerDependencies: - '@parcel/css' @@ -33845,7 +34215,7 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - '@modern-js/app-tools@2.70.8(@rspack/core@1.7.9(@swc/helpers@0.5.19))(@swc/core@1.15.41(@swc/helpers@0.5.19))(encoding@0.1.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.62.2)(styled-components@6.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.19))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(type-fest@2.19.0)(typescript@7.0.2)(webpack-cli@5.1.4)(webpack-dev-server@5.2.3(webpack-cli@5.1.4)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.19))(esbuild@0.18.20)(webpack-cli@5.1.4)))(webpack-hot-middleware@2.26.1)': + '@modern-js/app-tools@2.70.8(@rspack/core@1.7.9(@swc/helpers@0.5.19))(@swc/core@1.15.41(@swc/helpers@0.5.19))(encoding@0.1.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(styled-components@6.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.19))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(type-fest@2.19.0)(typescript@7.0.2)(webpack-cli@5.1.4)(webpack-dev-server@5.2.3(webpack-cli@5.1.4)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.19))(esbuild@0.18.20)(webpack-cli@5.1.4)))(webpack-hot-middleware@2.26.1)': dependencies: '@babel/parser': 7.29.2 '@babel/traverse': 7.29.0 @@ -33872,7 +34242,7 @@ snapshots: esbuild-register: 3.6.0(esbuild@0.25.5) flatted: 3.4.2 mlly: 1.8.1 - ndepe: 0.1.13(encoding@0.1.13)(rollup@4.62.2) + ndepe: 0.1.13(encoding@0.1.13)(rollup@4.59.0) pkg-types: 1.3.1 std-env: 3.10.0 optionalDependencies: @@ -33907,7 +34277,7 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - '@modern-js/app-tools@3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.0.6(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.17))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.0.6(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.2.0)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.28.1)(webpack-cli@5.1.4))': + '@modern-js/app-tools@3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.0.6(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.17))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.0.6(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.1.0)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.28.1)(webpack-cli@5.1.4))': dependencies: '@babel/parser': 7.29.7 '@babel/traverse': 7.29.7 @@ -33917,7 +34287,7 @@ snapshots: '@modern-js/plugin': 3.5.0(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@modern-js/plugin-data-loader': 3.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@modern-js/prod-server': 3.5.0(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@modern-js/server': 3.5.0(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.2.0)(typescript@7.0.2))(tsconfig-paths@4.2.0) + '@modern-js/server': 3.5.0(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.1.0)(typescript@7.0.2))(tsconfig-paths@4.2.0) '@modern-js/server-core': 3.5.0(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@modern-js/server-utils': 3.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@modern-js/types': 3.5.0 @@ -33929,11 +34299,11 @@ snapshots: flatted: 3.4.2 import-meta-resolve: 4.2.0 mlly: 1.8.2 - ndepe: 0.1.13(encoding@0.1.13)(rollup@4.62.2) + ndepe: 0.1.13(encoding@0.1.13)(rollup@4.59.0) pkg-types: 1.3.1 std-env: 3.10.0 optionalDependencies: - ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.2.0)(typescript@7.0.2) + ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.1.0)(typescript@7.0.2) tsconfig-paths: 4.2.0 transitivePeerDependencies: - '@module-federation/runtime-tools' @@ -33960,7 +34330,7 @@ snapshots: - utf-8-validate - webpack - '@modern-js/app-tools@3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@3.14.2)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1)))': + '@modern-js/app-tools@3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@3.14.2)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1)))': dependencies: '@babel/parser': 7.29.7 '@babel/traverse': 7.29.7 @@ -33982,7 +34352,7 @@ snapshots: flatted: 3.4.2 import-meta-resolve: 4.2.0 mlly: 1.8.2 - ndepe: 0.1.13(encoding@0.1.13)(rollup@4.62.2) + ndepe: 0.1.13(encoding@0.1.13)(rollup@4.59.0) pkg-types: 1.3.1 std-env: 3.10.0 optionalDependencies: @@ -34013,7 +34383,7 @@ snapshots: - utf-8-validate - webpack - '@modern-js/app-tools@3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1)))': + '@modern-js/app-tools@3.5.0(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(core-js@3.49.0)(encoding@0.1.13)(esbuild@0.28.1)(react-dom@18.3.1(react@18.3.1))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(tsconfig-paths@4.2.0)(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1)))': dependencies: '@babel/parser': 7.29.7 '@babel/traverse': 7.29.7 @@ -34035,7 +34405,7 @@ snapshots: flatted: 3.4.2 import-meta-resolve: 4.2.0 mlly: 1.8.2 - ndepe: 0.1.13(encoding@0.1.13)(rollup@4.62.2) + ndepe: 0.1.13(encoding@0.1.13)(rollup@4.59.0) pkg-types: 1.3.1 std-env: 3.10.0 optionalDependencies: @@ -34146,7 +34516,7 @@ snapshots: '@babel/preset-env': 7.29.2(@babel/core@7.29.7) '@babel/preset-typescript': 7.28.5(@babel/core@7.29.7) '@babel/runtime': 7.28.2 - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 '@rsbuild/plugin-babel': 1.0.5(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)) '@swc/helpers': 0.5.17 '@types/babel__core': 7.20.5 @@ -34167,7 +34537,7 @@ snapshots: '@babel/preset-env': 7.29.2(@babel/core@7.29.7) '@babel/preset-typescript': 7.28.5(@babel/core@7.29.7) '@babel/runtime': 7.28.2 - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 '@rsbuild/plugin-babel': 1.1.0(@rsbuild/core@1.7.3) '@swc/helpers': 0.5.17 '@types/babel__core': 7.20.5 @@ -34188,7 +34558,7 @@ snapshots: '@babel/preset-env': 7.29.2(@babel/core@7.29.7) '@babel/preset-typescript': 7.28.5(@babel/core@7.29.7) '@babel/runtime': 7.28.2 - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 '@rsbuild/plugin-babel': 1.1.0(@rsbuild/core@1.7.3) '@swc/helpers': 0.5.17 '@types/babel__core': 7.20.5 @@ -34215,20 +34585,20 @@ snapshots: '@rsbuild/plugin-typed-css-modules': 1.2.3(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)) '@swc/core': 1.15.41(@swc/helpers@0.5.17) '@swc/helpers': 0.5.17 - autoprefixer: 10.4.27(postcss@8.5.23) + autoprefixer: 10.4.27(postcss@8.5.24) browserslist: 4.28.2 core-js: 3.49.0 - cssnano: 6.1.2(postcss@8.5.23) + cssnano: 6.1.2(postcss@8.5.24) html-minifier-terser: 7.2.0 lodash: 4.18.1 - postcss: 8.5.23 - postcss-custom-properties: 13.3.12(postcss@8.5.23) - postcss-flexbugs-fixes: 5.0.2(postcss@8.5.23) - postcss-font-variant: 5.0.0(postcss@8.5.23) - postcss-initial: 4.0.1(postcss@8.5.23) - postcss-media-minmax: 5.0.0(postcss@8.5.23) - postcss-nesting: 12.1.5(postcss@8.5.23) - postcss-page-break: 3.0.4(postcss@8.5.23) + postcss: 8.5.24 + postcss-custom-properties: 13.3.12(postcss@8.5.24) + postcss-flexbugs-fixes: 5.0.2(postcss@8.5.24) + postcss-font-variant: 5.0.0(postcss@8.5.24) + postcss-initial: 4.0.1(postcss@8.5.24) + postcss-media-minmax: 5.0.0(postcss@8.5.24) + postcss-nesting: 12.1.5(postcss@8.5.24) + postcss-page-break: 3.0.4(postcss@8.5.24) rsbuild-plugin-rsc: 0.1.1(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(react-server-dom-rspack@0.0.2(@rspack/core@2.0.6(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) rspack-manifest-plugin: 5.2.2(@rspack/core@2.0.6(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.17)) ts-deepmerge: 7.0.3 @@ -34267,20 +34637,20 @@ snapshots: '@rsbuild/plugin-typed-css-modules': 1.2.3(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)) '@swc/core': 1.15.41(@swc/helpers@0.5.17) '@swc/helpers': 0.5.17 - autoprefixer: 10.4.27(postcss@8.5.23) + autoprefixer: 10.4.27(postcss@8.5.24) browserslist: 4.28.2 core-js: 3.49.0 - cssnano: 6.1.2(postcss@8.5.23) + cssnano: 6.1.2(postcss@8.5.24) html-minifier-terser: 7.2.0 lodash: 4.18.1 - postcss: 8.5.23 - postcss-custom-properties: 13.3.12(postcss@8.5.23) - postcss-flexbugs-fixes: 5.0.2(postcss@8.5.23) - postcss-font-variant: 5.0.0(postcss@8.5.23) - postcss-initial: 4.0.1(postcss@8.5.23) - postcss-media-minmax: 5.0.0(postcss@8.5.23) - postcss-nesting: 12.1.5(postcss@8.5.23) - postcss-page-break: 3.0.4(postcss@8.5.23) + postcss: 8.5.24 + postcss-custom-properties: 13.3.12(postcss@8.5.24) + postcss-flexbugs-fixes: 5.0.2(postcss@8.5.24) + postcss-font-variant: 5.0.0(postcss@8.5.24) + postcss-initial: 4.0.1(postcss@8.5.24) + postcss-media-minmax: 5.0.0(postcss@8.5.24) + postcss-nesting: 12.1.5(postcss@8.5.24) + postcss-page-break: 3.0.4(postcss@8.5.24) rsbuild-plugin-rsc: 0.1.1(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(react-server-dom-rspack@0.0.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) rspack-manifest-plugin: 5.2.2(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23)) ts-deepmerge: 7.0.3 @@ -34337,7 +34707,7 @@ snapshots: - react - react-dom - '@modern-js/module-tools@2.70.5(@types/node@26.2.0)(typescript@7.0.2)': + '@modern-js/module-tools@2.70.5(@types/node@26.1.0)(typescript@7.0.2)': dependencies: '@ampproject/remapping': 2.3.0 '@ast-grep/napi': 0.35.0 @@ -34345,7 +34715,7 @@ snapshots: '@babel/types': 7.29.0 '@modern-js/core': 2.70.5 '@modern-js/plugin': 2.70.5 - '@modern-js/plugin-changeset': 2.70.5(@types/node@26.2.0) + '@modern-js/plugin-changeset': 2.70.5(@types/node@26.1.0) '@modern-js/plugin-i18n': 2.70.5 '@modern-js/swc-plugins': 0.6.11(@swc/helpers@0.5.17) '@modern-js/types': 2.70.5 @@ -34419,9 +34789,9 @@ snapshots: '@swc/helpers': 0.5.17 esbuild: 0.25.5 - '@modern-js/plugin-changeset@2.70.5(@types/node@26.2.0)': + '@modern-js/plugin-changeset@2.70.5(@types/node@26.1.0)': dependencies: - '@changesets/cli': 2.30.0(@types/node@26.2.0) + '@changesets/cli': 2.30.0(@types/node@26.1.0) '@changesets/git': 2.0.0 '@changesets/read': 0.6.7 '@modern-js/plugin-i18n': 2.70.5 @@ -34493,10 +34863,10 @@ snapshots: '@modern-js/utils': 2.70.8 '@swc/helpers': 0.5.17 - '@modern-js/plugin-server@2.68.0(@babel/traverse@7.29.8)(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@modern-js/plugin-server@2.68.0(@babel/traverse@7.29.7)(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@modern-js/runtime-utils': 2.68.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@modern-js/server-utils': 2.68.0(@babel/traverse@7.29.8)(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)) + '@modern-js/server-utils': 2.68.0(@babel/traverse@7.29.7)(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)) '@modern-js/utils': 2.68.0 '@swc/helpers': 0.5.17 transitivePeerDependencies: @@ -34967,7 +35337,7 @@ snapshots: - react - react-dom - '@modern-js/server-utils@2.68.0(@babel/traverse@7.29.8)(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))': + '@modern-js/server-utils@2.68.0(@babel/traverse@7.29.7)(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))': dependencies: '@babel/core': 7.29.7 '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.7) @@ -34979,7 +35349,7 @@ snapshots: '@modern-js/babel-preset': 2.68.0(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)) '@modern-js/utils': 2.68.0 '@swc/helpers': 0.5.17 - babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.29.7)(@babel/traverse@7.29.8) + babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.29.7)(@babel/traverse@7.29.7) transitivePeerDependencies: - '@babel/traverse' - '@rsbuild/core' @@ -35029,7 +35399,7 @@ snapshots: - react - react-dom - '@modern-js/server@2.70.5(@babel/traverse@7.29.0)(@rsbuild/core@1.7.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.2.0)(typescript@7.0.2))(tsconfig-paths@4.2.0)': + '@modern-js/server@2.70.5(@babel/traverse@7.29.0)(@rsbuild/core@1.7.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.1.0)(typescript@7.0.2))(tsconfig-paths@4.2.0)': dependencies: '@babel/core': 7.29.7 '@babel/register': 7.28.6(@babel/core@7.29.7) @@ -35046,7 +35416,7 @@ snapshots: path-to-regexp: 6.3.0 ws: 8.21.0 optionalDependencies: - ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.2.0)(typescript@7.0.2) + ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.1.0)(typescript@7.0.2) tsconfig-paths: 4.2.0 transitivePeerDependencies: - '@babel/traverse' @@ -35087,7 +35457,7 @@ snapshots: - supports-color - utf-8-validate - '@modern-js/server@3.5.0(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.2.0)(typescript@7.0.2))(tsconfig-paths@4.2.0)': + '@modern-js/server@3.5.0(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.1.0)(typescript@7.0.2))(tsconfig-paths@4.2.0)': dependencies: '@modern-js/runtime-utils': 3.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@modern-js/server-core': 3.5.0(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -35102,7 +35472,7 @@ snapshots: path-to-regexp: 6.3.0 ws: 8.21.0 optionalDependencies: - ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.2.0)(typescript@7.0.2) + ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.1.0)(typescript@7.0.2) tsconfig-paths: 4.2.0 transitivePeerDependencies: - '@module-federation/runtime-tools' @@ -35349,13 +35719,13 @@ snapshots: '@rsbuild/webpack': 1.6.1(@rsbuild/core@1.7.3)(@rspack/core@1.7.9(@swc/helpers@0.5.17))(@swc/core@1.15.8(@swc/helpers@0.5.17))(esbuild@0.25.5)(webpack-cli@5.1.4) '@swc/core': 1.15.8(@swc/helpers@0.5.17) '@swc/helpers': 0.5.17 - autoprefixer: 10.4.23(postcss@8.5.23) + autoprefixer: 10.4.23(postcss@8.5.24) babel-loader: 9.2.1(@babel/core@7.29.7)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.25.5)(webpack-cli@5.1.4)) babel-plugin-import: 1.13.8 babel-plugin-styled-components: 1.13.3(styled-components@6.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)) babel-plugin-transform-react-remove-prop-types: 0.4.24 browserslist: 4.24.4 - cssnano: 6.1.2(postcss@8.5.23) + cssnano: 6.1.2(postcss@8.5.24) es-module-lexer: 1.5.3 glob: 9.3.5 html-minifier-terser: 7.2.0 @@ -35364,14 +35734,14 @@ snapshots: lodash: 4.18.1 magic-string: 0.30.21 picocolors: 1.1.1 - postcss: 8.5.23 - postcss-custom-properties: 13.3.12(postcss@8.5.23) - postcss-flexbugs-fixes: 5.0.2(postcss@8.5.23) - postcss-font-variant: 5.0.0(postcss@8.5.23) - postcss-initial: 4.0.1(postcss@8.5.23) - postcss-media-minmax: 5.0.0(postcss@8.5.23) - postcss-nesting: 12.1.5(postcss@8.5.23) - postcss-page-break: 3.0.4(postcss@8.5.23) + postcss: 8.5.24 + postcss-custom-properties: 13.3.12(postcss@8.5.24) + postcss-flexbugs-fixes: 5.0.2(postcss@8.5.24) + postcss-font-variant: 5.0.0(postcss@8.5.24) + postcss-initial: 4.0.1(postcss@8.5.24) + postcss-media-minmax: 5.0.0(postcss@8.5.24) + postcss-nesting: 12.1.5(postcss@8.5.24) + postcss-page-break: 3.0.4(postcss@8.5.24) react-refresh: 0.14.2 rspack-manifest-plugin: 5.0.3(@rspack/core@1.7.9(@swc/helpers@0.5.17)) terser-webpack-plugin: 5.3.14(@swc/core@1.15.8(@swc/helpers@0.5.17))(esbuild@0.25.5)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.25.5)(webpack-cli@5.1.4)) @@ -35429,13 +35799,13 @@ snapshots: '@rsbuild/webpack': 1.6.1(@rsbuild/core@1.7.3)(@rspack/core@1.7.9(@swc/helpers@0.5.19))(@swc/core@1.15.8(@swc/helpers@0.5.17))(esbuild@0.18.20)(webpack-cli@5.1.4) '@swc/core': 1.15.8(@swc/helpers@0.5.17) '@swc/helpers': 0.5.17 - autoprefixer: 10.4.23(postcss@8.5.23) + autoprefixer: 10.4.23(postcss@8.5.24) babel-loader: 9.2.1(@babel/core@7.29.0)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.19))(esbuild@0.18.20)(webpack-cli@5.1.4)) babel-plugin-import: 1.13.8 babel-plugin-styled-components: 1.13.3(styled-components@6.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) babel-plugin-transform-react-remove-prop-types: 0.4.24 browserslist: 4.24.4 - cssnano: 6.1.2(postcss@8.5.23) + cssnano: 6.1.2(postcss@8.5.24) es-module-lexer: 1.5.3 glob: 9.3.5 html-minifier-terser: 7.2.0 @@ -35444,14 +35814,14 @@ snapshots: lodash: 4.18.1 magic-string: 0.30.21 picocolors: 1.1.1 - postcss: 8.5.23 - postcss-custom-properties: 13.3.12(postcss@8.5.23) - postcss-flexbugs-fixes: 5.0.2(postcss@8.5.23) - postcss-font-variant: 5.0.0(postcss@8.5.23) - postcss-initial: 4.0.1(postcss@8.5.23) - postcss-media-minmax: 5.0.0(postcss@8.5.23) - postcss-nesting: 12.1.5(postcss@8.5.23) - postcss-page-break: 3.0.4(postcss@8.5.23) + postcss: 8.5.24 + postcss-custom-properties: 13.3.12(postcss@8.5.24) + postcss-flexbugs-fixes: 5.0.2(postcss@8.5.24) + postcss-font-variant: 5.0.0(postcss@8.5.24) + postcss-initial: 4.0.1(postcss@8.5.24) + postcss-media-minmax: 5.0.0(postcss@8.5.24) + postcss-nesting: 12.1.5(postcss@8.5.24) + postcss-page-break: 3.0.4(postcss@8.5.24) react-refresh: 0.14.2 rspack-manifest-plugin: 5.0.3(@rspack/core@1.7.9(@swc/helpers@0.5.19)) terser-webpack-plugin: 5.3.14(@swc/core@1.15.8(@swc/helpers@0.5.17))(esbuild@0.18.20)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.19))(esbuild@0.18.20)(webpack-cli@5.1.4)) @@ -35509,13 +35879,13 @@ snapshots: '@rsbuild/webpack': 1.6.1(@rsbuild/core@1.7.3)(@rspack/core@1.7.9(@swc/helpers@0.5.19))(@swc/core@1.15.8(@swc/helpers@0.5.17))(esbuild@0.25.5)(webpack-cli@5.1.4) '@swc/core': 1.15.8(@swc/helpers@0.5.17) '@swc/helpers': 0.5.17 - autoprefixer: 10.4.23(postcss@8.5.23) + autoprefixer: 10.4.23(postcss@8.5.24) babel-loader: 9.2.1(@babel/core@7.29.0)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.19))(esbuild@0.18.20)(webpack-cli@5.1.4)) babel-plugin-import: 1.13.8 babel-plugin-styled-components: 1.13.3(styled-components@6.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) babel-plugin-transform-react-remove-prop-types: 0.4.24 browserslist: 4.24.4 - cssnano: 6.1.2(postcss@8.5.23) + cssnano: 6.1.2(postcss@8.5.24) es-module-lexer: 1.5.3 glob: 9.3.5 html-minifier-terser: 7.2.0 @@ -35524,14 +35894,14 @@ snapshots: lodash: 4.18.1 magic-string: 0.30.21 picocolors: 1.1.1 - postcss: 8.5.23 - postcss-custom-properties: 13.3.12(postcss@8.5.23) - postcss-flexbugs-fixes: 5.0.2(postcss@8.5.23) - postcss-font-variant: 5.0.0(postcss@8.5.23) - postcss-initial: 4.0.1(postcss@8.5.23) - postcss-media-minmax: 5.0.0(postcss@8.5.23) - postcss-nesting: 12.1.5(postcss@8.5.23) - postcss-page-break: 3.0.4(postcss@8.5.23) + postcss: 8.5.24 + postcss-custom-properties: 13.3.12(postcss@8.5.24) + postcss-flexbugs-fixes: 5.0.2(postcss@8.5.24) + postcss-font-variant: 5.0.0(postcss@8.5.24) + postcss-initial: 4.0.1(postcss@8.5.24) + postcss-media-minmax: 5.0.0(postcss@8.5.24) + postcss-nesting: 12.1.5(postcss@8.5.24) + postcss-page-break: 3.0.4(postcss@8.5.24) react-refresh: 0.14.2 rspack-manifest-plugin: 5.0.3(@rspack/core@1.7.9(@swc/helpers@0.5.19)) terser-webpack-plugin: 5.3.14(@swc/core@1.15.8(@swc/helpers@0.5.17))(esbuild@0.25.5)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.19))(esbuild@0.18.20)(webpack-cli@5.1.4)) @@ -35680,7 +36050,7 @@ snapshots: '@module-federation/managers': 0.21.6 '@module-federation/sdk': 0.21.6 '@module-federation/third-party-dts-extractor': 0.21.6 - adm-zip: 0.5.18 + adm-zip: 0.5.16 ansi-colors: 4.1.3 axios: 1.18.0 chalk: 3.0.0 @@ -35707,7 +36077,7 @@ snapshots: '@module-federation/managers': 2.2.2(node-fetch@2.7.0(encoding@0.1.13)) '@module-federation/sdk': 2.2.2(node-fetch@2.7.0(encoding@0.1.13)) '@module-federation/third-party-dts-extractor': 2.2.2 - adm-zip: 0.5.18 + adm-zip: 0.5.16 ansi-colors: 4.1.3 axios: 1.18.0 chalk: 3.0.0 @@ -36317,20 +36687,20 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.9.0 + '@emnapi/core': 1.11.2 '@emnapi/runtime': 1.11.3 '@tybys/wasm-util': 0.10.1 optional: true '@napi-rs/wasm-runtime@0.2.4': dependencies: - '@emnapi/core': 1.11.1 + '@emnapi/core': 1.11.3 '@emnapi/runtime': 1.11.3 '@tybys/wasm-util': 0.9.0 '@napi-rs/wasm-runtime@1.0.7': dependencies: - '@emnapi/core': 1.11.1 + '@emnapi/core': 1.11.2 '@emnapi/runtime': 1.11.3 '@tybys/wasm-util': 0.10.3 optional: true @@ -36349,6 +36719,13 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@tybys/wasm-util': 0.10.3 + optional: true + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': dependencies: '@emnapi/core': 1.11.3 @@ -36452,10 +36829,10 @@ snapshots: tslib: 2.8.1 yargs-parser: 21.1.1 - '@nx/eslint@22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@zkochan/js-yaml@0.0.7)(eslint@9.39.3(jiti@2.7.0))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))': + '@nx/eslint@22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@zkochan/js-yaml@0.0.7)(eslint@9.39.3(jiti@2.7.0))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))': dependencies: '@nx/devkit': 22.5.4(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) - '@nx/js': 22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) + '@nx/js': 22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) eslint: 9.39.3(jiti@2.7.0) semver: 7.8.5 tslib: 2.8.1 @@ -36471,7 +36848,7 @@ snapshots: - supports-color - verdaccio - '@nx/js@22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))': + '@nx/js@22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))': dependencies: '@babel/core': 7.29.7 '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.7) @@ -36485,7 +36862,7 @@ snapshots: '@zkochan/js-yaml': 0.0.7 babel-plugin-const-enum: 1.2.0(@babel/core@7.29.7) babel-plugin-macros: 3.1.0 - babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.29.7)(@babel/traverse@7.29.8) + babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.29.7)(@babel/traverse@7.29.7) chalk: 4.1.2 columnify: 1.6.0 detect-port: 1.6.1 @@ -36507,14 +36884,14 @@ snapshots: - nx - supports-color - '@nx/module-federation@22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@swc/helpers@0.5.23)(esbuild@0.25.5)(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(vue-tsc@2.2.12(typescript@7.0.2))(webpack-cli@5.1.4)': + '@nx/module-federation@22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@swc/helpers@0.5.23)(esbuild@0.25.5)(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(vue-tsc@2.2.12(typescript@7.0.2))(webpack-cli@5.1.4)': dependencies: '@module-federation/enhanced': 0.21.6(@rspack/core@1.6.8(@swc/helpers@0.5.23))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(vue-tsc@2.2.12(typescript@7.0.2))(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(webpack-cli@5.1.4)) '@module-federation/node': 2.7.36(@rspack/core@1.6.8(@swc/helpers@0.5.23))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(vue-tsc@2.2.12(typescript@7.0.2))(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(webpack-cli@5.1.4)) '@module-federation/sdk': 0.21.6 '@nx/devkit': 22.5.4(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) - '@nx/js': 22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) - '@nx/web': 22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) + '@nx/js': 22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) + '@nx/web': 22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) '@rspack/core': 1.6.8(@swc/helpers@0.5.23) express: 4.22.1 http-proxy-middleware: 3.0.5 @@ -36570,14 +36947,14 @@ snapshots: '@nx/nx-win32-x64-msvc@22.5.4': optional: true - '@nx/react@22.5.4(@babel/core@7.29.7)(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@swc/helpers@0.5.23)(@types/babel__core@7.20.5)(@zkochan/js-yaml@0.0.7)(esbuild@0.25.5)(eslint@9.39.3(jiti@2.7.0))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(vite@7.3.6(@types/node@26.2.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))(vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.2.0)(jiti@2.7.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))(vue-tsc@2.2.12(typescript@7.0.2))(webpack-cli@5.1.4)': + '@nx/react@22.5.4(@babel/core@7.29.7)(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@swc/helpers@0.5.23)(@types/babel__core@7.20.5)(@zkochan/js-yaml@0.0.7)(esbuild@0.25.5)(eslint@9.39.3(jiti@2.7.0))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(vite@7.3.5(@types/node@26.1.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))(vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.1.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0))(vue-tsc@2.2.12(typescript@7.0.2))(webpack-cli@5.1.4)': dependencies: '@nx/devkit': 22.5.4(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) - '@nx/eslint': 22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@zkochan/js-yaml@0.0.7)(eslint@9.39.3(jiti@2.7.0))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) - '@nx/js': 22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) - '@nx/module-federation': 22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@swc/helpers@0.5.23)(esbuild@0.25.5)(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(vue-tsc@2.2.12(typescript@7.0.2))(webpack-cli@5.1.4) - '@nx/rollup': 22.5.4(@babel/core@7.29.7)(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/babel__core@7.20.5)(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(typescript@7.0.2) - '@nx/web': 22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) + '@nx/eslint': 22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@zkochan/js-yaml@0.0.7)(eslint@9.39.3(jiti@2.7.0))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) + '@nx/js': 22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) + '@nx/module-federation': 22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@swc/helpers@0.5.23)(esbuild@0.25.5)(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2)(vue-tsc@2.2.12(typescript@7.0.2))(webpack-cli@5.1.4) + '@nx/rollup': 22.5.4(@babel/core@7.29.7)(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/babel__core@7.20.5)(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(typescript@7.0.2) + '@nx/web': 22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) '@phenomnomnominal/tsquery': 6.1.4(typescript@7.0.2) '@svgr/webpack': 8.1.0(typescript@7.0.2) express: 4.22.1 @@ -36587,7 +36964,7 @@ snapshots: semver: 7.6.3 tslib: 2.8.1 optionalDependencies: - '@nx/vite': 22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(typescript@7.0.2)(vite@7.3.6(@types/node@26.2.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))(vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.2.0)(jiti@2.7.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0)) + '@nx/vite': 22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(typescript@7.0.2)(vite@7.3.5(@types/node@26.1.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))(vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.1.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)) transitivePeerDependencies: - '@babel/core' - '@babel/traverse' @@ -36613,22 +36990,22 @@ snapshots: - vue-tsc - webpack-cli - '@nx/rollup@22.5.4(@babel/core@7.29.7)(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/babel__core@7.20.5)(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(typescript@7.0.2)': + '@nx/rollup@22.5.4(@babel/core@7.29.7)(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/babel__core@7.20.5)(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(typescript@7.0.2)': dependencies: '@nx/devkit': 22.5.4(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) - '@nx/js': 22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) + '@nx/js': 22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) '@rollup/plugin-babel': 6.1.0(@babel/core@7.29.7)(@types/babel__core@7.20.5)(rollup@4.59.0) '@rollup/plugin-commonjs': 25.0.8(rollup@4.59.0) '@rollup/plugin-image': 3.0.3(rollup@4.59.0) '@rollup/plugin-json': 6.1.0(rollup@4.59.0) '@rollup/plugin-node-resolve': 15.3.1(rollup@4.59.0) '@rollup/plugin-typescript': 12.3.0(rollup@4.59.0)(tslib@2.8.1)(typescript@7.0.2) - autoprefixer: 10.4.20(postcss@8.5.23) + autoprefixer: 10.4.20(postcss@8.5.24) concat-with-sourcemaps: 1.1.0 picocolors: 1.1.1 picomatch: 4.0.2 - postcss: 8.5.23 - postcss-modules: 6.0.1(postcss@8.5.23) + postcss: 8.5.24 + postcss-modules: 6.0.1(postcss@8.5.24) rollup: 4.59.0 rollup-plugin-typescript2: 0.36.0(rollup@4.59.0)(typescript@7.0.2) tslib: 2.8.1 @@ -36644,11 +37021,11 @@ snapshots: - typescript - verdaccio - '@nx/vite@22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(typescript@7.0.2)(vite@7.3.6(@types/node@26.2.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))(vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.2.0)(jiti@2.7.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))': + '@nx/vite@22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(typescript@7.0.2)(vite@7.3.5(@types/node@26.1.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))(vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.1.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0))': dependencies: '@nx/devkit': 22.5.4(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) - '@nx/js': 22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) - '@nx/vitest': 22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(typescript@7.0.2)(vite@7.3.6(@types/node@26.2.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))(vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.2.0)(jiti@2.7.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0)) + '@nx/js': 22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) + '@nx/vitest': 22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(typescript@7.0.2)(vite@7.3.5(@types/node@26.1.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))(vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.1.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)) '@phenomnomnominal/tsquery': 6.1.4(typescript@7.0.2) ajv: 8.18.0 enquirer: 2.3.6 @@ -36656,8 +37033,8 @@ snapshots: semver: 7.8.5 tsconfig-paths: 4.2.0 tslib: 2.8.1 - vite: 7.3.6(@types/node@26.2.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0) - vitest: 3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.2.0)(jiti@2.7.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0) + vite: 7.3.5(@types/node@26.1.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0) + vitest: 3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.1.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0) transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -36669,16 +37046,16 @@ snapshots: - verdaccio optional: true - '@nx/vitest@22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(typescript@7.0.2)(vite@7.3.6(@types/node@26.2.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))(vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.2.0)(jiti@2.7.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))': + '@nx/vitest@22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(typescript@7.0.2)(vite@7.3.5(@types/node@26.1.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))(vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.1.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0))': dependencies: '@nx/devkit': 22.5.4(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) - '@nx/js': 22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) + '@nx/js': 22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) '@phenomnomnominal/tsquery': 6.1.4(typescript@7.0.2) semver: 7.8.5 tslib: 2.8.1 optionalDependencies: - vite: 7.3.6(@types/node@26.2.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0) - vitest: 3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.2.0)(jiti@2.7.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0) + vite: 7.3.5(@types/node@26.1.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0) + vitest: 3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.1.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0) transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -36690,10 +37067,10 @@ snapshots: - verdaccio optional: true - '@nx/web@22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))': + '@nx/web@22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))': dependencies: '@nx/devkit': 22.5.4(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) - '@nx/js': 22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) + '@nx/js': 22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) detect-port: 1.6.1 http-server: 14.1.1 picocolors: 1.1.1 @@ -36707,11 +37084,11 @@ snapshots: - supports-color - verdaccio - '@nx/webpack@22.5.4(@babel/traverse@7.29.8)(@rspack/core@1.6.8(@swc/helpers@0.5.23))(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(html-webpack-plugin@5.6.6(@rspack/core@1.6.8(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(webpack-cli@5.1.4)))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(typescript@7.0.2)(vue-template-compiler@2.7.16)(webpack-cli@5.1.4)': + '@nx/webpack@22.5.4(@babel/traverse@7.29.7)(@rspack/core@1.6.8(@swc/helpers@0.5.23))(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(html-webpack-plugin@5.6.6(@rspack/core@1.6.8(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(webpack-cli@5.1.4)))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23)))(typescript@7.0.2)(vue-template-compiler@2.7.16)(webpack-cli@5.1.4)': dependencies: '@babel/core': 7.29.0 '@nx/devkit': 22.5.4(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) - '@nx/js': 22.5.4(@babel/traverse@7.29.8)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) + '@nx/js': 22.5.4(@babel/traverse@7.29.7)(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))(nx@22.5.4(@swc-node/register@1.10.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(@swc/types@0.1.27)(typescript@6.0.3))(@swc/core@1.15.41(@swc/helpers@0.5.23))) '@phenomnomnominal/tsquery': 6.1.4(typescript@7.0.2) ajv: 8.18.0 autoprefixer: 10.4.20(postcss@8.4.49) @@ -37063,8 +37440,8 @@ snapshots: '@puppeteer/browsers@3.0.6(yauzl@2.10.0)': dependencies: - modern-tar: 0.7.7 - yargs: 18.1.0 + modern-tar: 0.7.6 + yargs: 18.0.0 optionalDependencies: yauzl: 2.10.0 @@ -38871,7 +39248,7 @@ snapshots: execa: 5.1.1 node-stream-zip: 1.15.0 ora: 5.4.1 - semver: 7.8.5 + semver: 7.6.3 wcwidth: 1.0.1 yaml: 2.8.2 transitivePeerDependencies: @@ -38892,7 +39269,7 @@ snapshots: execa: 5.1.1 node-stream-zip: 1.15.0 ora: 5.4.1 - semver: 7.8.5 + semver: 7.6.3 wcwidth: 1.0.1 yaml: 2.8.2 transitivePeerDependencies: @@ -38964,7 +39341,7 @@ snapshots: mime: 2.6.0 ora: 5.4.1 prompts: 2.4.2 - semver: 7.8.5 + semver: 7.6.3 '@react-native-community/cli-tools@20.1.3': dependencies: @@ -38977,7 +39354,7 @@ snapshots: ora: 5.4.1 picocolors: 1.1.1 prompts: 2.4.2 - semver: 7.8.5 + semver: 7.6.3 '@react-native-community/cli-types@19.1.2': dependencies: @@ -39121,7 +39498,7 @@ snapshots: metro: 0.82.5 metro-config: 0.82.5 metro-core: 0.82.5 - semver: 7.8.5 + semver: 7.6.3 optionalDependencies: '@react-native-community/cli': 19.1.2(typescript@6.0.3) transitivePeerDependencies: @@ -39138,7 +39515,7 @@ snapshots: metro: 0.82.5 metro-config: 0.82.5 metro-core: 0.82.5 - semver: 7.8.5 + semver: 7.6.3 optionalDependencies: '@react-native-community/cli': 19.1.2(typescript@7.0.2) transitivePeerDependencies: @@ -39187,7 +39564,7 @@ snapshots: - supports-color - typescript - '@react-native/eslint-config@0.80.0(eslint@8.57.1)(jest@29.7.0(@types/node@26.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)))(prettier@2.8.8)(typescript@7.0.2)': + '@react-native/eslint-config@0.80.0(eslint@8.57.1)(jest@29.7.0(@types/node@26.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)))(prettier@2.8.8)(typescript@7.0.2)': dependencies: '@babel/core': 7.29.0 '@babel/eslint-parser': 7.28.6(@babel/core@7.29.0)(eslint@8.57.1) @@ -39198,7 +39575,7 @@ snapshots: eslint-config-prettier: 8.10.2(eslint@8.57.1) eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) eslint-plugin-ft-flow: 2.0.3(@babel/eslint-parser@7.28.6(@babel/core@7.29.0)(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@7.0.2))(eslint@8.57.1)(typescript@7.0.2))(eslint@8.57.1)(jest@29.7.0(@types/node@26.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)))(typescript@7.0.2) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@7.0.2))(eslint@8.57.1)(typescript@7.0.2))(eslint@8.57.1)(jest@29.7.0(@types/node@26.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)))(typescript@7.0.2) eslint-plugin-react: 7.37.2(eslint@8.57.1) eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1) eslint-plugin-react-native: 4.1.0(eslint@8.57.1) @@ -39368,7 +39745,7 @@ snapshots: '@clack/prompts': 0.10.1 '@expo/fingerprint': 0.11.11 '@types/adm-zip': 0.5.8 - adm-zip: 0.5.18 + adm-zip: 0.5.16 appdirsjs: 1.2.7 fast-glob: 3.3.3 is-unicode-supported: 2.1.0 @@ -39407,7 +39784,7 @@ snapshots: '@react-native-community/cli-config': 20.1.3(typescript@7.0.2) '@react-native-community/cli-config-apple': 20.1.3 '@rock-js/tools': 0.14.1 - adm-zip: 0.5.18 + adm-zip: 0.5.16 fast-xml-parser: 4.5.4 tslib: 2.8.1 transitivePeerDependencies: @@ -39452,7 +39829,7 @@ snapshots: '@rock-js/tools@0.13.0': dependencies: '@clack/prompts': 0.11.0 - adm-zip: 0.5.18 + adm-zip: 0.5.16 appdirsjs: 1.2.7 fs-fingerprint: 0.11.0 is-unicode-supported: 2.1.0 @@ -39465,7 +39842,7 @@ snapshots: '@rock-js/tools@0.14.1': dependencies: '@clack/prompts': 0.11.0 - adm-zip: 0.5.18 + adm-zip: 0.5.16 appdirsjs: 1.2.7 fs-fingerprint: 0.11.0 is-unicode-supported: 2.1.0 @@ -39575,9 +39952,9 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.9': {} - '@rollup/plugin-alias@5.1.1(rollup@4.62.2)': + '@rollup/plugin-alias@5.1.1(rollup@4.59.0)': optionalDependencies: - rollup: 4.62.2 + rollup: 4.59.0 '@rollup/plugin-babel@6.1.0(@babel/core@7.29.7)(@types/babel__core@7.20.5)(rollup@4.59.0)': dependencies: @@ -39624,12 +40001,12 @@ snapshots: optionalDependencies: rollup: 4.59.0 - '@rollup/plugin-replace@6.0.1(rollup@4.62.2)': + '@rollup/plugin-replace@6.0.1(rollup@4.59.0)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.62.2) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) magic-string: 0.30.21 optionalDependencies: - rollup: 4.62.2 + rollup: 4.59.0 '@rollup/plugin-typescript@12.3.0(rollup@4.59.0)(tslib@2.8.1)(typescript@7.0.2)': dependencies: @@ -39653,164 +40030,81 @@ snapshots: optionalDependencies: rollup: 4.59.0 - '@rollup/pluginutils@5.3.0(rollup@4.62.2)': - dependencies: - '@types/estree': 1.0.8 - estree-walker: 2.0.2 - picomatch: 4.0.3 - optionalDependencies: - rollup: 4.62.2 - '@rollup/rollup-android-arm-eabi@4.59.0': optional: true - '@rollup/rollup-android-arm-eabi@4.62.2': - optional: true - '@rollup/rollup-android-arm64@4.59.0': optional: true - '@rollup/rollup-android-arm64@4.62.2': - optional: true - '@rollup/rollup-darwin-arm64@4.59.0': optional: true - '@rollup/rollup-darwin-arm64@4.62.2': - optional: true - '@rollup/rollup-darwin-x64@4.59.0': optional: true - '@rollup/rollup-darwin-x64@4.62.2': - optional: true - '@rollup/rollup-freebsd-arm64@4.59.0': optional: true - '@rollup/rollup-freebsd-arm64@4.62.2': - optional: true - '@rollup/rollup-freebsd-x64@4.59.0': optional: true - '@rollup/rollup-freebsd-x64@4.62.2': - optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.62.2': - optional: true - '@rollup/rollup-linux-arm-musleabihf@4.59.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.62.2': - optional: true - '@rollup/rollup-linux-arm64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.62.2': - optional: true - '@rollup/rollup-linux-arm64-musl@4.59.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.62.2': - optional: true - '@rollup/rollup-linux-loong64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-loong64-gnu@4.62.2': - optional: true - '@rollup/rollup-linux-loong64-musl@4.59.0': optional: true - '@rollup/rollup-linux-loong64-musl@4.62.2': - optional: true - '@rollup/rollup-linux-ppc64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.62.2': - optional: true - '@rollup/rollup-linux-ppc64-musl@4.59.0': optional: true - '@rollup/rollup-linux-ppc64-musl@4.62.2': - optional: true - '@rollup/rollup-linux-riscv64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.62.2': - optional: true - '@rollup/rollup-linux-riscv64-musl@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.62.2': - optional: true - '@rollup/rollup-linux-s390x-gnu@4.59.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.62.2': - optional: true - '@rollup/rollup-linux-x64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.62.2': - optional: true - '@rollup/rollup-linux-x64-musl@4.59.0': optional: true - '@rollup/rollup-linux-x64-musl@4.62.2': - optional: true - '@rollup/rollup-openbsd-x64@4.59.0': optional: true - '@rollup/rollup-openbsd-x64@4.62.2': - optional: true - '@rollup/rollup-openharmony-arm64@4.59.0': optional: true - '@rollup/rollup-openharmony-arm64@4.62.2': - optional: true - '@rollup/rollup-win32-arm64-msvc@4.59.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.62.2': - optional: true - '@rollup/rollup-win32-ia32-msvc@4.59.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.62.2': - optional: true - '@rollup/rollup-win32-x64-gnu@4.59.0': optional: true - '@rollup/rollup-win32-x64-gnu@4.62.2': - optional: true - '@rollup/rollup-win32-x64-msvc@4.59.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.62.2': - optional: true - '@rsbuild/core@1.0.1-rc.4': dependencies: '@rspack/core': 1.0.14(@swc/helpers@0.5.17) @@ -39892,6 +40186,15 @@ snapshots: transitivePeerDependencies: - '@module-federation/runtime-tools' + '@rsbuild/core@2.0.15(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0)': + dependencies: + '@rspack/core': 2.0.8(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23) + '@swc/helpers': 0.5.23 + optionalDependencies: + core-js: 3.49.0 + transitivePeerDependencies: + - '@module-federation/runtime-tools' + '@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)': dependencies: '@rspack/core': 2.1.2(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23) @@ -39937,6 +40240,15 @@ snapshots: transitivePeerDependencies: - '@module-federation/runtime-tools' + '@rsbuild/core@2.1.4(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0)': + dependencies: + '@rspack/core': 2.1.2(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23) + '@swc/helpers': 0.5.23 + optionalDependencies: + core-js: 3.49.0 + transitivePeerDependencies: + - '@module-federation/runtime-tools' + '@rsbuild/plugin-assets-retry@1.5.2(@rsbuild/core@1.7.3)': optionalDependencies: '@rsbuild/core': 1.7.3 @@ -40026,6 +40338,16 @@ snapshots: optionalDependencies: '@rsbuild/core': 1.7.3 + '@rsbuild/plugin-check-syntax@1.6.1(@rsbuild/core@2.1.4(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0))': + dependencies: + acorn: 8.17.0 + browserslist-to-es-version: 1.4.1 + htmlparser2: 10.0.0 + picocolors: 1.1.1 + source-map: 0.7.6 + optionalDependencies: + '@rsbuild/core': 2.1.4(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0) + '@rsbuild/plugin-check-syntax@2.0.0(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))': dependencies: acorn: 8.17.0 @@ -40081,6 +40403,21 @@ snapshots: - lightningcss - webpack + '@rsbuild/plugin-css-minimizer@2.0.0(@rsbuild/core@2.1.4(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0))(esbuild@0.28.1)(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4))': + dependencies: + css-minimizer-webpack-plugin: 8.0.0(esbuild@0.28.1)(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + reduce-configs: 1.1.2 + optionalDependencies: + '@rsbuild/core': 2.1.4(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0) + transitivePeerDependencies: + - '@parcel/css' + - '@swc/css' + - clean-css + - csso + - esbuild + - lightningcss + - webpack + '@rsbuild/plugin-css-minimizer@2.0.1(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(esbuild@0.28.1)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.28.1)(webpack-cli@5.1.4))': dependencies: css-minimizer-webpack-plugin: 8.0.0(esbuild@0.28.1)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.28.1)(webpack-cli@5.1.4)) @@ -40338,7 +40675,7 @@ snapshots: '@rsbuild/core': 1.7.3 deepmerge: 4.3.1 loader-utils: 2.0.4 - postcss: 8.5.23 + postcss: 8.5.24 reduce-configs: 1.1.2 sass-embedded: 1.100.0 @@ -40346,7 +40683,7 @@ snapshots: dependencies: deepmerge: 4.3.1 loader-utils: 2.0.4 - postcss: 8.5.23 + postcss: 8.5.24 reduce-configs: 1.1.2 sass-embedded: 1.100.0 optionalDependencies: @@ -40595,6 +40932,109 @@ snapshots: - uglify-js - webpack-cli + '@rsdoctor/client@1.5.18': {} + + '@rsdoctor/core@1.5.18(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@rsbuild/core@2.1.4(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0))(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4))': + dependencies: + '@rsbuild/plugin-check-syntax': 1.6.1(@rsbuild/core@2.1.4(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0)) + '@rsdoctor/graph': 1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + '@rsdoctor/sdk': 1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + '@rsdoctor/types': 1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + '@rsdoctor/utils': 1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + '@rspack/resolver': 0.2.8(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + browserslist-load-config: 1.0.3 + es-toolkit: 1.49.0 + filesize: 11.0.22 + fs-extra: 11.3.0 + semver: 7.8.5 + source-map: 0.7.6 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + - '@rsbuild/core' + - '@rspack/core' + - bufferutil + - supports-color + - utf-8-validate + - webpack + + '@rsdoctor/graph@1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4))': + dependencies: + '@rsdoctor/types': 1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + '@rsdoctor/utils': 1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + es-toolkit: 1.49.0 + path-browserify: 1.0.1 + source-map: 0.7.6 + transitivePeerDependencies: + - '@rspack/core' + - webpack + + '@rsdoctor/rspack-plugin@1.5.18(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@rsbuild/core@2.1.4(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0))(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4))': + dependencies: + '@rsdoctor/core': 1.5.18(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@rsbuild/core@2.1.4(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0))(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + '@rsdoctor/graph': 1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + '@rsdoctor/sdk': 1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + '@rsdoctor/types': 1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + '@rsdoctor/utils': 1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + optionalDependencies: + '@rspack/core': '@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23)' + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + - '@rsbuild/core' + - bufferutil + - supports-color + - utf-8-validate + - webpack + + '@rsdoctor/sdk@1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4))': + dependencies: + '@rsdoctor/client': 1.5.18 + '@rsdoctor/graph': 1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + '@rsdoctor/types': 1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + '@rsdoctor/utils': 1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + launch-editor: 2.14.1 + safer-buffer: 2.1.2 + socket.io: 4.8.1 + tapable: 2.3.3 + transitivePeerDependencies: + - '@rspack/core' + - bufferutil + - supports-color + - utf-8-validate + - webpack + + '@rsdoctor/types@1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4))': + dependencies: + '@types/connect': 3.4.38 + '@types/estree': 1.0.5 + '@types/tapable': 2.3.0 + source-map: 0.7.6 + optionalDependencies: + '@rspack/core': '@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23)' + webpack: 5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4) + + '@rsdoctor/utils@1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4))': + dependencies: + '@babel/code-frame': 7.26.2 + '@rsdoctor/types': 1.5.18(@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)) + '@types/estree': 1.0.5 + acorn: 8.17.0 + acorn-import-attributes: 1.9.5(acorn@8.17.0) + acorn-walk: 8.3.5 + deep-eql: 4.1.4 + envinfo: 7.21.0 + fs-extra: 11.3.0 + get-port: 5.1.1 + json-stream-stringify: 3.0.1 + lines-and-columns: 2.0.4 + picocolors: 1.1.1 + rslog: 2.3.0 + strip-ansi: 7.2.0 + transitivePeerDependencies: + - '@rspack/core' + - webpack + '@rslib/core@0.12.4(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@microsoft/api-extractor@7.57.7(@types/node@20.19.5))(typescript@7.0.2)': dependencies: '@rsbuild/core': 1.5.17(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) @@ -40643,39 +41083,101 @@ snapshots: - '@typescript/native-preview' - core-js - '@rslib/core@0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@6.0.3)': + '@rslib/core@0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@6.0.3)': dependencies: '@rsbuild/core': 2.1.4(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0) - rsbuild-plugin-dts: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@rsbuild/core@2.1.4(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(typescript@6.0.3) + rsbuild-plugin-dts: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@rsbuild/core@2.1.4(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(typescript@6.0.3) optionalDependencies: - '@microsoft/api-extractor': 7.57.7(@types/node@26.2.0) + '@microsoft/api-extractor': 7.57.7(@types/node@26.1.0) typescript: 6.0.3 transitivePeerDependencies: - '@module-federation/runtime-tools' - '@typescript/native-preview' - core-js - '@rslib/core@0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2)': + '@rslib/core@0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2)': dependencies: '@rsbuild/core': 2.1.4(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0) - rsbuild-plugin-dts: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@rsbuild/core@2.1.4(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(typescript@7.0.2) + rsbuild-plugin-dts: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@rsbuild/core@2.1.4(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(typescript@7.0.2) optionalDependencies: - '@microsoft/api-extractor': 7.57.7(@types/node@26.2.0) + '@microsoft/api-extractor': 7.57.7(@types/node@26.1.0) typescript: 7.0.2 transitivePeerDependencies: - '@module-federation/runtime-tools' - '@typescript/native-preview' - core-js - '@rslib/core@0.9.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(typescript@5.9.3)': + '@rslib/core@0.9.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(typescript@5.9.3)': dependencies: '@rsbuild/core': 1.4.0-beta.2 - rsbuild-plugin-dts: 0.9.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@rsbuild/core@1.4.0-beta.2)(typescript@5.9.3) + rsbuild-plugin-dts: 0.9.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@rsbuild/core@1.4.0-beta.2)(typescript@5.9.3) tinyglobby: 0.2.15 optionalDependencies: - '@microsoft/api-extractor': 7.57.7(@types/node@26.2.0) + '@microsoft/api-extractor': 7.57.7(@types/node@26.1.0) typescript: 5.9.3 + '@rspack-canary/binding-darwin-arm64@2.1.5-canary-54a0d8f3-20260715194831': + optional: true + + '@rspack-canary/binding-darwin-x64@2.1.5-canary-54a0d8f3-20260715194831': + optional: true + + '@rspack-canary/binding-linux-arm64-gnu@2.1.5-canary-54a0d8f3-20260715194831': + optional: true + + '@rspack-canary/binding-linux-arm64-musl@2.1.5-canary-54a0d8f3-20260715194831': + optional: true + + '@rspack-canary/binding-linux-riscv64-gnu@2.1.5-canary-54a0d8f3-20260715194831': + optional: true + + '@rspack-canary/binding-linux-riscv64-musl@2.1.5-canary-54a0d8f3-20260715194831': + optional: true + + '@rspack-canary/binding-linux-x64-gnu@2.1.5-canary-54a0d8f3-20260715194831': + optional: true + + '@rspack-canary/binding-linux-x64-musl@2.1.5-canary-54a0d8f3-20260715194831': + optional: true + + '@rspack-canary/binding-wasm32-wasi@2.1.5-canary-54a0d8f3-20260715194831': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + optional: true + + '@rspack-canary/binding-win32-arm64-msvc@2.1.5-canary-54a0d8f3-20260715194831': + optional: true + + '@rspack-canary/binding-win32-ia32-msvc@2.1.5-canary-54a0d8f3-20260715194831': + optional: true + + '@rspack-canary/binding-win32-x64-msvc@2.1.5-canary-54a0d8f3-20260715194831': + optional: true + + '@rspack-canary/binding@2.1.5-canary-54a0d8f3-20260715194831': + optionalDependencies: + '@rspack/binding-darwin-arm64': '@rspack-canary/binding-darwin-arm64@2.1.5-canary-54a0d8f3-20260715194831' + '@rspack/binding-darwin-x64': '@rspack-canary/binding-darwin-x64@2.1.5-canary-54a0d8f3-20260715194831' + '@rspack/binding-linux-arm64-gnu': '@rspack-canary/binding-linux-arm64-gnu@2.1.5-canary-54a0d8f3-20260715194831' + '@rspack/binding-linux-arm64-musl': '@rspack-canary/binding-linux-arm64-musl@2.1.5-canary-54a0d8f3-20260715194831' + '@rspack/binding-linux-riscv64-gnu': '@rspack-canary/binding-linux-riscv64-gnu@2.1.5-canary-54a0d8f3-20260715194831' + '@rspack/binding-linux-riscv64-musl': '@rspack-canary/binding-linux-riscv64-musl@2.1.5-canary-54a0d8f3-20260715194831' + '@rspack/binding-linux-x64-gnu': '@rspack-canary/binding-linux-x64-gnu@2.1.5-canary-54a0d8f3-20260715194831' + '@rspack/binding-linux-x64-musl': '@rspack-canary/binding-linux-x64-musl@2.1.5-canary-54a0d8f3-20260715194831' + '@rspack/binding-wasm32-wasi': '@rspack-canary/binding-wasm32-wasi@2.1.5-canary-54a0d8f3-20260715194831' + '@rspack/binding-win32-arm64-msvc': '@rspack-canary/binding-win32-arm64-msvc@2.1.5-canary-54a0d8f3-20260715194831' + '@rspack/binding-win32-ia32-msvc': '@rspack-canary/binding-win32-ia32-msvc@2.1.5-canary-54a0d8f3-20260715194831' + '@rspack/binding-win32-x64-msvc': '@rspack-canary/binding-win32-x64-msvc@2.1.5-canary-54a0d8f3-20260715194831' + + '@rspack-canary/core@2.1.5-canary-54a0d8f3-20260715194831(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23)': + dependencies: + '@rspack/binding': '@rspack-canary/binding@2.1.5-canary-54a0d8f3-20260715194831' + optionalDependencies: + '@module-federation/runtime-tools': link:packages/runtime-tools + '@swc/helpers': 0.5.23 + '@rspack/binding-darwin-arm64@0.7.5': optional: true @@ -41455,6 +41957,13 @@ snapshots: '@module-federation/runtime-tools': 2.8.2 '@swc/helpers': 0.5.23 + '@rspack/core@2.0.8(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23)': + dependencies: + '@rspack/binding': 2.0.8 + optionalDependencies: + '@module-federation/runtime-tools': link:packages/runtime-tools + '@swc/helpers': 0.5.23 + '@rspack/core@2.1.2(@module-federation/runtime-tools@2.2.2(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)': dependencies: '@rspack/binding': 2.1.2 @@ -41469,6 +41978,13 @@ snapshots: '@module-federation/runtime-tools': 2.8.2 '@swc/helpers': 0.5.23 + '@rspack/core@2.1.2(@module-federation/runtime-tools@packages+runtime-tools)(@swc/helpers@0.5.23)': + dependencies: + '@rspack/binding': 2.1.2 + optionalDependencies: + '@module-federation/runtime-tools': link:packages/runtime-tools + '@swc/helpers': 0.5.23 + '@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.17)': dependencies: '@rspack/binding': 2.1.8 @@ -41558,6 +42074,57 @@ snapshots: optionalDependencies: '@rspack/core': 2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23) + '@rspack/resolver-binding-darwin-arm64@0.2.8': + optional: true + + '@rspack/resolver-binding-darwin-x64@0.2.8': + optional: true + + '@rspack/resolver-binding-linux-arm64-gnu@0.2.8': + optional: true + + '@rspack/resolver-binding-linux-arm64-musl@0.2.8': + optional: true + + '@rspack/resolver-binding-linux-x64-gnu@0.2.8': + optional: true + + '@rspack/resolver-binding-linux-x64-musl@0.2.8': + optional: true + + '@rspack/resolver-binding-wasm32-wasi@0.2.8(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': + dependencies: + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + optional: true + + '@rspack/resolver-binding-win32-arm64-msvc@0.2.8': + optional: true + + '@rspack/resolver-binding-win32-ia32-msvc@0.2.8': + optional: true + + '@rspack/resolver-binding-win32-x64-msvc@0.2.8': + optional: true + + '@rspack/resolver@0.2.8(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': + optionalDependencies: + '@rspack/resolver-binding-darwin-arm64': 0.2.8 + '@rspack/resolver-binding-darwin-x64': 0.2.8 + '@rspack/resolver-binding-linux-arm64-gnu': 0.2.8 + '@rspack/resolver-binding-linux-arm64-musl': 0.2.8 + '@rspack/resolver-binding-linux-x64-gnu': 0.2.8 + '@rspack/resolver-binding-linux-x64-musl': 0.2.8 + '@rspack/resolver-binding-wasm32-wasi': 0.2.8(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + '@rspack/resolver-binding-win32-arm64-msvc': 0.2.8 + '@rspack/resolver-binding-win32-ia32-msvc': 0.2.8 + '@rspack/resolver-binding-win32-x64-msvc': 0.2.8 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + '@rspress/core@2.0.14(@module-federation/runtime-tools@2.8.2)(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(@types/mdast@4.0.4)(@types/react@18.3.28)(core-js@3.49.0)(micromark-util-types@2.0.2)(micromark@4.0.2)': dependencies: '@mdx-js/mdx': 3.1.1 @@ -41681,7 +42248,7 @@ snapshots: open-editor: 6.0.0 pathe: 2.0.3 sirv: 3.0.2 - ws: 8.21.2 + ws: 8.21.3 optionalDependencies: playwright: 1.57.0 transitivePeerDependencies: @@ -41708,6 +42275,16 @@ snapshots: - '@module-federation/runtime-tools' - core-js + '@rstest/core@0.10.6(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0)(jsdom@20.0.3)': + dependencies: + '@rsbuild/core': 2.0.15(@module-federation/runtime-tools@packages+runtime-tools)(core-js@3.49.0) + '@types/chai': 5.2.3 + optionalDependencies: + jsdom: 20.0.3 + transitivePeerDependencies: + - '@module-federation/runtime-tools' + - core-js + '@rstest/core@0.11.6(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(jsdom@20.0.3)': dependencies: '@rsbuild/core': 2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0) @@ -41733,10 +42310,10 @@ snapshots: ajv: 8.18.0 ajv-draft-04: 1.0.0(ajv@8.18.0) ajv-formats: 3.0.1(ajv@8.18.0) - fs-extra: 11.3.6 + fs-extra: 11.3.0 import-lazy: 4.0.0 jju: 1.4.0 - resolve: 1.22.12 + resolve: 1.22.11 semver: 7.5.4 optionalDependencies: '@types/node': 20.19.5 @@ -41747,27 +42324,27 @@ snapshots: ajv: 8.18.0 ajv-draft-04: 1.0.0(ajv@8.18.0) ajv-formats: 3.0.1(ajv@8.18.0) - fs-extra: 11.3.6 + fs-extra: 11.3.0 import-lazy: 4.0.0 jju: 1.4.0 - resolve: 1.22.12 + resolve: 1.22.11 semver: 7.5.4 optionalDependencies: '@types/node': 22.19.15 optional: true - '@rushstack/node-core-library@5.20.3(@types/node@26.2.0)': + '@rushstack/node-core-library@5.20.3(@types/node@26.1.0)': dependencies: ajv: 8.18.0 ajv-draft-04: 1.0.0(ajv@8.18.0) ajv-formats: 3.0.1(ajv@8.18.0) - fs-extra: 11.3.6 + fs-extra: 11.3.0 import-lazy: 4.0.0 jju: 1.4.0 - resolve: 1.22.12 + resolve: 1.22.11 semver: 7.5.4 optionalDependencies: - '@types/node': 26.2.0 + '@types/node': 26.1.0 optional: true '@rushstack/problem-matcher@0.2.1(@types/node@20.19.5)': @@ -41780,14 +42357,14 @@ snapshots: '@types/node': 22.19.15 optional: true - '@rushstack/problem-matcher@0.2.1(@types/node@26.2.0)': + '@rushstack/problem-matcher@0.2.1(@types/node@26.1.0)': optionalDependencies: - '@types/node': 26.2.0 + '@types/node': 26.1.0 optional: true '@rushstack/rig-package@0.7.2': dependencies: - resolve: 1.22.12 + resolve: 1.22.11 strip-json-comments: 3.1.1 optional: true @@ -41809,13 +42386,13 @@ snapshots: '@types/node': 22.19.15 optional: true - '@rushstack/terminal@0.22.3(@types/node@26.2.0)': + '@rushstack/terminal@0.22.3(@types/node@26.1.0)': dependencies: - '@rushstack/node-core-library': 5.20.3(@types/node@26.2.0) - '@rushstack/problem-matcher': 0.2.1(@types/node@26.2.0) + '@rushstack/node-core-library': 5.20.3(@types/node@26.1.0) + '@rushstack/problem-matcher': 0.2.1(@types/node@26.1.0) supports-color: 8.1.1 optionalDependencies: - '@types/node': 26.2.0 + '@types/node': 26.1.0 optional: true '@rushstack/ts-command-line@5.3.3(@types/node@20.19.5)': @@ -41838,9 +42415,9 @@ snapshots: - '@types/node' optional: true - '@rushstack/ts-command-line@5.3.3(@types/node@26.2.0)': + '@rushstack/ts-command-line@5.3.3(@types/node@26.1.0)': dependencies: - '@rushstack/terminal': 0.22.3(@types/node@26.2.0) + '@rushstack/terminal': 0.22.3(@types/node@26.1.0) '@types/argparse': 1.0.38 argparse: 1.0.10 string-argv: 0.3.2 @@ -41951,6 +42528,8 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@socket.io/component-emitter@3.1.2': {} + '@storybook/addon-docs@8.6.18(@types/react@18.3.28)(storybook@8.6.17(prettier@3.8.1))': dependencies: '@mdx-js/react': 3.1.1(@types/react@18.3.28)(react@18.3.1) @@ -42048,7 +42627,7 @@ snapshots: dependencies: '@babel/core': 7.29.7 '@babel/preset-env': 7.29.2(@babel/core@7.29.7) - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 '@ndelangen/get-tarball': 3.0.9 '@storybook/codemod': 7.6.24 '@storybook/core-common': 7.6.24(encoding@0.1.13) @@ -42081,7 +42660,7 @@ snapshots: prompts: 2.4.2 puppeteer-core: 2.1.1 read-pkg-up: 7.0.1 - semver: 7.8.5 + semver: 7.6.3 strip-json-comments: 3.1.1 tempy: 1.0.1 ts-dedent: 2.2.0 @@ -42100,7 +42679,7 @@ snapshots: dependencies: '@babel/core': 7.29.7 '@babel/preset-env': 7.29.2(@babel/core@7.29.7) - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 '@storybook/csf': 0.1.13 '@storybook/csf-tools': 7.6.24 '@storybook/node-logger': 7.6.24 @@ -42209,7 +42788,7 @@ snapshots: pretty-hrtime: 1.0.3 prompts: 2.4.2 read-pkg-up: 7.0.1 - semver: 7.8.5 + semver: 7.6.3 telejson: 7.2.0 tiny-invariant: 1.3.3 ts-dedent: 2.2.0 @@ -42264,7 +42843,7 @@ snapshots: jsdoc-type-pratt-parser: 4.8.0 process: 0.11.10 recast: 0.23.11 - semver: 7.8.5 + semver: 7.6.3 util: 0.12.5 ws: 8.21.0 optionalDependencies: @@ -42294,10 +42873,10 @@ snapshots: '@storybook/csf-tools@7.6.24': dependencies: - '@babel/generator': 7.29.8 - '@babel/parser': 7.29.8 + '@babel/generator': 7.29.7 + '@babel/parser': 7.29.7 '@babel/traverse': 7.29.7 - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 '@storybook/csf': 0.1.13 '@storybook/types': 7.6.24 fs-extra: 11.3.0 @@ -42373,8 +42952,8 @@ snapshots: image-size: 2.0.2 loader-utils: 3.3.1 node-polyfill-webpack-plugin: 2.0.1(webpack@5.104.1) - postcss: 8.5.23 - postcss-loader: 8.2.1(@rspack/core@1.3.9(@swc/helpers@0.5.13))(postcss@8.5.23)(typescript@6.0.3)(webpack@5.104.1) + postcss: 8.5.24 + postcss-loader: 8.2.1(@rspack/core@1.3.9(@swc/helpers@0.5.13))(postcss@8.5.24)(typescript@6.0.3)(webpack@5.104.1) react-refresh: 0.14.2 resolve-url-loader: 5.0.0 sass-loader: 14.2.1(@rspack/core@1.3.9(@swc/helpers@0.5.13))(sass-embedded@1.100.0)(sass@1.100.0)(webpack@5.104.1) @@ -42423,7 +43002,7 @@ snapshots: react-docgen: 7.1.1 react-dom: 19.2.7(react@19.2.7) resolve: 1.22.8 - semver: 7.8.5 + semver: 7.6.3 storybook: 8.6.17(prettier@3.8.1) tsconfig-paths: 4.2.0 webpack: 5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4) @@ -42695,7 +43274,7 @@ snapshots: '@svgr/hast-util-to-babel-ast@8.0.0': dependencies: - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 entities: 4.5.0 '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@6.0.3))': @@ -43230,16 +43809,16 @@ snapshots: '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 '@types/body-parser@1.19.6': dependencies: @@ -43286,6 +43865,10 @@ snapshots: '@types/cookie@0.4.1': {} + '@types/cors@2.8.19': + dependencies: + '@types/node': 20.19.5 + '@types/cross-spawn@6.0.6': dependencies: '@types/node': 20.19.5 @@ -43457,9 +44040,9 @@ snapshots: '@types/estree@0.0.51': {} - '@types/estree@1.0.8': {} + '@types/estree@1.0.5': {} - '@types/estree@1.0.9': {} + '@types/estree@1.0.8': {} '@types/express-serve-static-core@4.19.8': dependencies: @@ -43468,13 +44051,6 @@ snapshots: '@types/range-parser': 1.2.7 '@types/send': 1.2.1 - '@types/express-serve-static-core@4.19.9': - dependencies: - '@types/node': 26.2.0 - '@types/qs': 6.15.1 - '@types/range-parser': 1.2.7 - '@types/send': 1.2.1 - '@types/express@4.17.21': dependencies: '@types/body-parser': 1.19.6 @@ -43485,7 +44061,7 @@ snapshots: '@types/express@4.17.25': dependencies: '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 4.19.9 + '@types/express-serve-static-core': 4.19.8 '@types/qs': 6.15.1 '@types/serve-static': 1.15.10 @@ -43516,7 +44092,7 @@ snapshots: dependencies: '@types/node': 20.19.5 '@types/tough-cookie': 4.0.5 - form-data: 2.5.6 + form-data: 2.5.5 '@types/graceful-fs@4.1.9': dependencies: @@ -43534,9 +44110,9 @@ snapshots: '@types/history@4.7.11': {} - '@types/hoist-non-react-statics@3.3.7(@types/react@19.2.14)': + '@types/hoist-non-react-statics@3.3.7(@types/react@18.3.28)': dependencies: - '@types/react': 19.2.14 + '@types/react': 18.3.28 hoist-non-react-statics: 3.3.2 '@types/html-minifier-terser@6.1.0': {} @@ -43584,7 +44160,7 @@ snapshots: '@types/loadable__component@5.13.10': dependencies: - '@types/react': 19.2.14 + '@types/react': 18.3.28 '@types/lodash-es@4.17.12': dependencies: @@ -43641,9 +44217,10 @@ snapshots: dependencies: undici-types: 6.21.0 - '@types/node@26.2.0': + '@types/node@26.1.0': dependencies: undici-types: 8.3.0 + optional: true '@types/normalize-package-data@2.4.4': {} @@ -43691,7 +44268,7 @@ snapshots: '@types/react-helmet@6.1.11': dependencies: - '@types/react': 19.2.14 + '@types/react': 18.3.28 '@types/react-router-dom@5.3.3': dependencies: @@ -43702,11 +44279,11 @@ snapshots: '@types/react-router@5.1.20': dependencies: '@types/history': 4.7.11 - '@types/react': 19.2.14 + '@types/react': 18.3.28 '@types/react-test-renderer@19.1.0': dependencies: - '@types/react': 19.2.14 + '@types/react': 18.3.28 '@types/react@18.2.79': dependencies: @@ -43777,12 +44354,16 @@ snapshots: '@types/styled-components@5.1.36': dependencies: - '@types/hoist-non-react-statics': 3.3.7(@types/react@19.2.14) - '@types/react': 19.2.14 + '@types/hoist-non-react-statics': 3.3.7(@types/react@18.3.28) + '@types/react': 18.3.28 csstype: 3.2.3 '@types/stylis@4.2.0': {} + '@types/tapable@2.3.0': + dependencies: + tapable: 2.3.0 + '@types/tough-cookie@4.0.5': {} '@types/trusted-types@2.0.7': @@ -43829,7 +44410,7 @@ snapshots: graphemer: 1.4.0 ignore: 5.3.2 natural-compare-lite: 1.4.0 - semver: 7.8.5 + semver: 7.6.3 tsutils: 3.21.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 @@ -44157,7 +44738,7 @@ snapshots: debug: 4.4.3(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.8.5 + semver: 7.6.3 tsutils: 3.21.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 @@ -44172,7 +44753,7 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.8.5 + semver: 7.6.3 ts-api-utils: 1.4.3(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 @@ -44187,7 +44768,7 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.9 - semver: 7.8.5 + semver: 7.6.3 ts-api-utils: 1.4.3(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 @@ -44202,7 +44783,7 @@ snapshots: '@typescript-eslint/visitor-keys': 8.56.1 debug: 4.4.3(supports-color@8.1.1) minimatch: 10.2.4 - semver: 7.8.5 + semver: 7.7.4 tinyglobby: 0.2.15 ts-api-utils: 2.4.0(typescript@6.0.3) typescript: 6.0.3 @@ -44217,7 +44798,7 @@ snapshots: '@typescript-eslint/visitor-keys': 8.57.1 debug: 4.4.3(supports-color@8.1.1) minimatch: 10.2.4 - semver: 7.8.5 + semver: 7.7.4 tinyglobby: 0.2.15 ts-api-utils: 2.4.0(typescript@6.0.3) typescript: 6.0.3 @@ -44232,7 +44813,7 @@ snapshots: '@typescript-eslint/visitor-keys': 8.57.1 debug: 4.4.3(supports-color@8.1.1) minimatch: 10.2.4 - semver: 7.8.5 + semver: 7.7.4 tinyglobby: 0.2.15 ts-api-utils: 2.4.0(typescript@7.0.2) typescript: 7.0.2 @@ -44249,7 +44830,7 @@ snapshots: '@typescript-eslint/typescript-estree': 5.62.0(typescript@7.0.2) eslint: 8.57.1 eslint-scope: 5.1.1 - semver: 7.8.5 + semver: 7.6.3 transitivePeerDependencies: - supports-color - typescript @@ -44553,10 +45134,10 @@ snapshots: - encoding - supports-color - '@vercel/nft@0.29.2(encoding@0.1.13)(rollup@4.62.2)': + '@vercel/nft@0.29.2(encoding@0.1.13)(rollup@4.59.0)': dependencies: '@mapbox/node-pre-gyp': 2.0.3(encoding@0.1.13) - '@rollup/pluginutils': 5.3.0(rollup@4.62.2) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) acorn: 8.17.0 acorn-import-attributes: 1.9.5(acorn@8.17.0) async-sema: 3.1.1 @@ -44572,10 +45153,10 @@ snapshots: - rollup - supports-color - '@vercel/nft@1.3.2(encoding@0.1.13)(rollup@4.62.2)': + '@vercel/nft@1.3.2(encoding@0.1.13)(rollup@4.59.0)': dependencies: '@mapbox/node-pre-gyp': 2.0.3(encoding@0.1.13) - '@rollup/pluginutils': 5.3.0(rollup@4.62.2) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) acorn: 8.16.0 acorn-import-attributes: 1.9.5(acorn@8.16.0) async-sema: 3.1.1 @@ -44661,7 +45242,7 @@ snapshots: json-schema-to-ts: 1.6.4 ts-morph: 12.0.0 - '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@26.2.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0))': + '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -44669,7 +45250,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 5.4.21(@types/node@26.2.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) + vite: 5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) transitivePeerDependencies: - supports-color @@ -44684,13 +45265,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue-jsx@4.2.0(vite@5.4.21(@types/node@26.2.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0))(vue@3.5.30(typescript@7.0.2))': + '@vitejs/plugin-vue-jsx@4.2.0(vite@5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0))(vue@3.5.30(typescript@7.0.2))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) '@rolldown/pluginutils': 1.0.0-rc.9 '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.29.0) - vite: 5.4.21(@types/node@26.2.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) + vite: 5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) vue: 3.5.30(typescript@7.0.2) transitivePeerDependencies: - supports-color @@ -44700,9 +45281,9 @@ snapshots: vite: 5.4.21(@types/node@20.19.5)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) vue: 3.5.30(typescript@7.0.2) - '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@26.2.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0))(vue@3.5.30(typescript@7.0.2))': + '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0))(vue@3.5.30(typescript@7.0.2))': dependencies: - vite: 5.4.21(@types/node@26.2.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) + vite: 5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) vue: 3.5.30(typescript@7.0.2) '@vitest/expect@3.2.6': @@ -44713,32 +45294,23 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.6(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(vite@7.3.6(@types/node@20.19.5)(jiti@2.6.1)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0))': + '@vitest/mocker@3.2.6(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(vite@5.4.21(@types/node@20.19.5)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0))': dependencies: '@vitest/spy': 3.2.6 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3) - vite: 7.3.6(@types/node@20.19.5)(jiti@2.6.1)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0) - - '@vitest/mocker@3.2.6(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(vite@7.3.6(@types/node@20.19.5)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0))': - dependencies: - '@vitest/spy': 3.2.6 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - msw: 1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3) - vite: 7.3.6(@types/node@20.19.5)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0) + vite: 5.4.21(@types/node@20.19.5)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) - '@vitest/mocker@3.2.6(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(vite@7.3.6(@types/node@26.2.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0))': + '@vitest/mocker@3.2.6(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(vite@5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0))': dependencies: '@vitest/spy': 3.2.6 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3) - vite: 7.3.6(@types/node@26.2.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0) + vite: 5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0) optional: true '@vitest/pretty-format@3.2.6': @@ -44856,7 +45428,7 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.7 '@vue/compiler-sfc': 3.5.30 transitivePeerDependencies: - supports-color @@ -44867,7 +45439,7 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-module-imports': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.7 '@vue/compiler-sfc': 3.5.30 transitivePeerDependencies: - supports-color @@ -44878,14 +45450,14 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-module-imports': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.7 '@vue/compiler-sfc': 3.5.30 transitivePeerDependencies: - supports-color '@vue/compiler-core@3.5.30': dependencies: - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.7 '@vue/shared': 3.5.30 entities: 7.0.1 estree-walker: 2.0.2 @@ -44905,7 +45477,7 @@ snapshots: '@vue/shared': 3.5.30 estree-walker: 2.0.2 magic-string: 0.30.21 - postcss: 8.5.23 + postcss: 8.5.24 source-map-js: 1.2.1 '@vue/compiler-ssr@3.5.30': @@ -45264,7 +45836,7 @@ snapshots: '@yarnpkg/parsers@3.0.2': dependencies: - js-yaml: 3.15.1 + js-yaml: 3.15.0 tslib: 2.8.1 '@zeit/schemas@2.36.0': {} @@ -45354,7 +45926,7 @@ snapshots: acorn-loose@8.5.2: dependencies: - acorn: 8.18.0 + acorn: 8.17.0 acorn-walk@7.2.0: {} @@ -45368,8 +45940,6 @@ snapshots: acorn@8.17.0: {} - acorn@8.18.0: {} - address@1.2.2: {} adjust-sourcemap-loader@4.0.0: @@ -45379,8 +45949,6 @@ snapshots: adm-zip@0.5.16: {} - adm-zip@0.5.18: {} - adm-zip@0.6.0: {} agent-base@5.1.1: {} @@ -45987,7 +46555,7 @@ snapshots: asn1js@3.0.10: dependencies: pvtsutils: 1.3.6 - pvutils: 1.2.0 + pvutils: 1.1.5 tslib: 2.8.1 assert-never@1.4.0: {} @@ -46074,16 +46642,6 @@ snapshots: postcss: 8.4.49 postcss-value-parser: 4.2.0 - autoprefixer@10.4.20(postcss@8.5.23): - dependencies: - browserslist: 4.28.1 - caniuse-lite: 1.0.30001780 - fraction.js: 4.3.7 - normalize-range: 0.1.2 - picocolors: 1.1.1 - postcss: 8.5.23 - postcss-value-parser: 4.2.0 - autoprefixer@10.4.20(postcss@8.5.24): dependencies: browserslist: 4.28.1 @@ -46094,32 +46652,22 @@ snapshots: postcss: 8.5.24 postcss-value-parser: 4.2.0 - autoprefixer@10.4.20(postcss@8.5.26): - dependencies: - browserslist: 4.28.1 - caniuse-lite: 1.0.30001780 - fraction.js: 4.3.7 - normalize-range: 0.1.2 - picocolors: 1.1.1 - postcss: 8.5.26 - postcss-value-parser: 4.2.0 - - autoprefixer@10.4.23(postcss@8.5.23): + autoprefixer@10.4.23(postcss@8.5.24): dependencies: browserslist: 4.28.4 caniuse-lite: 1.0.30001800 fraction.js: 5.3.4 picocolors: 1.1.1 - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - autoprefixer@10.4.27(postcss@8.5.23): + autoprefixer@10.4.27(postcss@8.5.24): dependencies: browserslist: 4.28.4 caniuse-lite: 1.0.30001800 fraction.js: 5.3.4 picocolors: 1.1.1 - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 available-typed-arrays@1.0.7: @@ -46140,7 +46688,7 @@ snapshots: axios@1.18.0: dependencies: follow-redirects: 1.16.0(debug@4.4.3) - form-data: 4.0.6 + form-data: 4.0.5 https-proxy-agent: 5.0.1 proxy-from-env: 2.1.0 transitivePeerDependencies: @@ -46273,7 +46821,7 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: '@babel/template': 7.29.7 - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.28.0 @@ -46419,12 +46967,12 @@ snapshots: optionalDependencies: '@babel/traverse': 7.29.0 - babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.29.7)(@babel/traverse@7.29.8): + babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.29.7)(@babel/traverse@7.29.7): dependencies: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 optionalDependencies: - '@babel/traverse': 7.29.8 + '@babel/traverse': 7.29.7 babel-plugin-vue-jsx-hmr@1.0.0: dependencies: @@ -46485,7 +47033,9 @@ snapshots: babel-walk@3.0.0-canary-5: dependencies: - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 + + background-only@0.0.1: {} bail@1.0.5: {} @@ -46530,6 +47080,8 @@ snapshots: base64-js@1.5.1: {} + base64id@2.0.0: {} + base@0.11.2: dependencies: cache-base: 1.0.1 @@ -46571,7 +47123,7 @@ snapshots: bin-version-check@5.1.0: dependencies: bin-version: 6.0.0 - semver: 7.8.5 + semver: 7.6.3 semver-truncate: 3.0.0 bin-version@6.0.0: @@ -46671,11 +47223,6 @@ snapshots: dependencies: balanced-match: 4.0.4 - brace-expansion@5.0.9: - dependencies: - balanced-match: 4.0.4 - optional: true - braces@2.3.2: dependencies: arr-flatten: 1.1.0 @@ -46747,6 +47294,8 @@ snapshots: dependencies: pako: 1.0.11 + browserslist-load-config@1.0.3: {} + browserslist-to-es-version@1.4.1: dependencies: browserslist: 4.28.4 @@ -47466,8 +48015,8 @@ snapshots: constantinople@4.0.1: dependencies: - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 constants-browserify@1.0.0: {} @@ -47629,7 +48178,7 @@ snapshots: dependencies: import-fresh: 2.0.0 is-directory: 0.3.1 - js-yaml: 3.15.1 + js-yaml: 3.15.0 parse-json: 4.0.0 cosmiconfig@7.1.0: @@ -47728,13 +48277,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@26.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)): + create-jest@29.7.0(@types/node@26.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@26.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)) + jest-config: 29.7.0(@types/node@26.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -47780,18 +48329,18 @@ snapshots: css-color-keywords@1.0.0: {} - css-declaration-sorter@7.3.1(postcss@8.5.23): + css-declaration-sorter@7.3.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 css-loader@6.11.0(@rspack/core@1.3.9(@swc/helpers@0.5.13))(webpack@5.104.1): dependencies: - icss-utils: 5.1.0(postcss@8.5.23) - postcss: 8.5.23 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.23) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.23) - postcss-modules-scope: 3.2.1(postcss@8.5.23) - postcss-modules-values: 4.0.0(postcss@8.5.23) + icss-utils: 5.1.0(postcss@8.5.24) + postcss: 8.5.24 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.24) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.24) + postcss-modules-scope: 3.2.1(postcss@8.5.24) + postcss-modules-values: 4.0.0(postcss@8.5.24) postcss-value-parser: 4.2.0 semver: 7.6.3 optionalDependencies: @@ -47800,12 +48349,12 @@ snapshots: css-loader@6.11.0(@rspack/core@1.6.8(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(webpack-cli@5.1.4)): dependencies: - icss-utils: 5.1.0(postcss@8.5.23) - postcss: 8.5.23 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.23) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.23) - postcss-modules-scope: 3.2.1(postcss@8.5.23) - postcss-modules-values: 4.0.0(postcss@8.5.23) + icss-utils: 5.1.0(postcss@8.5.24) + postcss: 8.5.24 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.24) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.24) + postcss-modules-scope: 3.2.1(postcss@8.5.24) + postcss-modules-values: 4.0.0(postcss@8.5.24) postcss-value-parser: 4.2.0 semver: 7.6.3 optionalDependencies: @@ -47815,9 +48364,9 @@ snapshots: css-minimizer-webpack-plugin@5.0.1(esbuild@0.25.5)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(webpack-cli@5.1.4)): dependencies: '@jridgewell/trace-mapping': 0.3.31 - cssnano: 6.1.2(postcss@8.5.23) + cssnano: 6.1.2(postcss@8.5.24) jest-worker: 29.7.0 - postcss: 8.5.23 + postcss: 8.5.24 schema-utils: 4.3.0 serialize-javascript: 6.0.2 webpack: 5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(webpack-cli@5.1.4) @@ -47827,9 +48376,9 @@ snapshots: css-minimizer-webpack-plugin@7.0.2(esbuild@0.18.20)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.19))(esbuild@0.18.20)(webpack-cli@5.1.4)): dependencies: '@jridgewell/trace-mapping': 0.3.31 - cssnano: 7.1.3(postcss@8.5.23) + cssnano: 7.1.3(postcss@8.5.24) jest-worker: 29.7.0 - postcss: 8.5.23 + postcss: 8.5.24 schema-utils: 4.3.0 serialize-javascript: 6.0.2 webpack: 5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.19))(esbuild@0.18.20)(webpack-cli@5.1.4) @@ -47839,9 +48388,9 @@ snapshots: css-minimizer-webpack-plugin@7.0.2(esbuild@0.25.5)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.25.5)(webpack-cli@5.1.4)): dependencies: '@jridgewell/trace-mapping': 0.3.31 - cssnano: 7.1.3(postcss@8.5.23) + cssnano: 7.1.3(postcss@8.5.24) jest-worker: 29.7.0 - postcss: 8.5.23 + postcss: 8.5.24 schema-utils: 4.3.0 serialize-javascript: 6.0.2 webpack: 5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.25.5)(webpack-cli@5.1.4) @@ -47851,9 +48400,9 @@ snapshots: css-minimizer-webpack-plugin@7.0.2(esbuild@0.25.5)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.19))(esbuild@0.18.20)(webpack-cli@5.1.4)): dependencies: '@jridgewell/trace-mapping': 0.3.31 - cssnano: 7.1.3(postcss@8.5.23) + cssnano: 7.1.3(postcss@8.5.24) jest-worker: 29.7.0 - postcss: 8.5.23 + postcss: 8.5.24 schema-utils: 4.3.0 serialize-javascript: 6.0.2 webpack: 5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.19))(esbuild@0.18.20)(webpack-cli@5.1.4) @@ -47863,9 +48412,9 @@ snapshots: css-minimizer-webpack-plugin@8.0.0(esbuild@0.28.1)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.28.1)(webpack-cli@5.1.4)): dependencies: '@jridgewell/trace-mapping': 0.3.31 - cssnano: 7.1.3(postcss@8.5.23) + cssnano: 7.1.3(postcss@8.5.24) jest-worker: 30.4.1 - postcss: 8.5.23 + postcss: 8.5.24 schema-utils: 4.3.0 serialize-javascript: 7.0.7 webpack: 5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.28.1)(webpack-cli@5.1.4) @@ -47875,15 +48424,27 @@ snapshots: css-minimizer-webpack-plugin@8.0.0(esbuild@0.28.1)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1))): dependencies: '@jridgewell/trace-mapping': 0.3.31 - cssnano: 7.1.3(postcss@8.5.23) + cssnano: 7.1.3(postcss@8.5.24) jest-worker: 30.4.1 - postcss: 8.5.23 + postcss: 8.5.24 schema-utils: 4.3.0 serialize-javascript: 7.0.7 webpack: 5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.28.1)(webpack-cli@5.1.4(webpack-dev-server@5.2.3)(webpack@5.104.1)) optionalDependencies: esbuild: 0.28.1 + css-minimizer-webpack-plugin@8.0.0(esbuild@0.28.1)(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + cssnano: 7.1.3(postcss@8.5.24) + jest-worker: 30.4.1 + postcss: 8.5.24 + schema-utils: 4.3.0 + serialize-javascript: 7.0.7 + webpack: 5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4) + optionalDependencies: + esbuild: 0.28.1 + css-select@4.3.0: dependencies: boolbase: 1.0.0 @@ -47927,93 +48488,93 @@ snapshots: cssesc@3.0.0: {} - cssnano-preset-default@6.1.2(postcss@8.5.23): + cssnano-preset-default@6.1.2(postcss@8.5.24): dependencies: browserslist: 4.28.4 - css-declaration-sorter: 7.3.1(postcss@8.5.23) - cssnano-utils: 4.0.2(postcss@8.5.23) - postcss: 8.5.23 - postcss-calc: 9.0.1(postcss@8.5.23) - postcss-colormin: 6.1.0(postcss@8.5.23) - postcss-convert-values: 6.1.0(postcss@8.5.23) - postcss-discard-comments: 6.0.2(postcss@8.5.23) - postcss-discard-duplicates: 6.0.3(postcss@8.5.23) - postcss-discard-empty: 6.0.3(postcss@8.5.23) - postcss-discard-overridden: 6.0.2(postcss@8.5.23) - postcss-merge-longhand: 6.0.5(postcss@8.5.23) - postcss-merge-rules: 6.1.1(postcss@8.5.23) - postcss-minify-font-values: 6.1.0(postcss@8.5.23) - postcss-minify-gradients: 6.0.3(postcss@8.5.23) - postcss-minify-params: 6.1.0(postcss@8.5.23) - postcss-minify-selectors: 6.0.4(postcss@8.5.23) - postcss-normalize-charset: 6.0.2(postcss@8.5.23) - postcss-normalize-display-values: 6.0.2(postcss@8.5.23) - postcss-normalize-positions: 6.0.2(postcss@8.5.23) - postcss-normalize-repeat-style: 6.0.2(postcss@8.5.23) - postcss-normalize-string: 6.0.2(postcss@8.5.23) - postcss-normalize-timing-functions: 6.0.2(postcss@8.5.23) - postcss-normalize-unicode: 6.1.0(postcss@8.5.23) - postcss-normalize-url: 6.0.2(postcss@8.5.23) - postcss-normalize-whitespace: 6.0.2(postcss@8.5.23) - postcss-ordered-values: 6.0.2(postcss@8.5.23) - postcss-reduce-initial: 6.1.0(postcss@8.5.23) - postcss-reduce-transforms: 6.0.2(postcss@8.5.23) - postcss-svgo: 6.0.3(postcss@8.5.23) - postcss-unique-selectors: 6.0.4(postcss@8.5.23) - - cssnano-preset-default@7.0.11(postcss@8.5.23): + css-declaration-sorter: 7.3.1(postcss@8.5.24) + cssnano-utils: 4.0.2(postcss@8.5.24) + postcss: 8.5.24 + postcss-calc: 9.0.1(postcss@8.5.24) + postcss-colormin: 6.1.0(postcss@8.5.24) + postcss-convert-values: 6.1.0(postcss@8.5.24) + postcss-discard-comments: 6.0.2(postcss@8.5.24) + postcss-discard-duplicates: 6.0.3(postcss@8.5.24) + postcss-discard-empty: 6.0.3(postcss@8.5.24) + postcss-discard-overridden: 6.0.2(postcss@8.5.24) + postcss-merge-longhand: 6.0.5(postcss@8.5.24) + postcss-merge-rules: 6.1.1(postcss@8.5.24) + postcss-minify-font-values: 6.1.0(postcss@8.5.24) + postcss-minify-gradients: 6.0.3(postcss@8.5.24) + postcss-minify-params: 6.1.0(postcss@8.5.24) + postcss-minify-selectors: 6.0.4(postcss@8.5.24) + postcss-normalize-charset: 6.0.2(postcss@8.5.24) + postcss-normalize-display-values: 6.0.2(postcss@8.5.24) + postcss-normalize-positions: 6.0.2(postcss@8.5.24) + postcss-normalize-repeat-style: 6.0.2(postcss@8.5.24) + postcss-normalize-string: 6.0.2(postcss@8.5.24) + postcss-normalize-timing-functions: 6.0.2(postcss@8.5.24) + postcss-normalize-unicode: 6.1.0(postcss@8.5.24) + postcss-normalize-url: 6.0.2(postcss@8.5.24) + postcss-normalize-whitespace: 6.0.2(postcss@8.5.24) + postcss-ordered-values: 6.0.2(postcss@8.5.24) + postcss-reduce-initial: 6.1.0(postcss@8.5.24) + postcss-reduce-transforms: 6.0.2(postcss@8.5.24) + postcss-svgo: 6.0.3(postcss@8.5.24) + postcss-unique-selectors: 6.0.4(postcss@8.5.24) + + cssnano-preset-default@7.0.11(postcss@8.5.24): dependencies: browserslist: 4.28.4 - css-declaration-sorter: 7.3.1(postcss@8.5.23) - cssnano-utils: 5.0.1(postcss@8.5.23) - postcss: 8.5.23 - postcss-calc: 10.1.1(postcss@8.5.23) - postcss-colormin: 7.0.6(postcss@8.5.23) - postcss-convert-values: 7.0.9(postcss@8.5.23) - postcss-discard-comments: 7.0.6(postcss@8.5.23) - postcss-discard-duplicates: 7.0.2(postcss@8.5.23) - postcss-discard-empty: 7.0.1(postcss@8.5.23) - postcss-discard-overridden: 7.0.1(postcss@8.5.23) - postcss-merge-longhand: 7.0.5(postcss@8.5.23) - postcss-merge-rules: 7.0.8(postcss@8.5.23) - postcss-minify-font-values: 7.0.1(postcss@8.5.23) - postcss-minify-gradients: 7.0.1(postcss@8.5.23) - postcss-minify-params: 7.0.6(postcss@8.5.23) - postcss-minify-selectors: 7.0.6(postcss@8.5.23) - postcss-normalize-charset: 7.0.1(postcss@8.5.23) - postcss-normalize-display-values: 7.0.1(postcss@8.5.23) - postcss-normalize-positions: 7.0.1(postcss@8.5.23) - postcss-normalize-repeat-style: 7.0.1(postcss@8.5.23) - postcss-normalize-string: 7.0.1(postcss@8.5.23) - postcss-normalize-timing-functions: 7.0.1(postcss@8.5.23) - postcss-normalize-unicode: 7.0.6(postcss@8.5.23) - postcss-normalize-url: 7.0.1(postcss@8.5.23) - postcss-normalize-whitespace: 7.0.1(postcss@8.5.23) - postcss-ordered-values: 7.0.2(postcss@8.5.23) - postcss-reduce-initial: 7.0.6(postcss@8.5.23) - postcss-reduce-transforms: 7.0.1(postcss@8.5.23) - postcss-svgo: 7.1.1(postcss@8.5.23) - postcss-unique-selectors: 7.0.5(postcss@8.5.23) - - cssnano-utils@4.0.2(postcss@8.5.23): - dependencies: - postcss: 8.5.23 - - cssnano-utils@5.0.1(postcss@8.5.23): - dependencies: - postcss: 8.5.23 - - cssnano@6.1.2(postcss@8.5.23): - dependencies: - cssnano-preset-default: 6.1.2(postcss@8.5.23) + css-declaration-sorter: 7.3.1(postcss@8.5.24) + cssnano-utils: 5.0.1(postcss@8.5.24) + postcss: 8.5.24 + postcss-calc: 10.1.1(postcss@8.5.24) + postcss-colormin: 7.0.6(postcss@8.5.24) + postcss-convert-values: 7.0.9(postcss@8.5.24) + postcss-discard-comments: 7.0.6(postcss@8.5.24) + postcss-discard-duplicates: 7.0.2(postcss@8.5.24) + postcss-discard-empty: 7.0.1(postcss@8.5.24) + postcss-discard-overridden: 7.0.1(postcss@8.5.24) + postcss-merge-longhand: 7.0.5(postcss@8.5.24) + postcss-merge-rules: 7.0.8(postcss@8.5.24) + postcss-minify-font-values: 7.0.1(postcss@8.5.24) + postcss-minify-gradients: 7.0.1(postcss@8.5.24) + postcss-minify-params: 7.0.6(postcss@8.5.24) + postcss-minify-selectors: 7.0.6(postcss@8.5.24) + postcss-normalize-charset: 7.0.1(postcss@8.5.24) + postcss-normalize-display-values: 7.0.1(postcss@8.5.24) + postcss-normalize-positions: 7.0.1(postcss@8.5.24) + postcss-normalize-repeat-style: 7.0.1(postcss@8.5.24) + postcss-normalize-string: 7.0.1(postcss@8.5.24) + postcss-normalize-timing-functions: 7.0.1(postcss@8.5.24) + postcss-normalize-unicode: 7.0.6(postcss@8.5.24) + postcss-normalize-url: 7.0.1(postcss@8.5.24) + postcss-normalize-whitespace: 7.0.1(postcss@8.5.24) + postcss-ordered-values: 7.0.2(postcss@8.5.24) + postcss-reduce-initial: 7.0.6(postcss@8.5.24) + postcss-reduce-transforms: 7.0.1(postcss@8.5.24) + postcss-svgo: 7.1.1(postcss@8.5.24) + postcss-unique-selectors: 7.0.5(postcss@8.5.24) + + cssnano-utils@4.0.2(postcss@8.5.24): + dependencies: + postcss: 8.5.24 + + cssnano-utils@5.0.1(postcss@8.5.24): + dependencies: + postcss: 8.5.24 + + cssnano@6.1.2(postcss@8.5.24): + dependencies: + cssnano-preset-default: 6.1.2(postcss@8.5.24) lilconfig: 3.1.3 - postcss: 8.5.23 + postcss: 8.5.24 - cssnano@7.1.3(postcss@8.5.23): + cssnano@7.1.3(postcss@8.5.24): dependencies: - cssnano-preset-default: 7.0.11(postcss@8.5.23) + cssnano-preset-default: 7.0.11(postcss@8.5.24) lilconfig: 3.1.3 - postcss: 8.5.23 + postcss: 8.5.24 csso@5.0.5: dependencies: @@ -48029,6 +48590,8 @@ snapshots: csstype@3.1.2: {} + csstype@3.1.3: {} + csstype@3.2.3: {} cuint@0.2.2: {} @@ -48403,6 +48966,10 @@ snapshots: optionalDependencies: babel-plugin-macros: 3.1.0 + deep-eql@4.1.4: + dependencies: + type-detect: 4.1.0 + deep-eql@5.0.2: {} deep-equal@1.0.1: {} @@ -48539,7 +49106,7 @@ snapshots: diff@4.0.4: {} - diff@8.0.4: + diff@8.0.3: optional: true diffie-hellman@5.0.3: @@ -48789,6 +49356,25 @@ snapshots: fast-json-parse: 1.0.3 objectorarray: 1.0.5 + engine.io-parser@5.2.3: {} + + engine.io@6.6.9: + dependencies: + '@types/cors': 2.8.19 + '@types/node': 20.19.5 + '@types/ws': 8.5.12 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.7.2 + cors: 2.8.6 + debug: 4.4.3(supports-color@8.1.1) + engine.io-parser: 5.2.3 + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + enhanced-resolve@5.16.1: dependencies: graceful-fs: 4.2.11 @@ -48873,7 +49459,7 @@ snapshots: has-property-descriptors: 1.0.2 has-proto: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.4 + hasown: 2.0.2 internal-slot: 1.1.0 is-array-buffer: 3.0.5 is-callable: 1.2.7 @@ -48947,11 +49533,11 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.4 + hasown: 2.0.2 es-shim-unscopables@1.1.0: dependencies: - hasown: 2.0.4 + hasown: 2.0.2 es-to-primitive@1.3.0: dependencies: @@ -49484,7 +50070,7 @@ snapshots: eslint: 9.26.0(jiti@2.7.0) eslint-import-resolver-node: 0.3.9 eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@6.0.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@6.0.3))(eslint@9.26.0(jiti@2.7.0)))(eslint@9.26.0(jiti@2.7.0)))(eslint@9.26.0(jiti@2.7.0)) - hasown: 2.0.4 + hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 minimatch: 3.1.5 @@ -49512,13 +50098,13 @@ snapshots: - supports-color - typescript - eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@7.0.2))(eslint@8.57.1)(typescript@7.0.2))(eslint@8.57.1)(jest@29.7.0(@types/node@26.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)))(typescript@7.0.2): + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@7.0.2))(eslint@8.57.1)(typescript@7.0.2))(eslint@8.57.1)(jest@29.7.0(@types/node@26.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)))(typescript@7.0.2): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@7.0.2) eslint: 8.57.1 optionalDependencies: '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@7.0.2))(eslint@8.57.1)(typescript@7.0.2) - jest: 29.7.0(@types/node@26.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)) + jest: 29.7.0(@types/node@26.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)) transitivePeerDependencies: - supports-color - typescript @@ -49614,7 +50200,7 @@ snapshots: eslint-plugin-react-hooks@7.0.1(eslint@9.26.0(jiti@2.7.0)): dependencies: '@babel/core': 7.29.7 - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.7 eslint: 9.26.0(jiti@2.7.0) hermes-parser: 0.25.1 zod: 4.1.12 @@ -49949,7 +50535,7 @@ snapshots: estree-to-babel@3.2.1: dependencies: '@babel/traverse': 7.29.7 - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 c8: 7.14.0 transitivePeerDependencies: - supports-color @@ -50365,10 +50951,6 @@ snapshots: optionalDependencies: picomatch: 4.0.3 - fdir@6.5.0(picomatch@4.0.5): - optionalDependencies: - picomatch: 4.0.5 - fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 @@ -50428,6 +51010,8 @@ snapshots: filesize@10.1.6: {} + filesize@11.0.22: {} + fill-range@4.0.0: dependencies: extend-shallow: 2.0.1 @@ -50575,6 +51159,10 @@ snapshots: dependencies: tslib: 2.8.1 + follow-redirects@1.15.11(debug@4.4.3): + optionalDependencies: + debug: 4.4.3(supports-color@8.1.1) + follow-redirects@1.16.0(debug@4.4.3): optionalDependencies: debug: 4.4.3(supports-color@8.1.1) @@ -50609,7 +51197,7 @@ snapshots: minimatch: 3.1.5 node-abort-controller: 3.1.1 schema-utils: 3.3.0 - semver: 7.8.5 + semver: 7.6.3 tapable: 2.3.0 typescript: 7.0.2 webpack: 5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(webpack-cli@5.1.4) @@ -50628,19 +51216,19 @@ snapshots: minimatch: 3.1.5 node-abort-controller: 3.1.1 schema-utils: 3.3.0 - semver: 7.8.5 + semver: 7.6.3 tapable: 2.3.0 typescript: 6.0.3 webpack: 5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4) form-data-encoder@2.1.4: {} - form-data@2.5.6: + form-data@2.5.5: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.4 + hasown: 2.0.2 mime-types: 2.1.35 safe-buffer: 5.2.1 @@ -50649,7 +51237,7 @@ snapshots: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.4 + hasown: 2.0.2 mime-types: 2.1.35 form-data@4.0.6: @@ -50700,7 +51288,7 @@ snapshots: front-matter@4.0.2: dependencies: - js-yaml: 3.15.1 + js-yaml: 3.15.0 fs-constants@1.0.0: {} @@ -50728,13 +51316,6 @@ snapshots: jsonfile: 6.2.0 universalify: 2.0.1 - fs-extra@11.3.6: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.1 - universalify: 2.0.1 - optional: true - fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -50794,7 +51375,7 @@ snapshots: call-bound: 1.0.4 define-properties: 1.2.1 functions-have-names: 1.2.3 - hasown: 2.0.4 + hasown: 2.0.2 is-callable: 1.2.7 functions-have-names@1.2.3: {} @@ -50836,8 +51417,6 @@ snapshots: get-east-asian-width@1.5.0: {} - get-east-asian-width@1.6.0: {} - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -50848,7 +51427,7 @@ snapshots: get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.4 + hasown: 2.0.2 math-intrinsics: 1.1.0 get-nonce@1.0.1: {} @@ -51438,8 +52017,6 @@ snapshots: hono@3.12.12: {} - hono@4.13.0: {} - hono@4.13.1: {} hookable@6.1.0: {} @@ -51673,7 +52250,7 @@ snapshots: http-proxy@1.18.1(debug@4.4.3): dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.16.0(debug@4.4.3) + follow-redirects: 1.15.11(debug@4.4.3) requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -51777,9 +52354,9 @@ snapshots: icss-replace-symbols@1.1.0: {} - icss-utils@5.1.0(postcss@8.5.23): + icss-utils@5.1.0(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 icss-utils@5.1.0(postcss@8.5.8): dependencies: @@ -51907,7 +52484,7 @@ snapshots: internal-slot@1.1.0: dependencies: es-errors: 1.3.0 - hasown: 2.0.4 + hasown: 2.0.2 side-channel: 1.1.0 internmap@1.0.1: {} @@ -51932,7 +52509,7 @@ snapshots: is-accessor-descriptor@1.0.1: dependencies: - hasown: 2.0.4 + hasown: 2.0.2 is-alphabetical@1.0.4: {} @@ -51994,7 +52571,7 @@ snapshots: is-bun-module@2.0.0: dependencies: - semver: 7.8.5 + semver: 7.7.4 is-callable@1.2.7: {} @@ -52004,16 +52581,11 @@ snapshots: is-core-module@2.16.1: dependencies: - hasown: 2.0.4 - - is-core-module@2.16.2: - dependencies: - hasown: 2.0.4 - optional: true + hasown: 2.0.2 is-data-descriptor@1.0.1: dependencies: - hasown: 2.0.4 + hasown: 2.0.2 is-data-view@1.0.2: dependencies: @@ -52179,7 +52751,7 @@ snapshots: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.4 + hasown: 2.0.2 is-retry-allowed@2.2.0: {} @@ -52292,7 +52864,7 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: '@babel/core': 7.29.7 - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -52302,10 +52874,10 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: '@babel/core': 7.29.7 - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.8.5 + semver: 7.6.3 transitivePeerDependencies: - supports-color @@ -52423,16 +52995,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@26.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)): + jest-cli@29.7.0(@types/node@26.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@26.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)) + create-jest: 29.7.0(@types/node@26.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@26.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)) + jest-config: 29.7.0(@types/node@26.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -52504,7 +53076,7 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)): + jest-config@29.7.0(@types/node@20.19.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)): dependencies: '@babel/core': 7.29.7 '@jest/test-sequencer': 29.7.0 @@ -52530,12 +53102,12 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.19.5 - ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2) + ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@26.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)): + jest-config@29.7.0(@types/node@26.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)): dependencies: '@babel/core': 7.29.7 '@jest/test-sequencer': 29.7.0 @@ -52560,8 +53132,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 26.2.0 - ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2) + '@types/node': 26.1.0 + ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -52751,10 +53323,10 @@ snapshots: jest-snapshot@29.7.0: dependencies: '@babel/core': 7.29.7 - '@babel/generator': 7.29.8 + '@babel/generator': 7.29.7 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.7) '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.7) - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 @@ -52769,7 +53341,7 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.8.5 + semver: 7.6.3 transitivePeerDependencies: - supports-color @@ -52856,12 +53428,12 @@ snapshots: - supports-color - ts-node - jest@29.7.0(@types/node@26.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)): + jest@29.7.0(@types/node@26.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@26.2.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)) + jest-cli: 29.7.0(@types/node@26.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -52910,7 +53482,7 @@ snapshots: js-tokens@9.0.1: {} - js-yaml@3.15.1: + js-yaml@3.15.0: dependencies: argparse: 1.0.10 esprima: 4.0.1 @@ -52930,7 +53502,7 @@ snapshots: jscodeshift@0.15.2(@babel/preset-env@7.29.2(@babel/core@7.29.7)): dependencies: '@babel/core': 7.29.7 - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.7 '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.7) '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.7) '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.7) @@ -52967,7 +53539,7 @@ snapshots: decimal.js: 10.6.0 domexception: 4.0.0 escodegen: 2.1.0 - form-data: 4.0.6 + form-data: 4.0.5 html-encoding-sniffer: 3.0.0 http-proxy-agent: 5.0.0 https-proxy-agent: 5.0.1 @@ -53020,6 +53592,8 @@ snapshots: jsonify: 0.0.1 object-keys: 1.1.1 + json-stream-stringify@3.0.1: {} + json-stringify-safe@5.0.1: {} json2mq@0.2.0: @@ -53052,13 +53626,6 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - jsonfile@6.2.1: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - optional: true - jsonify@0.0.1: {} jsonparse@1.3.1: {} @@ -53149,6 +53716,11 @@ snapshots: picocolors: 1.1.1 shell-quote: 1.8.3 + launch-editor@2.14.1: + dependencies: + picocolors: 1.1.1 + shell-quote: 1.10.0 + layout-base@1.0.2: {} layout-base@2.0.1: {} @@ -53229,6 +53801,8 @@ snapshots: lines-and-columns@2.0.3: {} + lines-and-columns@2.0.4: {} + linkify-it@5.0.0: dependencies: uc.micro: 2.1.0 @@ -53499,7 +54073,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.8.5 + semver: 7.6.3 make-error@1.3.6: {} @@ -54015,7 +54589,7 @@ snapshots: metro-source-map@0.82.5: dependencies: '@babel/traverse': 7.29.0 - '@babel/traverse--for-generate-function-map': '@babel/traverse@7.29.8' + '@babel/traverse--for-generate-function-map': '@babel/traverse@7.29.7' '@babel/types': 7.29.0 flow-enums-runtime: 0.0.6 invariant: 2.2.4 @@ -54030,7 +54604,7 @@ snapshots: metro-source-map@0.83.5: dependencies: '@babel/traverse': 7.29.7 - '@babel/types': 7.29.8 + '@babel/types': 7.29.7 flow-enums-runtime: 0.0.6 invariant: 2.2.4 metro-symbolicate: 0.83.5 @@ -54077,7 +54651,7 @@ snapshots: metro-transform-plugins@0.83.5: dependencies: '@babel/core': 7.29.7 - '@babel/generator': 7.29.8 + '@babel/generator': 7.29.7 '@babel/template': 7.29.7 '@babel/traverse': 7.29.7 flow-enums-runtime: 0.0.6 @@ -54108,9 +54682,9 @@ snapshots: metro-transform-worker@0.83.5: dependencies: '@babel/core': 7.29.7 - '@babel/generator': 7.29.8 - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 + '@babel/generator': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 flow-enums-runtime: 0.0.6 metro: 0.83.5 metro-babel-transformer: 0.83.5 @@ -54615,7 +55189,7 @@ snapshots: minimatch@10.2.3: dependencies: - brace-expansion: 5.0.9 + brace-expansion: 5.0.4 optional: true minimatch@10.2.4: @@ -54718,7 +55292,7 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.3 - modern-tar@0.7.7: {} + modern-tar@0.7.6: {} moment@2.30.1: {} @@ -54806,6 +55380,8 @@ snapshots: nano-spawn@0.2.1: {} + nanoid@3.3.12: {} + nanoid@3.3.18: {} nanoid@5.1.16: {} @@ -54832,15 +55408,15 @@ snapshots: natural-compare@1.4.0: {} - ndepe@0.1.13(encoding@0.1.13)(rollup@4.62.2): + ndepe@0.1.13(encoding@0.1.13)(rollup@4.59.0): dependencies: - '@vercel/nft': 0.29.2(encoding@0.1.13)(rollup@4.62.2) + '@vercel/nft': 0.29.2(encoding@0.1.13)(rollup@4.59.0) debug: 4.4.3(supports-color@8.1.1) fs-extra: 11.3.0 mlly: 1.6.1 pkg-types: 1.3.1 pkg-up: 3.1.0 - semver: 7.8.5 + semver: 7.6.3 transitivePeerDependencies: - encoding - rollup @@ -55744,8 +56320,6 @@ snapshots: picomatch@4.0.3: {} - picomatch@4.0.5: {} - pidtree@0.6.0: {} pify@2.3.0: {} @@ -55832,7 +56406,7 @@ snapshots: asn1js: 3.0.10 bytestreamjs: 2.0.1 pvtsutils: 1.3.6 - pvutils: 1.2.0 + pvutils: 1.1.5 tslib: 2.8.1 playwright-core@1.57.0: {} @@ -55861,110 +56435,95 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-calc@10.1.1(postcss@8.5.23): + postcss-calc@10.1.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - postcss-calc@9.0.1(postcss@8.5.23): + postcss-calc@9.0.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 - postcss-selector-parser: 6.1.2 - postcss-value-parser: 4.2.0 - - postcss-calc@9.0.1(postcss@8.5.26): - dependencies: - postcss: 8.5.26 + postcss: 8.5.24 postcss-selector-parser: 6.1.2 postcss-value-parser: 4.2.0 - postcss-colormin@6.1.0(postcss@8.5.23): + postcss-colormin@6.1.0(postcss@8.5.24): dependencies: browserslist: 4.28.4 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-colormin@7.0.6(postcss@8.5.23): + postcss-colormin@7.0.6(postcss@8.5.24): dependencies: browserslist: 4.28.4 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-convert-values@6.1.0(postcss@8.5.23): + postcss-convert-values@6.1.0(postcss@8.5.24): dependencies: browserslist: 4.28.4 - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-convert-values@7.0.9(postcss@8.5.23): + postcss-convert-values@7.0.9(postcss@8.5.24): dependencies: browserslist: 4.28.4 - postcss: 8.5.23 - postcss-value-parser: 4.2.0 - - postcss-custom-properties@13.3.12(postcss@8.5.23): - dependencies: - '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) - '@csstools/css-tokenizer': 2.4.1 - '@csstools/utilities': 1.0.0(postcss@8.5.23) - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-custom-properties@13.3.12(postcss@8.5.26): + postcss-custom-properties@13.3.12(postcss@8.5.24): dependencies: '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/utilities': 1.0.0(postcss@8.5.26) - postcss: 8.5.26 + '@csstools/utilities': 1.0.0(postcss@8.5.24) + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-discard-comments@6.0.2(postcss@8.5.23): + postcss-discard-comments@6.0.2(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 - postcss-discard-comments@7.0.6(postcss@8.5.23): + postcss-discard-comments@7.0.6(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-selector-parser: 7.1.1 - postcss-discard-duplicates@6.0.3(postcss@8.5.23): + postcss-discard-duplicates@6.0.3(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 - postcss-discard-duplicates@7.0.2(postcss@8.5.23): + postcss-discard-duplicates@7.0.2(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 - postcss-discard-empty@6.0.3(postcss@8.5.23): + postcss-discard-empty@6.0.3(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 - postcss-discard-empty@7.0.1(postcss@8.5.23): + postcss-discard-empty@7.0.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 - postcss-discard-overridden@6.0.2(postcss@8.5.23): + postcss-discard-overridden@6.0.2(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 - postcss-discard-overridden@7.0.1(postcss@8.5.23): + postcss-discard-overridden@7.0.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 - postcss-flexbugs-fixes@5.0.2(postcss@8.5.23): + postcss-flexbugs-fixes@5.0.2(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 - postcss-font-variant@5.0.0(postcss@8.5.23): + postcss-font-variant@5.0.0(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-import@14.1.0(postcss@8.4.49): dependencies: @@ -55980,33 +56539,26 @@ snapshots: read-cache: 1.0.0 resolve: 1.22.8 - postcss-import@15.1.0(postcss@8.5.23): + postcss-import@15.1.0(postcss@8.5.24): dependencies: - postcss: 8.5.23 - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.8 - - postcss-import@15.1.0(postcss@8.5.26): - dependencies: - postcss: 8.5.26 + postcss: 8.5.24 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-initial@4.0.1(postcss@8.5.23): + postcss-initial@4.0.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-js@4.1.0(postcss@8.4.49): dependencies: camelcase-css: 2.0.1 postcss: 8.4.49 - postcss-js@4.1.0(postcss@8.5.23): + postcss-js@4.1.0(postcss@8.5.24): dependencies: camelcase-css: 2.0.1 - postcss: 8.5.23 + postcss: 8.5.24 postcss-load-config@4.0.2(postcss@8.4.49)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@6.0.3)): dependencies: @@ -56024,52 +56576,44 @@ snapshots: postcss: 8.4.49 ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2) - postcss-load-config@4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@5.9.3)): + postcss-load-config@4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@5.9.3)): dependencies: lilconfig: 3.1.3 yaml: 2.8.2 optionalDependencies: postcss: 8.4.49 - ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@5.9.3) + ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@5.9.3) - postcss-load-config@4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)): + postcss-load-config@4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)): dependencies: lilconfig: 3.1.3 yaml: 2.8.2 optionalDependencies: postcss: 8.4.49 - ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2) + ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2) - postcss-load-config@4.0.2(postcss@8.5.23)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@22.19.15)(typescript@6.0.3)): + postcss-load-config@4.0.2(postcss@8.5.24)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@6.0.3)): dependencies: lilconfig: 3.1.3 yaml: 2.8.2 optionalDependencies: - postcss: 8.5.23 - ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@22.19.15)(typescript@6.0.3) - - postcss-load-config@4.0.2(postcss@8.5.23)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2)): - dependencies: - lilconfig: 3.1.3 - yaml: 2.8.2 - optionalDependencies: - postcss: 8.5.23 - ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2) + postcss: 8.5.24 + ts-node: 10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@6.0.3) - postcss-load-config@4.0.2(postcss@8.5.26)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@6.0.3)): + postcss-load-config@4.0.2(postcss@8.5.24)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@22.19.15)(typescript@6.0.3)): dependencies: lilconfig: 3.1.3 yaml: 2.8.2 optionalDependencies: - postcss: 8.5.26 - ts-node: 10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@6.0.3) + postcss: 8.5.24 + ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@22.19.15)(typescript@6.0.3) - postcss-load-config@4.0.2(postcss@8.5.26)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2)): + postcss-load-config@4.0.2(postcss@8.5.24)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2)): dependencies: lilconfig: 3.1.3 yaml: 2.8.2 optionalDependencies: - postcss: 8.5.26 + postcss: 8.5.24 ts-node: 10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2) postcss-loader@6.2.1(postcss@8.4.49)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(webpack-cli@5.1.4)): @@ -56077,114 +56621,114 @@ snapshots: cosmiconfig: 7.1.0 klona: 2.0.6 postcss: 8.4.49 - semver: 7.8.5 + semver: 7.6.3 webpack: 5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(webpack-cli@5.1.4) - postcss-loader@8.2.1(@rspack/core@1.3.9(@swc/helpers@0.5.13))(postcss@8.5.23)(typescript@6.0.3)(webpack@5.104.1): + postcss-loader@8.2.1(@rspack/core@1.3.9(@swc/helpers@0.5.13))(postcss@8.5.24)(typescript@6.0.3)(webpack@5.104.1): dependencies: cosmiconfig: 9.0.1(typescript@6.0.3) jiti: 2.6.1 - postcss: 8.5.23 - semver: 7.8.5 + postcss: 8.5.24 + semver: 7.6.3 optionalDependencies: '@rspack/core': 1.3.9(@swc/helpers@0.5.13) webpack: 5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.28.1)(webpack-cli@5.1.4) transitivePeerDependencies: - typescript - postcss-media-minmax@5.0.0(postcss@8.5.23): + postcss-media-minmax@5.0.0(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 - postcss-merge-longhand@6.0.5(postcss@8.5.23): + postcss-merge-longhand@6.0.5(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - stylehacks: 6.1.1(postcss@8.5.23) + stylehacks: 6.1.1(postcss@8.5.24) - postcss-merge-longhand@7.0.5(postcss@8.5.23): + postcss-merge-longhand@7.0.5(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - stylehacks: 7.0.8(postcss@8.5.23) + stylehacks: 7.0.8(postcss@8.5.24) - postcss-merge-rules@6.1.1(postcss@8.5.23): + postcss-merge-rules@6.1.1(postcss@8.5.24): dependencies: browserslist: 4.28.4 caniuse-api: 3.0.0 - cssnano-utils: 4.0.2(postcss@8.5.23) - postcss: 8.5.23 + cssnano-utils: 4.0.2(postcss@8.5.24) + postcss: 8.5.24 postcss-selector-parser: 6.1.2 - postcss-merge-rules@7.0.8(postcss@8.5.23): + postcss-merge-rules@7.0.8(postcss@8.5.24): dependencies: browserslist: 4.28.4 caniuse-api: 3.0.0 - cssnano-utils: 5.0.1(postcss@8.5.23) - postcss: 8.5.23 + cssnano-utils: 5.0.1(postcss@8.5.24) + postcss: 8.5.24 postcss-selector-parser: 7.1.1 - postcss-minify-font-values@6.1.0(postcss@8.5.23): + postcss-minify-font-values@6.1.0(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-minify-font-values@7.0.1(postcss@8.5.23): + postcss-minify-font-values@7.0.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-minify-gradients@6.0.3(postcss@8.5.23): + postcss-minify-gradients@6.0.3(postcss@8.5.24): dependencies: colord: 2.9.3 - cssnano-utils: 4.0.2(postcss@8.5.23) - postcss: 8.5.23 + cssnano-utils: 4.0.2(postcss@8.5.24) + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-minify-gradients@7.0.1(postcss@8.5.23): + postcss-minify-gradients@7.0.1(postcss@8.5.24): dependencies: colord: 2.9.3 - cssnano-utils: 5.0.1(postcss@8.5.23) - postcss: 8.5.23 + cssnano-utils: 5.0.1(postcss@8.5.24) + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-minify-params@6.1.0(postcss@8.5.23): + postcss-minify-params@6.1.0(postcss@8.5.24): dependencies: browserslist: 4.28.4 - cssnano-utils: 4.0.2(postcss@8.5.23) - postcss: 8.5.23 + cssnano-utils: 4.0.2(postcss@8.5.24) + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-minify-params@7.0.6(postcss@8.5.23): + postcss-minify-params@7.0.6(postcss@8.5.24): dependencies: browserslist: 4.28.4 - cssnano-utils: 5.0.1(postcss@8.5.23) - postcss: 8.5.23 + cssnano-utils: 5.0.1(postcss@8.5.24) + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-minify-selectors@6.0.4(postcss@8.5.23): + postcss-minify-selectors@6.0.4(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-selector-parser: 6.1.2 - postcss-minify-selectors@7.0.6(postcss@8.5.23): + postcss-minify-selectors@7.0.6(postcss@8.5.24): dependencies: cssesc: 3.0.0 - postcss: 8.5.23 + postcss: 8.5.24 postcss-selector-parser: 7.1.1 - postcss-modules-extract-imports@3.1.0(postcss@8.5.23): + postcss-modules-extract-imports@3.1.0(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-modules-extract-imports@3.1.0(postcss@8.5.8): dependencies: postcss: 8.5.8 - postcss-modules-local-by-default@4.2.0(postcss@8.5.23): + postcss-modules-local-by-default@4.2.0(postcss@8.5.24): dependencies: - icss-utils: 5.1.0(postcss@8.5.23) - postcss: 8.5.23 + icss-utils: 5.1.0(postcss@8.5.24) + postcss: 8.5.24 postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 @@ -56195,9 +56739,9 @@ snapshots: postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.2.1(postcss@8.5.23): + postcss-modules-scope@3.2.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-selector-parser: 7.1.1 postcss-modules-scope@3.2.1(postcss@8.5.8): @@ -56205,10 +56749,10 @@ snapshots: postcss: 8.5.8 postcss-selector-parser: 7.1.1 - postcss-modules-values@4.0.0(postcss@8.5.23): + postcss-modules-values@4.0.0(postcss@8.5.24): dependencies: - icss-utils: 5.1.0(postcss@8.5.23) - postcss: 8.5.23 + icss-utils: 5.1.0(postcss@8.5.24) + postcss: 8.5.24 postcss-modules-values@4.0.0(postcss@8.5.8): dependencies: @@ -56227,16 +56771,16 @@ snapshots: postcss-modules-values: 4.0.0(postcss@8.5.8) string-hash: 1.1.3 - postcss-modules@6.0.1(postcss@8.5.23): + postcss-modules@6.0.1(postcss@8.5.24): dependencies: generic-names: 4.0.0 - icss-utils: 5.1.0(postcss@8.5.23) + icss-utils: 5.1.0(postcss@8.5.24) lodash.camelcase: 4.3.0 - postcss: 8.5.23 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.23) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.23) - postcss-modules-scope: 3.2.1(postcss@8.5.23) - postcss-modules-values: 4.0.0(postcss@8.5.23) + postcss: 8.5.24 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.24) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.24) + postcss-modules-scope: 3.2.1(postcss@8.5.24) + postcss-modules-values: 4.0.0(postcss@8.5.24) string-hash: 1.1.3 postcss-nested@6.2.0(postcss@8.4.49): @@ -56244,144 +56788,144 @@ snapshots: postcss: 8.4.49 postcss-selector-parser: 6.1.2 - postcss-nested@6.2.0(postcss@8.5.23): + postcss-nested@6.2.0(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-selector-parser: 6.1.2 - postcss-nesting@12.1.5(postcss@8.5.23): + postcss-nesting@12.1.5(postcss@8.5.24): dependencies: '@csstools/selector-resolve-nested': 1.1.0(postcss-selector-parser@6.1.2) '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.2) - postcss: 8.5.23 + postcss: 8.5.24 postcss-selector-parser: 6.1.2 - postcss-normalize-charset@6.0.2(postcss@8.5.23): + postcss-normalize-charset@6.0.2(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 - postcss-normalize-charset@7.0.1(postcss@8.5.23): + postcss-normalize-charset@7.0.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 - postcss-normalize-display-values@6.0.2(postcss@8.5.23): + postcss-normalize-display-values@6.0.2(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-normalize-display-values@7.0.1(postcss@8.5.23): + postcss-normalize-display-values@7.0.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-normalize-positions@6.0.2(postcss@8.5.23): + postcss-normalize-positions@6.0.2(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-normalize-positions@7.0.1(postcss@8.5.23): + postcss-normalize-positions@7.0.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-normalize-repeat-style@6.0.2(postcss@8.5.23): + postcss-normalize-repeat-style@6.0.2(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-normalize-repeat-style@7.0.1(postcss@8.5.23): + postcss-normalize-repeat-style@7.0.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-normalize-string@6.0.2(postcss@8.5.23): + postcss-normalize-string@6.0.2(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-normalize-string@7.0.1(postcss@8.5.23): + postcss-normalize-string@7.0.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-normalize-timing-functions@6.0.2(postcss@8.5.23): + postcss-normalize-timing-functions@6.0.2(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-normalize-timing-functions@7.0.1(postcss@8.5.23): + postcss-normalize-timing-functions@7.0.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-normalize-unicode@6.1.0(postcss@8.5.23): + postcss-normalize-unicode@6.1.0(postcss@8.5.24): dependencies: browserslist: 4.28.4 - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-normalize-unicode@7.0.6(postcss@8.5.23): + postcss-normalize-unicode@7.0.6(postcss@8.5.24): dependencies: browserslist: 4.28.4 - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-normalize-url@6.0.2(postcss@8.5.23): + postcss-normalize-url@6.0.2(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-normalize-url@7.0.1(postcss@8.5.23): + postcss-normalize-url@7.0.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-normalize-whitespace@6.0.2(postcss@8.5.23): + postcss-normalize-whitespace@6.0.2(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-normalize-whitespace@7.0.1(postcss@8.5.23): + postcss-normalize-whitespace@7.0.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-ordered-values@6.0.2(postcss@8.5.23): + postcss-ordered-values@6.0.2(postcss@8.5.24): dependencies: - cssnano-utils: 4.0.2(postcss@8.5.23) - postcss: 8.5.23 + cssnano-utils: 4.0.2(postcss@8.5.24) + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-ordered-values@7.0.2(postcss@8.5.23): + postcss-ordered-values@7.0.2(postcss@8.5.24): dependencies: - cssnano-utils: 5.0.1(postcss@8.5.23) - postcss: 8.5.23 + cssnano-utils: 5.0.1(postcss@8.5.24) + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-page-break@3.0.4(postcss@8.5.23): + postcss-page-break@3.0.4(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 - postcss-reduce-initial@6.1.0(postcss@8.5.23): + postcss-reduce-initial@6.1.0(postcss@8.5.24): dependencies: browserslist: 4.28.4 caniuse-api: 3.0.0 - postcss: 8.5.23 + postcss: 8.5.24 - postcss-reduce-initial@7.0.6(postcss@8.5.23): + postcss-reduce-initial@7.0.6(postcss@8.5.24): dependencies: browserslist: 4.28.4 caniuse-api: 3.0.0 - postcss: 8.5.23 + postcss: 8.5.24 - postcss-reduce-transforms@6.0.2(postcss@8.5.23): + postcss-reduce-transforms@6.0.2(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 - postcss-reduce-transforms@7.0.1(postcss@8.5.23): + postcss-reduce-transforms@7.0.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 postcss-selector-parser@6.0.10: @@ -56399,34 +56943,34 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-svgo@6.0.3(postcss@8.5.23): + postcss-svgo@6.0.3(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 svgo: 3.3.3 - postcss-svgo@7.1.1(postcss@8.5.23): + postcss-svgo@7.1.1(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-value-parser: 4.2.0 svgo: 4.0.1 - postcss-unique-selectors@6.0.4(postcss@8.5.23): + postcss-unique-selectors@6.0.4(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-selector-parser: 6.1.2 - postcss-unique-selectors@7.0.5(postcss@8.5.23): + postcss-unique-selectors@7.0.5(postcss@8.5.24): dependencies: - postcss: 8.5.23 + postcss: 8.5.24 postcss-selector-parser: 7.1.1 - postcss-url@10.1.3(postcss@8.5.26): + postcss-url@10.1.3(postcss@8.5.24): dependencies: make-dir: 3.1.0 mime: 2.5.2 minimatch: 3.0.8 - postcss: 8.5.26 + postcss: 8.5.24 xxhashjs: 0.2.2 postcss-value-parser@4.2.0: {} @@ -56439,19 +56983,13 @@ snapshots: postcss@8.4.49: dependencies: - nanoid: 3.3.18 + nanoid: 3.3.12 picocolors: 1.1.1 source-map-js: 1.2.1 postcss@8.5.15: dependencies: - nanoid: 3.3.18 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - postcss@8.5.23: - dependencies: - nanoid: 3.3.18 + nanoid: 3.3.12 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -56461,15 +56999,9 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.26: - dependencies: - nanoid: 3.3.18 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postcss@8.5.8: dependencies: - nanoid: 3.3.18 + nanoid: 3.3.12 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -56785,7 +57317,7 @@ snapshots: dependencies: tslib: 2.8.1 - pvutils@1.2.0: {} + pvutils@1.1.5: {} qs@6.14.2: dependencies: @@ -58178,7 +58710,7 @@ snapshots: react-docgen@6.0.0-alpha.3: dependencies: '@babel/core': 7.29.7 - '@babel/generator': 7.29.8 + '@babel/generator': 7.29.7 ast-types: 0.14.2 commander: 2.20.3 doctrine: 3.0.0 @@ -58717,7 +59249,7 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) webpack: 5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.19))(esbuild@0.18.20)(webpack-cli@5.1.4) - webpack-sources: 3.5.1 + webpack-sources: 3.5.0 react-server-dom-webpack@19.2.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.25.5)(webpack-cli@5.1.4)): dependencies: @@ -58726,7 +59258,7 @@ snapshots: react: 19.2.7 react-dom: 19.2.7(react@19.2.7) webpack: 5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.17))(esbuild@0.25.5)(webpack-cli@5.1.4) - webpack-sources: 3.5.1 + webpack-sources: 3.5.0 react-shadow@20.6.0(prop-types@15.8.1)(react-dom@17.0.2(react@17.0.2))(react@17.0.2): dependencies: @@ -58860,7 +59392,7 @@ snapshots: read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 - js-yaml: 3.15.1 + js-yaml: 3.15.0 pify: 4.0.1 strip-bom: 3.0.0 @@ -59268,7 +59800,7 @@ snapshots: adjust-sourcemap-loader: 4.0.0 convert-source-map: 1.9.0 loader-utils: 2.0.4 - postcss: 8.5.23 + postcss: 8.5.24 source-map: 0.6.1 resolve-url@0.2.1: {} @@ -59281,14 +59813,6 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - resolve@1.22.12: - dependencies: - es-errors: 1.3.0 - is-core-module: 2.16.2 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - optional: true - resolve@1.22.8: dependencies: is-core-module: 2.16.1 @@ -59495,37 +60019,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 - rollup@4.62.2: - dependencies: - '@types/estree': 1.0.9 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.62.2 - '@rollup/rollup-android-arm64': 4.62.2 - '@rollup/rollup-darwin-arm64': 4.62.2 - '@rollup/rollup-darwin-x64': 4.62.2 - '@rollup/rollup-freebsd-arm64': 4.62.2 - '@rollup/rollup-freebsd-x64': 4.62.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 - '@rollup/rollup-linux-arm-musleabihf': 4.62.2 - '@rollup/rollup-linux-arm64-gnu': 4.62.2 - '@rollup/rollup-linux-arm64-musl': 4.62.2 - '@rollup/rollup-linux-loong64-gnu': 4.62.2 - '@rollup/rollup-linux-loong64-musl': 4.62.2 - '@rollup/rollup-linux-ppc64-gnu': 4.62.2 - '@rollup/rollup-linux-ppc64-musl': 4.62.2 - '@rollup/rollup-linux-riscv64-gnu': 4.62.2 - '@rollup/rollup-linux-riscv64-musl': 4.62.2 - '@rollup/rollup-linux-s390x-gnu': 4.62.2 - '@rollup/rollup-linux-x64-gnu': 4.62.2 - '@rollup/rollup-linux-x64-musl': 4.62.2 - '@rollup/rollup-openbsd-x64': 4.62.2 - '@rollup/rollup-openharmony-arm64': 4.62.2 - '@rollup/rollup-win32-arm64-msvc': 4.62.2 - '@rollup/rollup-win32-ia32-msvc': 4.62.2 - '@rollup/rollup-win32-x64-gnu': 4.62.2 - '@rollup/rollup-win32-x64-msvc': 4.62.2 - fsevents: 2.3.3 - roughjs@4.6.6: dependencies: hachure-fill: 0.5.2 @@ -59579,23 +60072,23 @@ snapshots: '@microsoft/api-extractor': 7.57.7(@types/node@22.19.15) typescript: 6.0.3 - rsbuild-plugin-dts@0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@rsbuild/core@2.1.4(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(typescript@6.0.3): + rsbuild-plugin-dts@0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@rsbuild/core@2.1.4(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(typescript@6.0.3): dependencies: '@ast-grep/napi': 0.37.0 '@rsbuild/core': 2.1.4(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0) optionalDependencies: - '@microsoft/api-extractor': 7.57.7(@types/node@26.2.0) + '@microsoft/api-extractor': 7.57.7(@types/node@26.1.0) typescript: 6.0.3 - rsbuild-plugin-dts@0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@rsbuild/core@2.1.4(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(typescript@7.0.2): + rsbuild-plugin-dts@0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@rsbuild/core@2.1.4(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(typescript@7.0.2): dependencies: '@ast-grep/napi': 0.37.0 '@rsbuild/core': 2.1.4(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0) optionalDependencies: - '@microsoft/api-extractor': 7.57.7(@types/node@26.2.0) + '@microsoft/api-extractor': 7.57.7(@types/node@26.1.0) typescript: 7.0.2 - rsbuild-plugin-dts@0.9.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@rsbuild/core@1.4.0-beta.2)(typescript@5.9.3): + rsbuild-plugin-dts@0.9.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@rsbuild/core@1.4.0-beta.2)(typescript@5.9.3): dependencies: '@ast-grep/napi': 0.37.0 '@rsbuild/core': 1.4.0-beta.2 @@ -59604,7 +60097,7 @@ snapshots: tinyglobby: 0.2.15 tsconfig-paths: 4.2.0 optionalDependencies: - '@microsoft/api-extractor': 7.57.7(@types/node@26.2.0) + '@microsoft/api-extractor': 7.57.7(@types/node@26.1.0) typescript: 5.9.3 rsbuild-plugin-html-minifier-terser@1.1.3(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)): @@ -59635,6 +60128,8 @@ snapshots: rslog@1.3.2: {} + rslog@2.3.0: {} + rspack-manifest-plugin@5.0.3(@rspack/core@1.7.9(@swc/helpers@0.5.17)): dependencies: '@rspack/lite-tapable': 1.1.2 @@ -60012,7 +60507,7 @@ snapshots: semver-truncate@3.0.0: dependencies: - semver: 7.8.5 + semver: 7.6.3 semver@5.7.2: {} @@ -60216,37 +60711,38 @@ snapshots: '@img/sharp-win32-x64': 0.34.5 optional: true - sharp@0.35.0: + sharp@0.35.3(@types/node@20.19.5): dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 semver: 7.8.5 optionalDependencies: - '@img/sharp-darwin-arm64': 0.35.0 - '@img/sharp-darwin-x64': 0.35.0 - '@img/sharp-freebsd-wasm32': 0.35.0 - '@img/sharp-libvips-darwin-arm64': 1.3.0 - '@img/sharp-libvips-darwin-x64': 1.3.0 - '@img/sharp-libvips-linux-arm': 1.3.0 - '@img/sharp-libvips-linux-arm64': 1.3.0 - '@img/sharp-libvips-linux-ppc64': 1.3.0 - '@img/sharp-libvips-linux-riscv64': 1.3.0 - '@img/sharp-libvips-linux-s390x': 1.3.0 - '@img/sharp-libvips-linux-x64': 1.3.0 - '@img/sharp-libvips-linuxmusl-arm64': 1.3.0 - '@img/sharp-libvips-linuxmusl-x64': 1.3.0 - '@img/sharp-linux-arm': 0.35.0 - '@img/sharp-linux-arm64': 0.35.0 - '@img/sharp-linux-ppc64': 0.35.0 - '@img/sharp-linux-riscv64': 0.35.0 - '@img/sharp-linux-s390x': 0.35.0 - '@img/sharp-linux-x64': 0.35.0 - '@img/sharp-linuxmusl-arm64': 0.35.0 - '@img/sharp-linuxmusl-x64': 0.35.0 - '@img/sharp-webcontainers-wasm32': 0.35.0 - '@img/sharp-win32-arm64': 0.35.0 - '@img/sharp-win32-ia32': 0.35.0 - '@img/sharp-win32-x64': 0.35.0 + '@img/sharp-darwin-arm64': 0.35.3 + '@img/sharp-darwin-x64': 0.35.3 + '@img/sharp-freebsd-wasm32': 0.35.3 + '@img/sharp-libvips-darwin-arm64': 1.3.2 + '@img/sharp-libvips-darwin-x64': 1.3.2 + '@img/sharp-libvips-linux-arm': 1.3.2 + '@img/sharp-libvips-linux-arm64': 1.3.2 + '@img/sharp-libvips-linux-ppc64': 1.3.2 + '@img/sharp-libvips-linux-riscv64': 1.3.2 + '@img/sharp-libvips-linux-s390x': 1.3.2 + '@img/sharp-libvips-linux-x64': 1.3.2 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.2 + '@img/sharp-libvips-linuxmusl-x64': 1.3.2 + '@img/sharp-linux-arm': 0.35.3 + '@img/sharp-linux-arm64': 0.35.3 + '@img/sharp-linux-ppc64': 0.35.3 + '@img/sharp-linux-riscv64': 0.35.3 + '@img/sharp-linux-s390x': 0.35.3 + '@img/sharp-linux-x64': 0.35.3 + '@img/sharp-linuxmusl-arm64': 0.35.3 + '@img/sharp-linuxmusl-x64': 0.35.3 + '@img/sharp-webcontainers-wasm32': 0.35.3 + '@img/sharp-win32-arm64': 0.35.3 + '@img/sharp-win32-ia32': 0.35.3 + '@img/sharp-win32-x64': 0.35.3 + '@types/node': 20.19.5 shebang-command@1.2.0: dependencies: @@ -60262,6 +60758,8 @@ snapshots: shell-exec@1.0.2: {} + shell-quote@1.10.0: {} + shell-quote@1.8.3: {} shiki@4.2.0: @@ -60317,7 +60815,7 @@ snapshots: simple-update-notifier@2.0.0: dependencies: - semver: 7.8.5 + semver: 7.6.3 sirv@2.0.4: dependencies: @@ -60395,6 +60893,36 @@ snapshots: transitivePeerDependencies: - supports-color + socket.io-adapter@2.5.8: + dependencies: + debug: 4.4.3(supports-color@8.1.1) + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@4.2.7: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + socket.io@4.8.1: + dependencies: + accepts: 1.3.8 + base64id: 2.0.0 + cors: 2.8.6 + debug: 4.3.4 + engine.io: 6.6.9 + socket.io-adapter: 2.5.8 + socket.io-parser: 4.2.7 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + sockjs@0.3.24: dependencies: faye-websocket: 0.11.4 @@ -60576,10 +61104,10 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 - storybook-addon-rslib@1.0.3(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(@rslib/core@0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2))(storybook-builder-rsbuild@1.0.3(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(@types/react@18.3.28)(storybook@8.6.17(prettier@3.8.1))(tslib@2.8.1)(typescript@7.0.2))(typescript@7.0.2): + storybook-addon-rslib@1.0.3(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(@rslib/core@0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2))(storybook-builder-rsbuild@1.0.3(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(@types/react@18.3.28)(storybook@8.6.17(prettier@3.8.1))(tslib@2.8.1)(typescript@7.0.2))(typescript@7.0.2): dependencies: '@rsbuild/core': 2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0) - '@rslib/core': 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.2.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) + '@rslib/core': 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.0))(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0)(typescript@7.0.2) storybook-builder-rsbuild: 1.0.3(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(@types/react@18.3.28)(storybook@8.6.17(prettier@3.8.1))(tslib@2.8.1)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 @@ -60614,9 +61142,9 @@ snapshots: - '@typescript/native-preview' - tslib - storybook-react-rsbuild@1.0.3(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(storybook@8.6.17(prettier@3.8.1))(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.25.5)(webpack-cli@5.1.4)): + storybook-react-rsbuild@1.0.3(@rsbuild/core@2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0))(@rspack/core@2.1.8(@module-federation/runtime-tools@2.8.2)(@swc/helpers@0.5.23))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.59.0)(storybook@8.6.17(prettier@3.8.1))(tslib@2.8.1)(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.25.5)(webpack-cli@5.1.4)): dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.62.2) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) '@rsbuild/core': 2.1.10(@module-federation/runtime-tools@2.8.2)(core-js@3.49.0) '@storybook/react': 8.6.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.17(prettier@3.8.1))(typescript@7.0.2) '@storybook/react-docgen-typescript-plugin': 1.0.1(typescript@7.0.2)(webpack@5.104.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.25.5)(webpack-cli@5.1.4)) @@ -60748,11 +61276,6 @@ snapshots: get-east-asian-width: 1.5.0 strip-ansi: 7.2.0 - string-width@8.2.2: - dependencies: - get-east-asian-width: 1.6.0 - strip-ansi: 7.2.0 - string.prototype.includes@2.0.1: dependencies: call-bind: 1.0.8 @@ -61020,16 +61543,16 @@ snapshots: '@babel/core': 7.29.0 babel-plugin-macros: 3.1.0 - stylehacks@6.1.1(postcss@8.5.23): + stylehacks@6.1.1(postcss@8.5.24): dependencies: browserslist: 4.28.4 - postcss: 8.5.23 + postcss: 8.5.24 postcss-selector-parser: 6.1.2 - stylehacks@7.0.8(postcss@8.5.23): + stylehacks@7.0.8(postcss@8.5.24): dependencies: browserslist: 4.28.4 - postcss: 8.5.23 + postcss: 8.5.24 postcss-selector-parser: 7.1.1 stylis@4.2.0: {} @@ -61189,7 +61712,7 @@ snapshots: transitivePeerDependencies: - ts-node - tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@5.9.3)): + tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@5.9.3)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -61208,7 +61731,7 @@ snapshots: postcss: 8.4.49 postcss-import: 15.1.0(postcss@8.4.49) postcss-js: 4.1.0(postcss@8.4.49) - postcss-load-config: 4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@5.9.3)) + postcss-load-config: 4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@5.9.3)) postcss-nested: 6.2.0(postcss@8.4.49) postcss-selector-parser: 6.1.2 resolve: 1.22.8 @@ -61216,7 +61739,7 @@ snapshots: transitivePeerDependencies: - ts-node - tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)): + tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -61235,7 +61758,7 @@ snapshots: postcss: 8.4.49 postcss-import: 15.1.0(postcss@8.4.49) postcss-js: 4.1.0(postcss@8.4.49) - postcss-load-config: 4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2)) + postcss-load-config: 4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2)) postcss-nested: 6.2.0(postcss@8.4.49) postcss-selector-parser: 6.1.2 resolve: 1.22.8 @@ -61259,11 +61782,11 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.23 - postcss-import: 15.1.0(postcss@8.5.23) - postcss-js: 4.1.0(postcss@8.5.23) - postcss-load-config: 4.0.2(postcss@8.5.23)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@22.19.15)(typescript@6.0.3)) - postcss-nested: 6.2.0(postcss@8.5.23) + postcss: 8.5.24 + postcss-import: 15.1.0(postcss@8.5.24) + postcss-js: 4.1.0(postcss@8.5.24) + postcss-load-config: 4.0.2(postcss@8.5.24)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@22.19.15)(typescript@6.0.3)) + postcss-nested: 6.2.0(postcss@8.5.24) postcss-selector-parser: 6.1.2 resolve: 1.22.8 sucrase: 3.35.1 @@ -61286,11 +61809,11 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.23 - postcss-import: 15.1.0(postcss@8.5.23) - postcss-js: 4.1.0(postcss@8.5.23) - postcss-load-config: 4.0.2(postcss@8.5.23)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2)) - postcss-nested: 6.2.0(postcss@8.5.23) + postcss: 8.5.24 + postcss-import: 15.1.0(postcss@8.5.24) + postcss-js: 4.1.0(postcss@8.5.24) + postcss-load-config: 4.0.2(postcss@8.5.24)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2)) + postcss-nested: 6.2.0(postcss@8.5.24) postcss-selector-parser: 6.1.2 resolve: 1.22.8 sucrase: 3.35.1 @@ -61301,6 +61824,8 @@ snapshots: tapable@2.3.0: {} + tapable@2.3.3: {} + tar-fs@2.1.4: dependencies: chownr: 1.1.4 @@ -61622,15 +62147,12 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - tinyglobby@0.2.17: - dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 - tinypool@0.8.4: {} tinypool@1.1.1: {} + tinypool@2.1.0: {} + tinyrainbow@2.0.0: {} tinyspy@4.0.4: {} @@ -61846,7 +62368,7 @@ snapshots: chalk: 4.1.2 enhanced-resolve: 5.20.1 micromatch: 4.0.8 - semver: 7.8.5 + semver: 7.6.3 source-map: 0.7.6 typescript: 7.0.2 webpack: 5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(webpack-cli@5.1.4) @@ -61917,14 +62439,14 @@ snapshots: '@swc/core': 1.15.41(@swc/helpers@0.5.17) optional: true - ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.2.0)(typescript@7.0.2): + ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.17))(@types/node@26.1.0)(typescript@7.0.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 26.2.0 + '@types/node': 26.1.0 acorn: 8.17.0 acorn-walk: 8.3.5 arg: 4.1.3 @@ -61999,14 +62521,14 @@ snapshots: optionalDependencies: '@swc/core': 1.15.41(@swc/helpers@0.5.23) - ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@5.9.3): + ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 26.2.0 + '@types/node': 26.1.0 acorn: 8.17.0 acorn-walk: 8.3.5 arg: 4.1.3 @@ -62020,14 +62542,14 @@ snapshots: '@swc/core': 1.15.41(@swc/helpers@0.5.23) optional: true - ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.2.0)(typescript@7.0.2): + ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@26.1.0)(typescript@7.0.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 26.2.0 + '@types/node': 26.1.0 acorn: 8.17.0 acorn-walk: 8.3.5 arg: 4.1.3 @@ -62152,7 +62674,7 @@ snapshots: tsscmp@1.0.6: {} - tsup@7.3.0(@swc/core@1.15.41(@swc/helpers@0.5.23))(postcss@8.5.26)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(typescript@7.0.2): + tsup@7.3.0(@swc/core@1.15.41(@swc/helpers@0.5.23))(postcss@8.5.24)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2))(typescript@7.0.2): dependencies: bundle-require: 4.2.1(esbuild@0.19.12) cac: 6.7.14 @@ -62162,7 +62684,7 @@ snapshots: execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 - postcss-load-config: 4.0.2(postcss@8.5.26)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2)) + postcss-load-config: 4.0.2(postcss@8.5.24)(ts-node@10.9.2(@swc/core@1.15.41(@swc/helpers@0.5.23))(@types/node@20.19.5)(typescript@7.0.2)) resolve-from: 5.0.0 rollup: 4.59.0 source-map: 0.8.0-beta.0 @@ -62170,13 +62692,13 @@ snapshots: tree-kill: 1.2.2 optionalDependencies: '@swc/core': 1.15.41(@swc/helpers@0.5.23) - postcss: 8.5.26 + postcss: 8.5.24 typescript: 7.0.2 transitivePeerDependencies: - supports-color - ts-node - tsup@7.3.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(postcss@8.5.26)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@6.0.3))(typescript@6.0.3): + tsup@7.3.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(postcss@8.5.24)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@6.0.3))(typescript@6.0.3): dependencies: bundle-require: 4.2.1(esbuild@0.19.12) cac: 6.7.14 @@ -62186,7 +62708,7 @@ snapshots: execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 - postcss-load-config: 4.0.2(postcss@8.5.26)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@6.0.3)) + postcss-load-config: 4.0.2(postcss@8.5.24)(ts-node@10.9.1(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.19.5)(typescript@6.0.3)) resolve-from: 5.0.0 rollup: 4.59.0 source-map: 0.8.0-beta.0 @@ -62194,7 +62716,7 @@ snapshots: tree-kill: 1.2.2 optionalDependencies: '@swc/core': 1.7.26(@swc/helpers@0.5.13) - postcss: 8.5.26 + postcss: 8.5.24 typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -62234,6 +62756,8 @@ snapshots: type-detect@4.0.8: {} + type-detect@4.1.0: {} + type-fest@0.16.0: {} type-fest@0.20.2: {} @@ -62311,7 +62835,7 @@ snapshots: types-react-dom@19.0.0-rc.1: dependencies: - '@types/react': 19.2.14 + '@types/react': 18.3.28 types-react@19.0.0-rc.1: dependencies: @@ -62409,7 +62933,8 @@ snapshots: undici-types@6.21.0: {} - undici-types@8.3.0: {} + undici-types@8.3.0: + optional: true undici@5.26.5: dependencies: @@ -62889,37 +63414,15 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-node@3.2.4(@types/node@20.19.5)(jiti@2.6.1)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0): + vite-node@3.2.4(@types/node@20.19.5)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0): dependencies: cac: 6.7.14 debug: 4.4.3(supports-color@8.1.1) es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.6(@types/node@20.19.5)(jiti@2.6.1)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vite-node@3.2.4(@types/node@20.19.5)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0): - dependencies: - cac: 6.7.14 - debug: 4.4.3(supports-color@8.1.1) - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.3.6(@types/node@20.19.5)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0) + vite: 5.4.21(@types/node@20.19.5)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) transitivePeerDependencies: - '@types/node' - - jiti - less - lightningcss - sass @@ -62928,19 +63431,16 @@ snapshots: - sugarss - supports-color - terser - - tsx - - yaml - vite-node@3.2.4(@types/node@26.2.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0): + vite-node@3.2.4(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0): dependencies: cac: 6.7.14 debug: 4.4.3(supports-color@8.1.1) es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.6(@types/node@26.2.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0) + vite: 5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0) transitivePeerDependencies: - '@types/node' - - jiti - less - lightningcss - sass @@ -62949,8 +63449,6 @@ snapshots: - sugarss - supports-color - terser - - tsx - - yaml optional: true vite-tsconfig-paths@4.2.3(typescript@6.0.3)(vite@7.3.5(@types/node@20.19.5)(jiti@2.6.1)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0)): @@ -62977,45 +63475,41 @@ snapshots: sass-embedded: 1.100.0 terser: 5.48.0 - vite@5.4.21(@types/node@26.2.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0): + vite@5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0): dependencies: esbuild: 0.21.5 postcss: 8.4.49 rollup: 4.59.0 optionalDependencies: - '@types/node': 26.2.0 + '@types/node': 26.1.0 fsevents: 2.3.3 less: 4.6.4 sass: 1.100.0 sass-embedded: 1.100.0 terser: 5.48.0 - vite@7.3.5(@types/node@20.19.5)(jiti@2.6.1)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0): + vite@5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0): dependencies: - esbuild: 0.27.3 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.23 + esbuild: 0.21.5 + postcss: 8.4.49 rollup: 4.59.0 - tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 20.19.5 + '@types/node': 26.1.0 fsevents: 2.3.3 - jiti: 2.6.1 less: 4.6.4 - sass: 1.100.0 - sass-embedded: 1.100.0 + sass: 1.98.0 + sass-embedded: 1.98.0 terser: 5.48.0 - yaml: 2.9.0 + optional: true - vite@7.3.6(@types/node@20.19.5)(jiti@2.6.1)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0): + vite@7.3.5(@types/node@20.19.5)(jiti@2.6.1)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0): dependencies: - esbuild: 0.28.1 - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 - postcss: 8.5.23 - rollup: 4.62.2 - tinyglobby: 0.2.17 + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.24 + rollup: 4.59.0 + tinyglobby: 0.2.15 optionalDependencies: '@types/node': 20.19.5 fsevents: 2.3.3 @@ -63026,34 +63520,16 @@ snapshots: terser: 5.48.0 yaml: 2.9.0 - vite@7.3.6(@types/node@20.19.5)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0): + vite@7.3.5(@types/node@26.1.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0): dependencies: - esbuild: 0.28.1 - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 - postcss: 8.5.23 - rollup: 4.62.2 - tinyglobby: 0.2.17 - optionalDependencies: - '@types/node': 20.19.5 - fsevents: 2.3.3 - jiti: 2.7.0 - less: 4.6.4 - sass: 1.100.0 - sass-embedded: 1.100.0 - terser: 5.48.0 - yaml: 2.9.0 - - vite@7.3.6(@types/node@26.2.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0): - dependencies: - esbuild: 0.28.1 - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 - postcss: 8.5.23 - rollup: 4.62.2 - tinyglobby: 0.2.17 + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.24 + rollup: 4.59.0 + tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 26.2.0 + '@types/node': 26.1.0 fsevents: 2.3.3 jiti: 2.7.0 less: 4.6.4 @@ -63063,55 +63539,11 @@ snapshots: yaml: 2.9.0 optional: true - vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@20.19.5)(jiti@2.6.1)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0): - dependencies: - '@types/chai': 5.2.3 - '@vitest/expect': 3.2.6 - '@vitest/mocker': 3.2.6(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(vite@7.3.6(@types/node@20.19.5)(jiti@2.6.1)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0)) - '@vitest/pretty-format': 3.2.7 - '@vitest/runner': 3.2.6 - '@vitest/snapshot': 3.2.6 - '@vitest/spy': 3.2.6 - '@vitest/utils': 3.2.6 - chai: 5.3.3 - debug: 4.4.3(supports-color@8.1.1) - expect-type: 1.4.0 - magic-string: 0.30.21 - pathe: 2.0.3 - picomatch: 4.0.5 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.17 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 7.3.6(@types/node@20.19.5)(jiti@2.6.1)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0) - vite-node: 3.2.4(@types/node@20.19.5)(jiti@2.6.1)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@edge-runtime/vm': 3.2.0 - '@types/debug': 4.1.12 - '@types/node': 20.19.5 - jsdom: 20.0.3 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@20.19.5)(jiti@2.7.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0): + vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@20.19.5)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.6 - '@vitest/mocker': 3.2.6(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(vite@7.3.6(@types/node@20.19.5)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0)) + '@vitest/mocker': 3.2.6(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(vite@5.4.21(@types/node@20.19.5)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)) '@vitest/pretty-format': 3.2.7 '@vitest/runner': 3.2.6 '@vitest/snapshot': 3.2.6 @@ -63122,15 +63554,15 @@ snapshots: expect-type: 1.4.0 magic-string: 0.30.21 pathe: 2.0.3 - picomatch: 4.0.5 + picomatch: 4.0.3 std-env: 3.10.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.17 + tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.3.6(@types/node@20.19.5)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0) - vite-node: 3.2.4(@types/node@20.19.5)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0) + vite: 5.4.21(@types/node@20.19.5)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) + vite-node: 3.2.4(@types/node@20.19.5)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0) why-is-node-running: 2.3.0 optionalDependencies: '@edge-runtime/vm': 3.2.0 @@ -63138,7 +63570,6 @@ snapshots: '@types/node': 20.19.5 jsdom: 20.0.3 transitivePeerDependencies: - - jiti - less - lightningcss - msw @@ -63148,14 +63579,12 @@ snapshots: - sugarss - supports-color - terser - - tsx - - yaml - vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.2.0)(jiti@2.7.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0): + vitest@3.2.6(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@26.1.0)(jsdom@20.0.3)(less@4.6.4)(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.6 - '@vitest/mocker': 3.2.6(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(vite@7.3.6(@types/node@26.2.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0)) + '@vitest/mocker': 3.2.6(msw@1.3.5(@types/node@20.19.5)(encoding@0.1.13)(typescript@6.0.3))(vite@5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)) '@vitest/pretty-format': 3.2.7 '@vitest/runner': 3.2.6 '@vitest/snapshot': 3.2.6 @@ -63166,23 +63595,22 @@ snapshots: expect-type: 1.4.0 magic-string: 0.30.21 pathe: 2.0.3 - picomatch: 4.0.5 + picomatch: 4.0.3 std-env: 3.10.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.17 + tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.3.6(@types/node@26.2.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0) - vite-node: 3.2.4(@types/node@26.2.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0)(yaml@2.9.0) + vite: 5.4.21(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0) + vite-node: 3.2.4(@types/node@26.1.0)(less@4.6.4)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.48.0) why-is-node-running: 2.3.0 optionalDependencies: '@edge-runtime/vm': 3.2.0 '@types/debug': 4.1.12 - '@types/node': 26.2.0 + '@types/node': 26.1.0 jsdom: 20.0.3 transitivePeerDependencies: - - jiti - less - lightningcss - msw @@ -63192,8 +63620,6 @@ snapshots: - sugarss - supports-color - terser - - tsx - - yaml optional: true vlq@1.0.1: {} @@ -63213,7 +63639,7 @@ snapshots: espree: 9.6.1 esquery: 1.7.0 lodash: 4.18.1 - semver: 7.8.5 + semver: 7.6.3 transitivePeerDependencies: - supports-color @@ -63292,6 +63718,8 @@ snapshots: dependencies: makeerror: 1.0.12 + wasm-feature-detect@1.8.0: {} + watchpack@2.5.1: dependencies: glob-to-regexp: 0.4.1 @@ -63637,8 +64065,6 @@ snapshots: webpack-sources@3.5.0: {} - webpack-sources@3.5.1: {} - webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.6(@rspack/core@1.6.8(@swc/helpers@0.5.23))(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(webpack-cli@5.1.4)))(webpack@5.104.1(@swc/core@1.15.41(@swc/helpers@0.5.23))(esbuild@0.25.5)(webpack-cli@5.1.4)): dependencies: typed-assert: 1.0.9 @@ -64136,8 +64562,8 @@ snapshots: with@7.0.2: dependencies: - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 assert-never: 1.4.0 babel-walk: 3.0.0-canary-5 @@ -64201,10 +64627,7 @@ snapshots: ws@8.21.0: {} - ws@8.21.2: {} - - ws@8.21.3: - optional: true + ws@8.21.3: {} wsl-utils@0.1.0: dependencies: @@ -64321,15 +64744,6 @@ snapshots: y18n: 5.0.8 yargs-parser: 22.0.0 - yargs@18.1.0: - dependencies: - cliui: 9.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - string-width: 8.2.2 - y18n: 5.0.8 - yargs-parser: 22.0.0 - yauzl-clone@1.0.4: dependencies: events-intercept: 2.0.0 diff --git a/tools/scripts/ci-e2e-suites.mjs b/tools/scripts/ci-e2e-suites.mjs index 84bacd4d154..b7eefcb23f8 100644 --- a/tools/scripts/ci-e2e-suites.mjs +++ b/tools/scripts/ci-e2e-suites.mjs @@ -12,6 +12,10 @@ export const E2E_SUITE_DEFINITIONS = { 'tools/scripts/run-manifest-e2e.mjs', ], }, + lynx: { + appNames: ['lynx-module-federation-demo'], + inputs: ['.github/workflows/e2e-lynx.yml'], + }, metro: { appNames: ['example-host'], inputs: [ diff --git a/tools/scripts/ci-local.mjs b/tools/scripts/ci-local.mjs index cd35d74bcff..6dcd2473444 100644 --- a/tools/scripts/ci-local.mjs +++ b/tools/scripts/ci-local.mjs @@ -219,6 +219,54 @@ const jobs = [ ), ], }, + { + name: 'e2e-lynx', + env: SKIP_DEVTOOLS_POSTINSTALL_ENV, + steps: [ + ...e2eSetupSteps('lynx', { cypress: false }), + step('Test Lynx federation compiler and transport', (ctx) => + runWhenAffected(ctx, () => + runCommand( + 'pnpm', + ['--filter', '@module-federation/lynx', 'test'], + ctx, + ), + ), + ), + step('Build and validate native Lynx artifacts', (ctx) => + runWhenAffected(ctx, () => + runCommand( + 'pnpm', + ['--filter', 'lynx-module-federation-demo', 'run', 'e2e:native:ci'], + ctx, + ), + ), + ), + step('Validate standalone iOS project policy', (ctx) => + runWhenAffected(ctx, () => + runCommand( + 'pnpm', + [ + '--filter', + 'lynx-module-federation-demo', + 'run', + 'test:ios-project', + ], + ctx, + ), + ), + ), + step('Run real Lynx for Web E2E', (ctx) => + runWhenAffected(ctx, () => + runCommand( + 'pnpm', + ['--filter', 'lynx-module-federation-demo', 'run', 'e2e:web:ci'], + ctx, + ), + ), + ), + ], + }, { name: 'e2e-manifest', env: SKIP_DEVTOOLS_POSTINSTALL_ENV, diff --git a/tools/scripts/publish-pkg-pr-new-previews.mjs b/tools/scripts/publish-pkg-pr-new-previews.mjs index 942dfb4d8ea..43bf7e0d235 100644 --- a/tools/scripts/publish-pkg-pr-new-previews.mjs +++ b/tools/scripts/publish-pkg-pr-new-previews.mjs @@ -302,6 +302,7 @@ function publishPkgPrNewPreviews(paths) { function isRetriablePkgPrFailure(output) { return ( /Publishing failed \((5\d\d|429)\)/.test(output) || + /Check failed \(404\):.*There is no workflow defined for/.test(output) || /Cloudflare|Internal Server Error|Bad Gateway|Gateway Timeout/.test(output) ); }