diff --git a/.buckconfig b/.buckconfig index 9e8266bd..476f4149 100644 --- a/.buckconfig +++ b/.buckconfig @@ -33,7 +33,8 @@ ignore = .git,node_modules,dist,.nx,.buck-out,target [build_mode.debug] - compiler_flags = -O0 -g3 -DDEBUG + # Maximally fast iteration: no LTO, minimal debug info, no strip. + compiler_flags = -O0 -g0 -DDEBUG [build_mode.release] compiler_flags = -Oz -flto=thin -DNDEBUG -fvisibility=hidden diff --git a/.cursorrules b/.cursorrules index 509a085b..43bde8b5 100644 --- a/.cursorrules +++ b/.cursorrules @@ -1,8 +1,8 @@ All project guidance for AI agents lives in AGENTS.md — read it before -working in this repo. It covers Nx task conventions, the NativeScript -plugins, native build pipelines (prebuilt xcframeworks, cargo-ndk, -Buck2 via nx-buck2), testing layers, and the code-review-graph MCP -tools that should be used before Grep/Glob/Read. +working in this repo. Durable gotchas live in MEMORY.md. They cover Nx +task conventions, the NativeScript plugins, native build pipelines +(prebuilt xcframeworks, cargo-ndk, Buck2 via nx-buck2), testing layers, +and the code-review-graph MCP tools (review-only). This file exists only so tools that look for .cursorrules find a path to the canonical guidance. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 18a38d30..78f325be 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,5 +1,8 @@ # GitHub Copilot Instructions +Canonical agent guidance is [`AGENTS.md`](../AGENTS.md). Durable gotchas +are [`MEMORY.md`](../MEMORY.md). Read both before editing. + ## Code Review The following paths contain **vendored third-party sources** and should be excluded from code review: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2b395ec..8ed79031 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,553 +1,618 @@ -name: CI - -on: - push: - branches: - - main - pull_request: - -permissions: - actions: read - contents: read - pull-requests: write - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -jobs: - main: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - with: - filter: tree:0 - fetch-depth: 0 - - # pnpm is the default package manager (packageManager in package.json); - # pnpm/action-setup installs the pinned version from there. - - uses: pnpm/action-setup@v4 - - # Cache node_modules - - uses: actions/setup-node@v5 - with: - node-version: 24 - cache: 'pnpm' - - - run: pnpm install --frozen-lockfile - - # This enables task distribution via Nx Cloud. - # Learn more at https://nx.dev/docs/reference/nx-cloud-cli#npx-nxcloud-startcirun - - run: pnpm exec nx start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="e2e" - - # Prepend any command with "nx record --" to record its logs to Nx Cloud - # - run: pnpm exec nx record -- pnpm exec nx format:check --base="remotes/origin/main" - # - run: pnpm exec nx run-many -t lint test build typecheck e2e - # Nx Cloud recommends fixes for failures to help you get CI green faster. Learn more: https://nx.dev/docs/features/ci-features/self-healing-ci - # - run: pnpm exec nx fix-ci - # if: always() - # - run: pnpm exec nx format:check --base="remotes/origin/main" - - # Vitest, TypeScript and the Rust fixture crate. Everything here runs on a - # plain Linux runner: the .wasm fixtures are committed build outputs, so no - # wasm-pack, Android SDK or Xcode is needed. Rust comes preinstalled on the - # GitHub-hosted ubuntu image. - # - # The test app's own suite is not here — it runs on a simulator/emulator, in - # the wasm-test-ios and wasm-test-android jobs below. - unit-tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - - uses: pnpm/action-setup@v4 - - - uses: actions/setup-node@v5 - with: - node-version: 24 - cache: 'pnpm' - cache-dependency-path: | - pnpm-lock.yaml - apps/ns-wasm-test/pnpm-lock.yaml - apps/ns-wry-app/pnpm-lock.yaml - - - run: pnpm install --frozen-lockfile - - # Both apps resolve the plugins through file: dependencies and import - # their built dist/. pnpm snapshots file: deps into the app's - # node_modules at install time, so dist must already exist before the - # apps are installed — build it first, mirroring the device jobs below. - - name: Build the plugins - run: pnpm exec nx run-many -t build - - # The NativeScript app is not a pnpm workspace member (it has its own - # pnpm-workspace.yaml) — the CLI needs its own node_modules — and its - # typecheck resolves @nativescript/types, Vitest and TypeScript - # from there. - - name: Install the test app's dependencies - working-directory: apps/ns-wasm-test - run: pnpm install --frozen-lockfile - - # The wry demo app is not a workspace member either; its typecheck - # resolves @nativescript/core and @cross-code/ns-wry from its own - # node_modules, which is never installed otherwise. - - name: Install the wry demo app's dependencies - working-directory: apps/ns-wry-app - run: pnpm install --frozen-lockfile - - # Vitest specs plus the typecheck of all projects — including the test - # app's NativeScript specs, which are type-checked - # here even though they can only run on a device. - - name: Vitest + typecheck - run: pnpm exec nx run-many -t test typecheck - - # Code coverage per project: vitest (v8) for the TS packages, llvm-cov - # for the Rust fixture. Reports land in /test-output/vitest/coverage - # and /target/coverage (both gitignored). Native (Swift/Kotlin) - # coverage runs via `nx run-many -t coverage.ios coverage.android` on a - # machine with Xcode/JDK — not in this job. - - name: Coverage - run: | - rustup component add llvm-tools-preview - pnpm exec nx run-many -t coverage - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: | - packages/ns-wasm-core/test-output/vitest/coverage/lcov.info - packages/ns-wasm3/test-output/vitest/coverage/lcov.info - packages/ns-wamr/test-output/vitest/coverage/lcov.info - packages/ns-wasm-kit-runtime/test-output/vitest/coverage/lcov.info - packages/ns-wasm-chicory/test-output/vitest/coverage/lcov.info - packages/ns-wasm-edge/test-output/vitest/coverage/lcov.info - packages/ns-endive/test-output/vitest/coverage/lcov.info - packages/ns-wry/test-output/vitest/coverage/lcov.info - packages/vitest-ns/test-output/vitest/coverage/lcov.info - packages/vitest-ns-ui/test-output/vitest/coverage/lcov.info - packages/ns-wasm-fixture/target/coverage/lcov.info - - # Rust is pre-installed on GitHub's ubuntu-latest runner. When debugging - # locally with `act` the slim image may not have it — install on the fly - # so the same workflow is self-contained. - - name: Install Rust (if missing) - run: | - if ! command -v cargo >/dev/null 2>&1; then - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal - echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - fi - - # The fixture crate: wasm exports plus the globals.wasm byte encoder. - - name: Rust unit tests - run: pnpm exec nx run ns-wasm-fixture:test.wasm - - # Clippy + rustfmt over the hand-written Rust in all four Rust projects - # (ns-wasm3, ns-wamr, ns-wry, ns-wasm-fixture). Generated bindings - # (bindgen output, UniFFI scaffolding) are excluded: bindgen output is - # `#![rustfmt::skip]` and `clippy::all` is allowed around the includes. - - name: Rust lint (Clippy) + format check - run: | - rustup component add clippy rustfmt - pnpm exec nx run-many -t lint.rust fmt.rust -p ns-wasm3 ns-wamr ns-wry ns-wasm-fixture - - # globals.wasm is committed but generated, and the generator is - # deterministic — so a stale copy is a hard error rather than a surprise - # on someone's device. (test_types_bg.wasm needs wasm-pack to rebuild and - # is not byte-reproducible, so it is not checked here.) - - name: Verify the committed globals.wasm matches its generator - run: | - cargo run --quiet \ - --manifest-path packages/ns-wasm-fixture/src/test-types/Cargo.toml \ - --bin gen_globals -- "$RUNNER_TEMP/globals" - cmp "$RUNNER_TEMP/globals/globals.wasm" \ - packages/ns-wasm-fixture/src/test-types/pkg/globals.wasm || { - echo "::error::globals.wasm is stale — run 'pnpm run build.wasm' in packages/ns-wasm-fixture and commit the result." - exit 1 - } - - # The ns-wasm3 native suites below need a macOS runner / an Android - # SDK, so they only run when the plugin or this workflow changed. Fails open: - # any detection problem runs them anyway. - changes: - runs-on: ubuntu-latest - outputs: - wasm3: ${{ steps.filter.outputs.wasm3 }} - wamr: ${{ steps.filter.outputs.wamr }} - app: ${{ steps.filter.outputs.app }} - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - id: filter - shell: bash - run: | - run_all() { - { - echo "wasm3=true" - echo "wamr=true" - echo "app=true" - } >> "$GITHUB_OUTPUT" - exit 0 - } - [ "${{ github.event_name }}" = "pull_request" ] || run_all - - base="refs/remotes/origin/${{ github.base_ref }}" - git fetch --no-tags origin \ - "+refs/heads/${{ github.base_ref }}:$base" || run_all - changed="$(git diff --name-only "$base...HEAD")" || run_all - - echo "$changed" - if grep -qE '^(packages/ns-wasm3/|packages/ns-wasm-core/|\.github/workflows/ci\.yml$)' <<<"$changed"; then - echo "wasm3=true" >> "$GITHUB_OUTPUT" - else - echo "wasm3=false" >> "$GITHUB_OUTPUT" - fi - - if grep -qE '^(packages/ns-wamr/|packages/ns-wasm-core/|\.github/workflows/ci\.yml$)' <<<"$changed"; then - echo "wamr=true" >> "$GITHUB_OUTPUT" - else - echo "wamr=false" >> "$GITHUB_OUTPUT" - fi - - # The app's suite runs the plugin end to end, so a plugin or fixture - # change is as relevant to it as a change to the app itself. - if grep -qE '^(apps/ns-wasm-test/|packages/ns-wasm3/|packages/ns-wamr/|packages/ns-wasm-kit-runtime/|packages/ns-endive/|packages/ns-wasm-chicory/|packages/ns-wasm-edge/|packages/ns-wasm-core/|packages/ns-wasm-fixture/|\.github/workflows/ci\.yml$)' <<<"$changed"; then - echo "app=true" >> "$GITHUB_OUTPUT" - else - echo "app=false" >> "$GITHUB_OUTPUT" - fi - - # iOS: compiles the vendored wasm3 C sources through SwiftPM and runs the - # XCTest suite, which exercises the interpreter natively on macOS. - wasm3-ios: - needs: changes - if: needs.changes.outputs.wasm3 == 'true' - runs-on: macos-latest - steps: - - uses: actions/checkout@v5 - - - uses: pnpm/action-setup@v4 - - - uses: actions/setup-node@v5 - with: - node-version: 24 - package-manager-cache: false - - - name: Verify the synced Swift package copy is current - working-directory: packages/ns-wasm3 - run: | - node tools/sync-wasm3.mjs - git diff --exit-code -- . || { - echo "::error::platforms/ios/NSCWasm3/Sources/CWasm3 is stale — run 'pnpm run sync.vendors' and commit the result." - exit 1 - } - - - name: Swift tests - working-directory: packages/ns-wasm3/platforms/ios/NSCWasm3 - run: swift test --disable-sandbox - - - name: Install SwiftLint and Periphery - run: | - brew install swiftlint - brew install peripheryapp/periphery/periphery - - - name: SwiftLint - working-directory: packages/ns-wasm3/platforms/ios/NSCWasm3 - run: swiftlint lint --config ../../../../../.swiftlint.yml Sources Tests - - - name: Periphery (unused code) - working-directory: packages/ns-wasm3/platforms/ios/NSCWasm3 - run: periphery scan --config ../../../../../.periphery.yml --disable-update-check - - - name: Build for iOS device - working-directory: packages/ns-wasm3/platforms/ios/NSCWasm3 - run: | - xcodebuild build \ - -scheme NSCWasm3 \ - -destination 'generic/platform=iOS' - - # Android: builds the wasm3-jni Rust crate via cargo and runs the Kotlin - # wrapper's JUnit hosttest suite — no emulator needed. - wasm3-android: - needs: changes - if: needs.changes.outputs.wasm3 == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - # Gradle shells out to build-native.mjs (zx) to generate the bindings and - # compile wasm3, so the native build needs Node and the root node_modules. - - uses: pnpm/action-setup@v4 - - - uses: actions/setup-node@v5 - with: - node-version: 24 - cache: 'pnpm' - - - run: pnpm install --frozen-lockfile - - - uses: actions/setup-java@v4.9.0 - with: - distribution: temurin - java-version: '21' - - - uses: gradle/actions/setup-gradle@v4.4.4 - with: - validate-wrappers: false - - # Rust is pre-installed on ubuntu-latest; install if missing (e.g. act). - - name: Install Rust (if missing) - run: | - if ! command -v cargo >/dev/null 2>&1; then - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal - echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - fi - - - name: JVM host tests (cargo build + Kotlin wrapper) - working-directory: packages/ns-wasm3/platforms/android/wasm3-android - run: ./gradlew :hosttest:test - - - name: Kotlin lint (Detekt + Ktlint) - working-directory: packages/ns-wasm3/platforms/android/wasm3-android - run: ./gradlew detekt ktlintCheck - - - uses: actions/upload-artifact@v4 - if: failure() - with: - name: wasm3-android-test-report - path: packages/ns-wasm3/platforms/android/wasm3-android/hosttest/build/reports/tests/test - - # The test app's Vitest suite, on a simulator and an emulator. This is the only - # place the plugin's TypeScript adapters run against the real native layer — - # the NSData/NSArray marshalling on iOS, the signed byte[] handling on Android - # — so it covers what neither the Swift/Kotlin suites nor a Node runner can. - # iOS: compiles the vendored WAMR C sources through SwiftPM and runs the - # XCTest suite, which exercises the runtime natively on macOS. - wamr-ios: - needs: changes - if: needs.changes.outputs.wamr == 'true' - runs-on: macos-latest - steps: - - uses: actions/checkout@v5 - - - uses: pnpm/action-setup@v4 - - - uses: actions/setup-node@v5 - with: - node-version: 24 - package-manager-cache: false - - - name: Check for WAMR C sources - id: sources - run: | - # -print -quit: stop after the first match so find exits cleanly — - # under pipefail, grep -q exiting early would SIGPIPE find and flip - # the result to "no sources", silently skipping the job. - if find packages/ns-wamr/src/vendors/wamr \ - \( -name '*.c' -o -name '*.h' \) -print -quit | grep -q .; then - echo "present=true" >> "$GITHUB_OUTPUT" - else - echo "present=false" >> "$GITHUB_OUTPUT" - echo "::warning::WAMR C sources not found — Swift steps will be skipped." - fi - - - name: Verify the synced Swift package copy is current - if: steps.sources.outputs.present == 'true' - working-directory: packages/ns-wamr - run: | - node tools/sync-wamr.mjs - git diff --exit-code -- . || { - echo "::error::platforms/ios/NSCWamr/Sources/CWamr is stale — run 'pnpm run sync.vendors' and commit the result." - exit 1 - } - - - name: Swift tests - if: steps.sources.outputs.present == 'true' - working-directory: packages/ns-wamr/platforms/ios/NSCWamr - run: swift test --disable-sandbox - - - name: Install SwiftLint and Periphery - if: steps.sources.outputs.present == 'true' - run: | - brew install swiftlint - brew install peripheryapp/periphery/periphery - - - name: SwiftLint - if: steps.sources.outputs.present == 'true' - working-directory: packages/ns-wamr/platforms/ios/NSCWamr - run: swiftlint lint --config ../../../../../.swiftlint.yml Sources Tests - - - name: Periphery (unused code) - if: steps.sources.outputs.present == 'true' - working-directory: packages/ns-wamr/platforms/ios/NSCWamr - run: periphery scan --config ../../../../../.periphery.yml --disable-update-check - - - name: Build for iOS device - if: steps.sources.outputs.present == 'true' - working-directory: packages/ns-wamr/platforms/ios/NSCWamr - run: | - xcodebuild build \ - -scheme NSCWamr \ - -destination 'generic/platform=iOS' - - # Android: builds the wamr-jni Rust crate via cargo and runs the Kotlin - # wrapper's JUnit hosttest suite — no emulator. - wamr-android: - needs: changes - if: needs.changes.outputs.wamr == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - - uses: pnpm/action-setup@v4 - - - uses: actions/setup-node@v5 - with: - node-version: 24 - cache: 'pnpm' - - - run: pnpm install --frozen-lockfile - - - name: Check for WAMR C sources - id: sources - run: | - # -print -quit: stop after the first match so find exits cleanly — - # under pipefail, grep -q exiting early would SIGPIPE find and flip - # the result to "no sources", silently skipping the job. - if find packages/ns-wamr/src/vendors/wamr \ - \( -name '*.c' -o -name '*.h' \) -print -quit | grep -q .; then - echo "present=true" >> "$GITHUB_OUTPUT" - else - echo "present=false" >> "$GITHUB_OUTPUT" - echo "::warning::WAMR C sources not found — Gradle steps will be skipped." - fi - - - uses: actions/setup-java@v4.9.0 - if: steps.sources.outputs.present == 'true' - with: - distribution: temurin - java-version: '21' - - - uses: gradle/actions/setup-gradle@v4.4.4 - if: steps.sources.outputs.present == 'true' - with: - validate-wrappers: false - - # Rust is pre-installed on ubuntu-latest; install if missing (e.g. act). - - name: Install Rust (if missing) - if: steps.sources.outputs.present == 'true' - run: | - if ! command -v cargo >/dev/null 2>&1; then - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal - echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - fi - - - name: JVM host tests (cargo build + Kotlin wrapper) - if: steps.sources.outputs.present == 'true' - working-directory: packages/ns-wamr/platforms/android/wamr-android - run: ./gradlew :hosttest:test - - - name: Kotlin lint (Detekt + Ktlint) - if: steps.sources.outputs.present == 'true' - working-directory: packages/ns-wamr/platforms/android/wamr-android - run: ./gradlew detekt ktlintCheck - - - uses: actions/upload-artifact@v4 - if: failure() && steps.sources.outputs.present == 'true' - with: - name: wamr-android-test-report - path: packages/ns-wamr/platforms/android/wamr-android/hosttest/build/reports/tests/test - - # The test app's Vitest suite, on a simulator and an emulator. This is the only - # place the plugin's TypeScript adapters run against the real native layer — - # the NSData/NSArray marshalling on iOS, the signed byte[] handling on Android - # — so it covers what neither the Swift/Kotlin suites nor a Node runner can. - wasm-test-ios: - needs: changes - if: needs.changes.outputs.app == 'true' - runs-on: macos-latest - steps: - - uses: actions/checkout@v5 - - - uses: pnpm/action-setup@v4 - - - uses: actions/setup-node@v5 - with: - node-version: 24 - cache: 'pnpm' - cache-dependency-path: | - pnpm-lock.yaml - apps/ns-wasm-test/pnpm-lock.yaml - - - run: pnpm install --frozen-lockfile - - # The app snapshots local file: dependencies when pnpm installs them, so - # every package whose entry point lives in dist/ must be built first. - - name: Build the app's local packages - run: pnpm exec nx run-many -t build -p ns-wasm-core ns-wasm3 ns-wamr ns-wasm-kit-runtime ns-endive ns-wasm-chicory ns-wasm-edge vitest-ns vitest-ns-ui - - - name: Install the test app's dependencies - working-directory: apps/ns-wasm-test - run: pnpm install --frozen-lockfile - - # `ns doctor` shells out to `pod --version`, which prints a locale warning - # the CLI treats as a failed CocoaPods check unless LANG is a UTF-8 one. - # The plugin itself needs no pods — it ships a Swift package. - - name: Vitest specs on the iOS Simulator - working-directory: . - env: - LANG: en_US.UTF-8 - run: pnpm exec nx run ns-wasm-test:test.ios - - wasm-test-android: - needs: changes - if: needs.changes.outputs.app == 'true' - continue-on-error: true - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - - uses: pnpm/action-setup@v4 - - - uses: actions/setup-node@v5 - with: - node-version: 24 - cache: 'pnpm' - cache-dependency-path: | - pnpm-lock.yaml - apps/ns-wasm-test/pnpm-lock.yaml - - - run: pnpm install --frozen-lockfile - - # Keep this before the app install: pnpm snapshots local file: packages, - # including their generated dist/ entry points, into app node_modules. - - name: Build the app's local packages - run: pnpm exec nx run-many -t build -p ns-wasm-core ns-wasm3 ns-wamr ns-wasm-kit-runtime ns-endive ns-wasm-chicory ns-wasm-edge vitest-ns vitest-ns-ui - - - name: Install the test app's dependencies - working-directory: apps/ns-wasm-test - run: pnpm install --frozen-lockfile - - - uses: actions/setup-java@v4.9.0 - with: - distribution: temurin - java-version: '21' - - # Lets the emulator use hardware acceleration on the GitHub runner. - - name: Enable KVM - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ - | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - # Both plugins ship prebuilt .aar files with their .so files. - - name: Vitest specs on the Android emulator - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: 34 - arch: x86_64 - working-directory: . - script: pnpm exec nx run ns-wasm-test:test.android +name: CI + +on: + push: + branches: + - main + pull_request: + +permissions: + actions: read + contents: read + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + main: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + filter: tree:0 + fetch-depth: 0 + + # pnpm is the default package manager (packageManager in package.json); + # pnpm/action-setup installs the pinned version from there. + - uses: pnpm/action-setup@v4 + + # Cache node_modules + - uses: actions/setup-node@v5 + with: + node-version: 24 + cache: 'pnpm' + + - run: pnpm install --frozen-lockfile + + # This enables task distribution via Nx Cloud. + # Learn more at https://nx.dev/docs/reference/nx-cloud-cli#npx-nxcloud-startcirun + - run: pnpm exec nx start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="e2e" + + # Prepend any command with "nx record --" to record its logs to Nx Cloud + # - run: pnpm exec nx record -- pnpm exec nx format:check --base="remotes/origin/main" + # - run: pnpm exec nx run-many -t lint test build typecheck e2e + # Nx Cloud recommends fixes for failures to help you get CI green faster. Learn more: https://nx.dev/docs/features/ci-features/self-healing-ci + # - run: pnpm exec nx fix-ci + # if: always() + # - run: pnpm exec nx format:check --base="remotes/origin/main" + + # Vitest, TypeScript and the Rust fixture crate. Everything here runs on a + # plain Linux runner: the .wasm fixtures are committed build outputs, so no + # wasm-pack, Android SDK or Xcode is needed. Rust comes preinstalled on the + # GitHub-hosted ubuntu image. + # + # The test app's own suite is not here — it runs on a simulator/emulator, in + # the wasm-test-ios and wasm-test-android jobs below. + unit-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v5 + with: + node-version: 24 + cache: 'pnpm' + cache-dependency-path: | + pnpm-lock.yaml + apps/ns-wasm-test/pnpm-lock.yaml + apps/ns-wry-app/pnpm-lock.yaml + apps/ns-lynx-app/pnpm-lock.yaml + + - run: pnpm install --frozen-lockfile + + # Nested NS apps snapshot workspace: deps into their own lockfiles. A dep + # change in packages/* must be followed by `pnpm install` in each app. + - name: Verify nested app lockfiles + run: pnpm run verify:lockfiles + + # Both apps resolve the plugins through file: dependencies and import + # their built dist/. pnpm snapshots file: deps into the app's + # node_modules at install time, so dist must already exist before the + # apps are installed — build it first, mirroring the device jobs below. + - name: Build the plugins + run: pnpm exec nx run-many -t build + + # The NativeScript app is not a pnpm workspace member (it has its own + # pnpm-workspace.yaml) — the CLI needs its own node_modules — and its + # typecheck resolves @nativescript/types, Vitest and TypeScript + # from there. + - name: Install the test app's dependencies + working-directory: apps/ns-wasm-test + run: pnpm install --frozen-lockfile + + # The wry demo app is not a workspace member either; its typecheck + # resolves @nativescript/core and @cross-code/ns-wry from its own + # node_modules, which is never installed otherwise. + - name: Install the wry demo app's dependencies + working-directory: apps/ns-wry-app + run: pnpm install --frozen-lockfile + + # Vitest specs plus the typecheck of all projects — including the test + # app's NativeScript specs, which are type-checked + # here even though they can only run on a device. + - name: Vitest + typecheck + run: pnpm exec nx run-many -t test typecheck + + # Code coverage per project: vitest (v8) for the TS packages, llvm-cov + # for the Rust fixture. Reports land in /test-output/vitest/coverage + # and /target/coverage (both gitignored). Native (Swift/Kotlin) + # coverage runs via `nx run-many -t coverage.ios coverage.android` on a + # machine with Xcode/JDK — not in this job. + - name: Coverage + run: | + rustup component add llvm-tools-preview + pnpm exec nx run-many -t coverage + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: | + packages/ns-wasm-core/test-output/vitest/coverage/lcov.info + packages/ns-wasm3/test-output/vitest/coverage/lcov.info + packages/ns-wamr/test-output/vitest/coverage/lcov.info + packages/ns-wasm-kit-runtime/test-output/vitest/coverage/lcov.info + packages/ns-wasm-chicory/test-output/vitest/coverage/lcov.info + packages/ns-wasm-edge/test-output/vitest/coverage/lcov.info + packages/ns-endive/test-output/vitest/coverage/lcov.info + packages/ns-wry/test-output/vitest/coverage/lcov.info + packages/ns-lynx/test-output/vitest/coverage/lcov.info + packages/ns-rstest/test-output/vitest/coverage/lcov.info + packages/ns-wasm-fixture/target/coverage/lcov.info + + # Rust is pre-installed on GitHub's ubuntu-latest runner. When debugging + # locally with `act` the slim image may not have it — install on the fly + # so the same workflow is self-contained. + - name: Install Rust (if missing) + run: | + if ! command -v cargo >/dev/null 2>&1; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + fi + + # The fixture crate: wasm exports plus the globals.wasm byte encoder. + - name: Rust unit tests + run: pnpm exec nx run ns-wasm-fixture:test.wasm + + # Clippy + rustfmt over the hand-written Rust in all four Rust projects + # (ns-wasm3, ns-wamr, ns-wry, ns-wasm-fixture). Generated bindings + # (bindgen output, UniFFI scaffolding) are excluded: bindgen output is + # `#![rustfmt::skip]` and `clippy::all` is allowed around the includes. + - name: Rust lint (Clippy) + format check + run: | + rustup component add clippy rustfmt + pnpm exec nx run-many -t lint.rust fmt.rust -p ns-wasm3 ns-wamr ns-wry ns-wasm-fixture + + # globals.wasm is committed but generated, and the generator is + # deterministic — so a stale copy is a hard error rather than a surprise + # on someone's device. (test_types_bg.wasm needs wasm-pack to rebuild and + # is not byte-reproducible, so it is not checked here.) + - name: Verify the committed globals.wasm matches its generator + run: | + cargo run --quiet \ + --manifest-path packages/ns-wasm-fixture/src/test-types/Cargo.toml \ + --bin gen_globals -- "$RUNNER_TEMP/globals" + cmp "$RUNNER_TEMP/globals/globals.wasm" \ + packages/ns-wasm-fixture/src/test-types/pkg/globals.wasm || { + echo "::error::globals.wasm is stale — run 'pnpm run build.wasm' in packages/ns-wasm-fixture and commit the result." + exit 1 + } + + # The ns-wasm3 native suites below need a macOS runner / an Android + # SDK, so they only run when the plugin or this workflow changed. Fails open: + # any detection problem runs them anyway. + changes: + runs-on: ubuntu-latest + outputs: + wasm3: ${{ steps.filter.outputs.wasm3 }} + wamr: ${{ steps.filter.outputs.wamr }} + app: ${{ steps.filter.outputs.app }} + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - id: filter + shell: bash + run: | + run_all() { + { + echo "wasm3=true" + echo "wamr=true" + echo "app=true" + } >> "$GITHUB_OUTPUT" + exit 0 + } + [ "${{ github.event_name }}" = "pull_request" ] || run_all + + base="refs/remotes/origin/${{ github.base_ref }}" + git fetch --no-tags origin \ + "+refs/heads/${{ github.base_ref }}:$base" || run_all + changed="$(git diff --name-only "$base...HEAD")" || run_all + + echo "$changed" + if grep -qE '^(packages/ns-wasm3/|packages/ns-wasm-core/|\.github/workflows/ci\.yml$)' <<<"$changed"; then + echo "wasm3=true" >> "$GITHUB_OUTPUT" + else + echo "wasm3=false" >> "$GITHUB_OUTPUT" + fi + + if grep -qE '^(packages/ns-wamr/|packages/ns-wasm-core/|\.github/workflows/ci\.yml$)' <<<"$changed"; then + echo "wamr=true" >> "$GITHUB_OUTPUT" + else + echo "wamr=false" >> "$GITHUB_OUTPUT" + fi + + # The app's suite runs the plugin end to end, so a plugin or fixture + # change is as relevant to it as a change to the app itself. + if grep -qE '^(apps/ns-wasm-test/|packages/ns-wasm3/|packages/ns-wamr/|packages/ns-wasm-kit-runtime/|packages/ns-endive/|packages/ns-wasm-chicory/|packages/ns-wasm-edge/|packages/ns-wasm-core/|packages/ns-wasm-fixture/|packages/ns-rspack/|\.github/workflows/ci\.yml$)' <<<"$changed"; then + echo "app=true" >> "$GITHUB_OUTPUT" + else + echo "app=false" >> "$GITHUB_OUTPUT" + fi + + # iOS: compiles the vendored wasm3 C sources through SwiftPM and runs the + # XCTest suite, which exercises the interpreter natively on macOS. + wasm3-ios: + needs: changes + if: needs.changes.outputs.wasm3 == 'true' + runs-on: macos-latest + steps: + - uses: actions/checkout@v5 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v5 + with: + node-version: 24 + package-manager-cache: false + + - name: Verify the synced Swift package copy is current + working-directory: packages/ns-wasm3 + run: | + node tools/sync-wasm3.mjs + git diff --exit-code -- . || { + echo "::error::platforms/ios/NSCWasm3/Sources/CWasm3 is stale — run 'pnpm run sync.vendors' and commit the result." + exit 1 + } + + - name: Swift tests + working-directory: packages/ns-wasm3/platforms/ios/NSCWasm3 + run: swift test --disable-sandbox + + - name: Install SwiftLint and Periphery + run: | + brew install swiftlint + brew install peripheryapp/periphery/periphery + + - name: SwiftLint + working-directory: packages/ns-wasm3/platforms/ios/NSCWasm3 + run: swiftlint lint --config ../../../../../.swiftlint.yml Sources Tests + + - name: Periphery (unused code) + working-directory: packages/ns-wasm3/platforms/ios/NSCWasm3 + run: periphery scan --config ../../../../../.periphery.yml --disable-update-check + + - name: Build for iOS device + working-directory: packages/ns-wasm3/platforms/ios/NSCWasm3 + run: | + xcodebuild build \ + -scheme NSCWasm3 \ + -destination 'generic/platform=iOS' + + # Android: builds the wasm3-jni Rust crate via cargo and runs the Kotlin + # wrapper's JUnit hosttest suite — no emulator needed. + wasm3-android: + needs: changes + if: needs.changes.outputs.wasm3 == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + # Gradle shells out to build-native.mjs (zx) to generate the bindings and + # compile wasm3, so the native build needs Node and the root node_modules. + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v5 + with: + node-version: 24 + cache: 'pnpm' + + - run: pnpm install --frozen-lockfile + + - uses: actions/setup-java@v4.9.0 + with: + distribution: temurin + java-version: '21' + + - uses: gradle/actions/setup-gradle@v4.4.4 + with: + validate-wrappers: false + + # Rust is pre-installed on ubuntu-latest; install if missing (e.g. act). + - name: Install Rust (if missing) + run: | + if ! command -v cargo >/dev/null 2>&1; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + fi + + - name: JVM host tests (cargo build + Kotlin wrapper) + working-directory: packages/ns-wasm3/platforms/android/wasm3-android + run: ./gradlew :hosttest:test + + - name: Kotlin lint (Detekt + Ktlint) + working-directory: packages/ns-wasm3/platforms/android/wasm3-android + run: ./gradlew detekt ktlintCheck + + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: wasm3-android-test-report + path: packages/ns-wasm3/platforms/android/wasm3-android/hosttest/build/reports/tests/test + + # The test app's Vitest suite, on a simulator and an emulator. This is the only + # place the plugin's TypeScript adapters run against the real native layer — + # the NSData/NSArray marshalling on iOS, the signed byte[] handling on Android + # — so it covers what neither the Swift/Kotlin suites nor a Node runner can. + # iOS: compiles the vendored WAMR C sources through SwiftPM and runs the + # XCTest suite, which exercises the runtime natively on macOS. + wamr-ios: + needs: changes + if: needs.changes.outputs.wamr == 'true' + runs-on: macos-latest + steps: + - uses: actions/checkout@v5 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v5 + with: + node-version: 24 + package-manager-cache: false + + - name: Check for WAMR C sources + id: sources + run: | + # -print -quit: stop after the first match so find exits cleanly — + # under pipefail, grep -q exiting early would SIGPIPE find and flip + # the result to "no sources", silently skipping the job. + if find packages/ns-wamr/src/vendors/wamr \ + \( -name '*.c' -o -name '*.h' \) -print -quit | grep -q .; then + echo "present=true" >> "$GITHUB_OUTPUT" + else + echo "present=false" >> "$GITHUB_OUTPUT" + echo "::warning::WAMR C sources not found — Swift steps will be skipped." + fi + + - name: Verify the synced Swift package copy is current + if: steps.sources.outputs.present == 'true' + working-directory: packages/ns-wamr + run: | + node tools/sync-wamr.mjs + git diff --exit-code -- . || { + echo "::error::platforms/ios/NSCWamr/Sources/CWamr is stale — run 'pnpm run sync.vendors' and commit the result." + exit 1 + } + + - name: Swift tests + if: steps.sources.outputs.present == 'true' + working-directory: packages/ns-wamr/platforms/ios/NSCWamr + run: swift test --disable-sandbox + + - name: Install SwiftLint and Periphery + if: steps.sources.outputs.present == 'true' + run: | + brew install swiftlint + brew install peripheryapp/periphery/periphery + + - name: SwiftLint + if: steps.sources.outputs.present == 'true' + working-directory: packages/ns-wamr/platforms/ios/NSCWamr + run: swiftlint lint --config ../../../../../.swiftlint.yml Sources Tests + + - name: Periphery (unused code) + if: steps.sources.outputs.present == 'true' + working-directory: packages/ns-wamr/platforms/ios/NSCWamr + run: periphery scan --config ../../../../../.periphery.yml --disable-update-check + + - name: Build for iOS device + if: steps.sources.outputs.present == 'true' + working-directory: packages/ns-wamr/platforms/ios/NSCWamr + run: | + xcodebuild build \ + -scheme NSCWamr \ + -destination 'generic/platform=iOS' + + # Android: builds the wamr-jni Rust crate via cargo and runs the Kotlin + # wrapper's JUnit hosttest suite — no emulator. + wamr-android: + needs: changes + if: needs.changes.outputs.wamr == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v5 + with: + node-version: 24 + cache: 'pnpm' + + - run: pnpm install --frozen-lockfile + + - name: Check for WAMR C sources + id: sources + run: | + # -print -quit: stop after the first match so find exits cleanly — + # under pipefail, grep -q exiting early would SIGPIPE find and flip + # the result to "no sources", silently skipping the job. + if find packages/ns-wamr/src/vendors/wamr \ + \( -name '*.c' -o -name '*.h' \) -print -quit | grep -q .; then + echo "present=true" >> "$GITHUB_OUTPUT" + else + echo "present=false" >> "$GITHUB_OUTPUT" + echo "::warning::WAMR C sources not found — Gradle steps will be skipped." + fi + + - uses: actions/setup-java@v4.9.0 + if: steps.sources.outputs.present == 'true' + with: + distribution: temurin + java-version: '21' + + - uses: gradle/actions/setup-gradle@v4.4.4 + if: steps.sources.outputs.present == 'true' + with: + validate-wrappers: false + + # Rust is pre-installed on ubuntu-latest; install if missing (e.g. act). + - name: Install Rust (if missing) + if: steps.sources.outputs.present == 'true' + run: | + if ! command -v cargo >/dev/null 2>&1; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + fi + + - name: JVM host tests (cargo build + Kotlin wrapper) + if: steps.sources.outputs.present == 'true' + working-directory: packages/ns-wamr/platforms/android/wamr-android + run: ./gradlew :hosttest:test + + - name: Kotlin lint (Detekt + Ktlint) + if: steps.sources.outputs.present == 'true' + working-directory: packages/ns-wamr/platforms/android/wamr-android + run: ./gradlew detekt ktlintCheck + + - uses: actions/upload-artifact@v4 + if: failure() && steps.sources.outputs.present == 'true' + with: + name: wamr-android-test-report + path: packages/ns-wamr/platforms/android/wamr-android/hosttest/build/reports/tests/test + + # The test app's Vitest suite, on a simulator and an emulator. This is the only + # place the plugin's TypeScript adapters run against the real native layer — + # the NSData/NSArray marshalling on iOS, the signed byte[] handling on Android + # — so it covers what neither the Swift/Kotlin suites nor a Node runner can. + wasm-test-ios: + needs: changes + if: needs.changes.outputs.app == 'true' + runs-on: macos-latest + steps: + - uses: actions/checkout@v5 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v5 + with: + node-version: 24 + cache: 'pnpm' + cache-dependency-path: | + pnpm-lock.yaml + apps/ns-wasm-test/pnpm-lock.yaml + + - run: pnpm install --frozen-lockfile + + # The app consumes local packages through `workspace:` links, so the + # linked dist/ entry points must exist before the app is installed or + # built — build every local package first. + - name: Build the app's local packages + run: pnpm exec nx run-many -t build -p ns-wasm-core ns-wasm3 ns-wamr ns-wasm-kit-runtime ns-endive ns-wasm-chicory ns-wasm-edge ns-rspack ns-rstest ns-lynx + + - name: Install Buck2 + run: | + set -o pipefail + curl -fsSL https://github.com/facebook/buck2/releases/download/latest/buck2-aarch64-apple-darwin.zst \ + | zstd -d | sudo tee /usr/local/bin/buck2 > /dev/null + sudo chmod +x /usr/local/bin/buck2 + buck2 --version + + - name: Build native iOS artifacts + env: + HOME: /tmp/buck2-home + run: | + pnpm exec nx run-many -t build.xcframework --configuration=release \ + -p ns-wamr ns-wasm3 ns-wry ns-wasm-kit-runtime + + - name: Install the test app's dependencies + working-directory: apps/ns-wasm-test + run: pnpm install --frozen-lockfile + + # `ns run ios --emulator` only auto-boots a simulator when Simulator.app + # is already a running process (ios-sim-portable's startSimulator calls + # `simctl boot` solely inside its `isSimulatorAppRunning` branch) — true + # on a developer's Mac, never true on a fresh runner. Left to itself it + # just opens an empty Simulator window and gives up, so every device + # search after it comes back empty ("Unable to find applicable + # devices..."). Boot one explicitly so the CLI's own device-discovery + # (plain `simctl`-state polling, no GUI dependency) finds it already + # running and skips that codepath entirely. + - name: Boot an iOS Simulator + run: | + UDID=$(xcrun simctl list devices available | grep -m1 'iPhone' | grep -oE '[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}') + xcrun simctl boot "$UDID" + xcrun simctl bootstatus "$UDID" -b + + # `ns doctor` shells out to `pod --version`, which prints a locale warning + # the CLI treats as a failed CocoaPods check unless LANG is a UTF-8 one. + # The plugin itself needs no pods — it ships a Swift package. + - name: Vitest specs on the iOS Simulator + working-directory: . + env: + LANG: en_US.UTF-8 + run: pnpm exec nx run ns-wasm-test:test.ios + + wasm-test-android: + needs: changes + if: needs.changes.outputs.app == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v5 + with: + node-version: 24 + cache: 'pnpm' + cache-dependency-path: | + pnpm-lock.yaml + apps/ns-wasm-test/pnpm-lock.yaml + + - run: pnpm install --frozen-lockfile + + # Keep this before the app install: the app consumes local packages + # through `workspace:` links, so their dist/ entry points must exist + # before the app is installed or built. + - name: Build the app's local packages + run: pnpm exec nx run-many -t build -p ns-wasm-core ns-wasm3 ns-wamr ns-wasm-kit-runtime ns-endive ns-wasm-chicory ns-wasm-edge ns-rspack ns-rstest ns-lynx + + - name: Install the test app's dependencies + working-directory: apps/ns-wasm-test + run: pnpm install --frozen-lockfile + + - uses: actions/setup-java@v4.9.0 + with: + distribution: temurin + java-version: '21' + + # Lets the emulator use hardware acceleration on the GitHub runner. + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ + | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + # gradle's buildNative shells out to `cargo ndk`, several process layers + # below (emulator-runner -> nx -> buck2 genrule -> gradle Exec). Only a + # real process env var survives that, so pin the toolchain paths here + # rather than in the emulator script, which cannot export anything (see + # below). Written via $GITHUB_ENV so $HOME expands. + - name: Pin the cargo/rustup toolchain locations + run: | + echo "RUSTUP_HOME=$HOME/.rustup" >> "$GITHUB_ENV" + echo "CARGO_HOME=$HOME/.cargo" >> "$GITHUB_ENV" + + # Both plugins ship prebuilt .aar files with their .so files. + - name: Vitest specs on the Android emulator + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 34 + arch: x86_64 + working-directory: . + script: | + # android-emulator-runner splits this script on newlines and runs + # each line as its own `/bin/sh -c`. So: every line must stand + # alone (a `\` continuation is passed as a literal argument, not + # joined — that silently dropped the `-p` filter below and built + # every project with a build.android target), `export` is useless + # (see the step above), and `set -e`/`set -o pipefail` are too — + # the action already stops on the first line that exits non-zero, + # which is why the pipe is avoided here rather than guarded. + curl -fsSL https://github.com/facebook/buck2/releases/download/latest/buck2-x86_64-unknown-linux-gnu.zst -o /tmp/buck2.zst + zstd -d /tmp/buck2.zst -o /tmp/buck2 + sudo install -m 0755 /tmp/buck2 /usr/local/bin/buck2 + buck2 --version + rustup show + rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android + cargo install cargo-ndk + pnpm exec nx run-many -t build.android --configuration=release -p ns-wamr ns-wasm3 ns-wasm-chicory + ls -l packages/ns-wamr/platforms/android/nativescript-wamr.aar packages/ns-wasm3/platforms/android/nativescript-wasm3.aar packages/ns-wasm-chicory/platforms/android/nativescript-chicory.aar + pnpm exec nx run ns-wasm-test:test.android diff --git a/.github/workflows/code-review.yml b/.github/workflows/code-review.yml index 2dd0755a..50fd3521 100644 --- a/.github/workflows/code-review.yml +++ b/.github/workflows/code-review.yml @@ -1,64 +1,64 @@ -name: DeepSeek CR - -# AI code review on pull requests via hustcer/deepseek-review. -# -# Setup: add a repository secret named CHAT_TOKEN with your DeepSeek API key -# (Settings → Secrets and variables → Actions). Optionally override the model -# or base-url with the `with:` inputs below (e.g. a SiliconFlow / GitHub Models -# endpoint — see https://github.com/hustcer/deepseek-review for options). -# -# Skip a review by adding "skip cr" / "skip review" to the PR title or body, -# or lock the PR conversation. - -on: - # Disabled — change to workflow_dispatch to run manually. - # Previously: pull_request_target + issue_comment triggers. - workflow_dispatch: - # pull_request_target: - # types: - # - opened # Triggers when a PR is opened - # - reopened # Triggers when a PR is reopened - # - synchronize # Triggers when a commit is pushed to the PR - # issue_comment: - # types: - # - created # Triggers when a comment is created on a PR (watch-mention) - -permissions: - pull-requests: write - -jobs: - setup-deepseek-review: - timeout-minutes: 30 - runs-on: ubuntu-latest - name: DeepSeek Code Review - steps: - - name: DeepSeek Code Review - uses: hustcer/deepseek-review@v1 - with: - chat-token: ${{ secrets.CHAT_TOKEN }} - # A prompt tuned to this monorepo: NativeScript plugin (TypeScript + - # Kotlin/Swift), a Rust wasm fixture, and an Nx workspace. - sys-prompt: | - You are a senior code reviewer for a NativeScript monorepo built - with Nx. The codebase spans: a TypeScript NativeScript plugin - (packages/ns-wasm3) that bridges WebAssembly (wasm3) to - iOS (Swift) and Android (Kotlin + Rust JNI); a Rust wasm-pack - fixture crate (packages/ns-wasm-fixture); and a - NativeScript test app (apps/ns-wasm-test) running Vitest - through vitest-ns on a device/emulator. - - Review the diff for: correctness across the JS↔native wire - protocol (i64 must stay lossless as decimal strings, signed Java - byte[] handling, NSArray/NSData marshalling), memory safety in - wasm3 C interop, resource leaks (native runtimes must be - disposed/closed), TypeScript strictness, Rust unsafe usage, CI - workflow correctness, and test coverage. Flag security issues - (injection, secrets, unsafe FFI) first. Be concise; list concrete - problems with file:line references and actionable fixes. - # DeepSeek's own API is the default; uncomment to switch provider: - # model: deepseek-v4-flash - # base-url: https://api.deepseek.com - # exclude-patterns: pnpm-lock.yaml,package-lock.json,*.lock - # Trigger a review by commenting "@github-actions" on the PR - # (requires the issue_comment event above): - # watch-mention: "@github-actions" +name: DeepSeek CR + +# AI code review on pull requests via hustcer/deepseek-review. +# +# Setup: add a repository secret named CHAT_TOKEN with your DeepSeek API key +# (Settings → Secrets and variables → Actions). Optionally override the model +# or base-url with the `with:` inputs below (e.g. a SiliconFlow / GitHub Models +# endpoint — see https://github.com/hustcer/deepseek-review for options). +# +# Skip a review by adding "skip cr" / "skip review" to the PR title or body, +# or lock the PR conversation. + +on: + # Disabled — change to workflow_dispatch to run manually. + # Previously: pull_request_target + issue_comment triggers. + workflow_dispatch: + # pull_request_target: + # types: + # - opened # Triggers when a PR is opened + # - reopened # Triggers when a PR is reopened + # - synchronize # Triggers when a commit is pushed to the PR + # issue_comment: + # types: + # - created # Triggers when a comment is created on a PR (watch-mention) + +permissions: + pull-requests: write + +jobs: + setup-deepseek-review: + timeout-minutes: 30 + runs-on: ubuntu-latest + name: DeepSeek Code Review + steps: + - name: DeepSeek Code Review + uses: hustcer/deepseek-review@v1 + with: + chat-token: ${{ secrets.CHAT_TOKEN }} + # A prompt tuned to this monorepo: NativeScript plugin (TypeScript + + # Kotlin/Swift), a Rust wasm fixture, and an Nx workspace. + sys-prompt: | + You are a senior code reviewer for a NativeScript monorepo built + with Nx. The codebase spans: a TypeScript NativeScript plugin + (packages/ns-wasm3) that bridges WebAssembly (wasm3) to + iOS (Swift) and Android (Kotlin + Rust JNI); a Rust wasm-pack + fixture crate (packages/ns-wasm-fixture); and a + NativeScript test app (apps/ns-wasm-test) running Rstest + through ns-rstest on a device/emulator. + + Review the diff for: correctness across the JS↔native wire + protocol (i64 must stay lossless as decimal strings, signed Java + byte[] handling, NSArray/NSData marshalling), memory safety in + wasm3 C interop, resource leaks (native runtimes must be + disposed/closed), TypeScript strictness, Rust unsafe usage, CI + workflow correctness, and test coverage. Flag security issues + (injection, secrets, unsafe FFI) first. Be concise; list concrete + problems with file:line references and actionable fixes. + # DeepSeek's own API is the default; uncomment to switch provider: + # model: deepseek-v4-flash + # base-url: https://api.deepseek.com + # exclude-patterns: pnpm-lock.yaml,package-lock.json,*.lock + # Trigger a review by commenting "@github-actions" on the PR + # (requires the issue_comment event above): + # watch-mention: "@github-actions" diff --git a/.gitignore b/.gitignore index c0702b5e..8d495fac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,80 +1,106 @@ -# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files. - -# compiled output -dist -tmp -out-tsc - -# dependencies -node_modules - -# IDEs and editors -/.idea -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# IDE - VSCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json - -# misc -/.sass-cache -/connect.lock -/coverage -/apps/ns-wasm-test/test-output/ -/libpeerconnection.log -npm-debug.log -yarn-error.log -testem.log -/typings - -# System Files -.DS_Store -Thumbs.db - -.nx/cache -.nx/workspace-data -.cursor/rules/nx-rules.mdc -.github/instructions/nx.instructions.md - -.claude/worktrees -.claude/settings.local.json -.nx/polygraph -.nx/self-healing -.nx/migrate-runs -vitest.config.*.timestamp* -# Added by code-review-graph -.code-review-graph/ - -# Kotlin -**/.kotlin/errors/*.log - -# Reasonix -.reasonix/desktop-topic-auto-title-meta.json -.reasonix/desktop-topic-created-at.json -.reasonix/desktop-topic-title-sources.json -.reasonix/desktop-topic-titles.json -.reasonix/tasks/ - -# DeepSeek CR local config (contains API token) -.deepseek-cr.config.json - -reasonix.toml - -/packages/ns-wasm3/src/vendors/wasm3-rust/target/ - -# pnpm local package store (created during resolution) -.pnpm-store +# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# compiled output +dist +tmp +out-tsc + +# dependencies +node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log + +# test-runner output (coverage reports, device-run artifacts) +test-output/ + +# native type declarations generated by `ns typings ios|android` +# (nx target: ns-wasm-test:typings.ios / typings.android) +typings/ + +# System Files +.DS_Store +Thumbs.db + +.nx/cache +.nx/workspace-data +.cursor/rules/nx-rules.mdc +.github/instructions/nx.instructions.md + +.claude/worktrees +.claude/settings.local.json +.nx/polygraph +.nx/self-healing +.nx/migrate-runs +vitest.config.*.timestamp* +# Added by code-review-graph +.code-review-graph/ + +# CodeGraph index +.codegraph/ + +# Kotlin +**/.kotlin/errors/*.log + +# Reasonix +.reasonix/desktop-topic-auto-title-meta.json +.reasonix/desktop-topic-created-at.json +.reasonix/desktop-topic-title-sources.json +.reasonix/desktop-topic-titles.json +.reasonix/tasks/ + +# DeepSeek CR local config (contains API token) +.deepseek-cr.config.json + +reasonix.toml + +/packages/ns-wasm3/src/vendors/wasm3-rust/target/ + +# Native prebuilt / platform build artifacts (build.xcframework, build.android, cargo-ndk) +*.xcframework/ +*.xcframework.dSYMs/ +*.framework.dSYM/ +**/platforms/android/*.aar +**/platforms/android/symbols/ +**/Sources/*FFI/*.a +**/target/ +**/jniLibs/**/*.so +!**/jniLibs/**/.gitkeep + +# SwiftPM / Xcode build caches +**/.build/ +**/DerivedData/ + +# pnpm local package store (created during resolution) +.pnpm-store .buck-out/ /buck-out .buck-out/ # VS Code multi-root workspace *.code-workspace + +vite.config.*.timestamp* +/.serena diff --git a/.mcp.json b/.mcp.json index fa861d02..da39e4ff 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,14 +1,3 @@ { - "mcpServers": { - "code-review-graph": { - "command": "/opt/homebrew/opt/python@3.14/bin/python3.14", - "args": [ - "-m", - "code_review_graph", - "serve" - ], - "cwd": "/Users/listepo/GitHub/cross-code", - "type": "stdio" - } - } + "mcpServers": {} } diff --git a/.oxlintrc.json b/.oxlintrc.json index ddd1254c..0764e3ff 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,11 +1,16 @@ { "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/crates/oxc_linter/src/schema.json", - "plugins": ["typescript", "import", "unicorn"], + "plugins": [ + "typescript", + "import", + "unicorn" + ], "rules": { "no-unused-vars": "error", "no-console": "warn", "typescript/no-non-null-assertion": "warn", - "import/no-default-export": "off" + "import/no-default-export": "off", + "typescript/no-explicit-any": "error" }, "env": { "node": true, @@ -26,6 +31,8 @@ "**/platforms/**", "**/src/vendors/**", "**/test-output/**", - "**/*.d.ts" + "**/*.d.ts", + "!**/src/**/*.d.ts", + "**/pkg/**" ] } diff --git a/AGENTS.md b/AGENTS.md index 4603b36e..98915494 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,6 +22,14 @@ +## AI agent files + +This file is the canonical agent instructions. Durable gotchas and decisions +live in [MEMORY.md](MEMORY.md) — read both before editing. `CLAUDE.md`, +`GEMINI.md`, `CODEX.md`, and `.cursorrules` are pointers here. Per-package +`AGENTS.md` (and `MEMORY.md` where it exists) covers package-specific +invariants. + ## NativeScript - Docs: https://docs.nativescript.org (append .md to any URL for markdown) @@ -42,9 +50,10 @@ TypeScript API (wire protocol, error mapping, and `WasmRuntime` / `WasmModule` / `WasmFunction` class shapes). The Rust cargo workspace and UniFFI (uniffi-rs) Kotlin/Swift bindings — together with the mirror-image native architecture described in [Shared plugin architecture](#shared-plugin-architecture) — apply -to **ns-wasm3 and ns-wamr**. The newer runtimes (`ns-wasm-kit-runtime`, the -Swift-native WasmKit interpreter; `ns-endive`, the Java-native Endive -interpreter) share the same TypeScript adapter pattern, wire protocol, and +to **ns-wasm3, ns-wamr and ns-wasm-edge**. The newer runtimes +(`ns-wasm-kit-runtime`, the Swift-native WasmKit interpreter; `ns-wasm-chicory` +and `ns-endive`, the Java-native Chicory and Endive interpreters) share the +same TypeScript adapter pattern, wire protocol, and `@cross-code/ns-wasm-core` foundation, but have their own per-engine native layers. Only engine-specific detail lives in each package's AGENTS.md. @@ -68,23 +77,55 @@ layers. Only engine-specific detail lives in each package's AGENTS.md. iOS-only at this time (WasmKit is Swift-native and served through SwiftPM); Android throws a clear unsupported error. Follows the same TypeScript adapter pattern as the other two plugins. +- **`ns-wasm-edge`** (`@cross-code/ns-wasm-edge`) — plugin binding the + [WasmEdge](https://github.com/WasmEdge/WasmEdge) runtime (Swift Package on + iOS, Kotlin + Rust JNI (cargo-ndk) on Android). Follows the Rust/UniFFI + architecture of ns-wasm3/ns-wamr. +- **`ns-wasm-chicory`** (`@cross-code/ns-wasm-chicory`) — plugin binding the + [Chicory](https://github.com/dylibso/chicory) interpreter, a pure-Java + runtime (no NDK/Rust needed) — Android-only. +- **`ns-endive`** (`@cross-code/ns-endive`) — plugin binding the + [Endive](https://github.com/bytecodealliance/endive) interpreter — Java/JNI + on Android with a TypeScript adapter. Android-only. +- **`ns-rspack`** (`@cross-code/ns-rspack`) — **not a WASM plugin**: an + [rspack](https://rspack.rs) bundler for NativeScript apps. It owns the whole + NativeScript build configuration natively — entry stubs, platform-suffixed + resolution, XML/CSS loaders, copy rules, defines, HMR — with no + `@nativescript/webpack` and no webpack in its dependency tree. Installed + under the alias the {N} CLI resolves the bundler by (`@nativescript/rspack`). + See `packages/ns-rspack/README.md` and `packages/ns-rspack/AGENTS.md`. - **`ns-wry`** (`@cross-code/ns-wry`) — general-purpose NativeScript plugin scaffold built on Rust + UniFFI (uniffi-rs) with cargo-ndk Android pipeline. See `packages/ns-wry/AGENTS.md` for the bare-metal architecture; extend the `wry-rust` workspace and `wry_ffi.udl` IDL to add engine-specific APIs. -### Vitest + NativeScript unit-test packages - -- **`vitest-ns`** (`@cross-code/vitest-ns`) — a Vitest - custom pool that runs unit tests in NativeScript Worker runtimes. Read - `packages/vitest-ns/AGENTS.md` before changing its Node/device - protocol or webpack aliases. -- **`vitest-ns-ui`** (`@cross-code/vitest-ns-ui`) — an - optional NativeScript Core results view. It is presentation-only and should - remain removable for headless or CI usage. -- These packages support one-shot unit tests; they are not a component-testing - or end-to-end framework. Run their Nx `build`, `typecheck`, and `test` - targets with `pnpm exec nx`. +### LynxJS integration + +- **`ns-lynx`** (`@cross-code/ns-lynx`) — embeds the + [Lynx](https://lynxjs.org) engine in a NativeScript app as a ``, + with NativeScript as the host. Unlike the WASM plugins it has **no native + layer at all**: Lynx publishes its engine through CocoaPods and Maven, and + NativeScript reaches those classes from JavaScript directly, so the package + is TypeScript plus a Podfile and an include.gradle. Read + `packages/ns-lynx/AGENTS.md` before changing the view or the SDK versions — + the two manifests pin the same engine and must move together. +- `apps/ns-lynx-app` is the worked example: a NativeScript host page around a + rspeedy/ReactLynx bundle built from its own `lynx/` sub-project. + +### Rstest + NativeScript unit-test package + +- **`ns-rstest`** (`@cross-code/ns-rstest`) — runs Rstest unit tests in + NativeScript Worker runtimes. Rstest has no custom-pool API, so the package + owns its own Node host (`runNativeScriptTests`) and drives Rstest's own + runtime (`@rstest/core/internal/browser-runtime`) on the device. Read + `packages/ns-rstest/AGENTS.md` before changing its Node/device protocol or + bundler aliases. +- The optional on-device results view ships from the same package under + `@cross-code/ns-rstest/ui`. It is presentation-only and should remain + removable for headless or CI usage. +- The package supports one-shot unit tests; it is not a component-testing or + end-to-end framework. Run its Nx `build`, `typecheck`, and `test` targets + with `pnpm exec nx`. **wamr native suites**: if the vendored WAMR C sources are ever absent, the wamr native commands and CI jobs (`wamr-ios`, `wamr-android`) skip gracefully @@ -225,16 +266,19 @@ in `CWamr/include/` pointing at the four public headers (`wasm_export.h`, ### iOS: prebuilt XCFrameworks (SwiftPM replaced) -Each plugin ships a **prebuilt dynamic `.xcframework`** in -`platforms/ios/.xcframework` (plus -`.xcframework.dSYMs/` with the debug symbols). The NativeScript CLI +Each plugin ships a **dynamic `.xcframework`** under +`platforms/ios/.xcframework` (plus +`.xcframework.dSYMs/`). These are **gitignored** — build them with +`nx run :build.xcframework` (nx-buck2, release by default) before +running a NativeScript app or CI device jobs. The NativeScript CLI 9.x discovers any `platforms/ios/*.xcframework` in a plugin automatically (`FRAMEWORK_EXTENSIONS` in `ios-project-service.js`) — it links the matching slice, adds it to **Embed Frameworks** with `CodeSignOnCopy`, and re-signs. No `SPMPackages` entry is used anymore (that mechanism predates this change; see git history for the old SwiftPM declaration). -Rebuild with `npm run build.xcframework` (per plugin). All three plugins +Rebuild with `nx run :build.xcframework --configuration=release` +(or `--configuration=debug` for fast iteration). The Rust/Swift/C plugins share ONE builder: `tools/build-xcframework.sh ` (each package's `tools/build-xcframework.sh` is a 3-line wrapper). It builds each slice with `swift build --disable-sandbox --triple ` @@ -291,8 +335,12 @@ Both plugins use the identical Android architecture (no JavaCPP): `NSCWasm3.kt` + `NativeWasm3.kt`) loads `libwasm3_jni.so` / `libwamr_jni.so` via JNI and `System.loadLibrary`. - `deployAar` copies the release `.aar` to `platforms/android/nativescript-.aar`. - The `.aar` is **committed** because it contains precompiled `.so` files — - consumers don't need the NDK or a Rust toolchain. + AARs are **gitignored**. `nx run :build.android` (nx-buck2) must also + copy that AAR out of the Buck2 srcs sandbox into the plugin tree — + NativeScript scans `platforms/android/**/*.aar`, not `.buck-out/` + (`tools/install-nativescript-aar.sh`). Without that copy the app still + builds, linking only `gradle-wrapper.jar` from the nested Gradle project, + and device tests fail with "native runtime not found". - `hosttest/` is a pure-JVM module that compiles the Kotlin wrapper sources and runs JUnit tests against a host (`cargo build --release -p -jni`) build of the library, with `java.library.path` pointed at `target/release`. @@ -404,11 +452,13 @@ No globally installed gradle, cocoapods, or wasm toolchain is required. ### Buck2 builds (nx-buck2) -Native builds can optionally run through **Buck2** via the `@cross-code/nx-buck2` +Native builds run through **Buck2** via the `@cross-code/nx-buck2` Nx plugin (`packages/nx-buck2`): executors `build`/`test`/`run` dispatch -`buck2 build/test/run` against per-project `BUCK` files (currently genrule -wrappers around the Cargo/SwiftPM toolchains — the standard Buck2 migration -path). Debug/release is selected per invocation: +`buck2 build/test/run` against per-project `BUCK` files (genrule wrappers +around Cargo/SwiftPM/Gradle). Use `build.android`, `build.xcframework`, or +`buck2-build` on each engine plugin — all route through nx-buck2 with +`--configuration=debug` (fast: `-Onone`/`-O0`, no LTO/strip) or `release` +(size-optimized). Debug/release is selected per invocation: ```bash nx run ns-wamr:buck2-build --configuration=release # -Oz, LTO, stripped @@ -421,7 +471,7 @@ Buck2 is NOT installed by mise (the crates.io `buck2` crate is a placeholder). Install the prebuilt binary once: ```bash -curl -fsSL https://github.com/facebook/buck2/releases/latest/download/buck2-aarch64-apple-darwin.zst \ +curl -fsSL https://github.com/facebook/buck2/releases/download/latest/buck2-aarch64-apple-darwin.zst \ | zstd -d | sudo tee /usr/local/bin/buck2 > /dev/null && sudo chmod +x /usr/local/bin/buck2 ``` @@ -475,6 +525,16 @@ Three test layers, each covering a different slice: touch `wire.ts` or an adapter file. Details in `apps/ns-wasm-test/AGENTS.md`. +### CI jobs are real gates — never `continue-on-error` + +The CI jobs that run the test app's suite (`wasm-test-ios`, +`wasm-test-android` in `.github/workflows/ci.yml`) are the only place the +TypeScript adapters meet the real native layers, so a failure in either is a +release-blocking signal. **Do not add `continue-on-error` to any CI job** — +emulator/simulator flakiness must be fixed, not bypassed. If a job is +genuinely environment-broken, disable it with an `if:` condition and a +comment instead, never a silent `continue-on-error`. + ### Kotlin linting (Detekt + Ktlint) Each Android Gradle project (`platforms/android/-android/`) runs @@ -616,46 +676,60 @@ wasm-pack-generated `.d.ts`. See `packages/ns-wasm-fixture/README.md`. | `packages/ns-wasm3/AGENTS.md` | wasm3-specific: stack ABI, globals, fixtures, build/test | | `packages/ns-wamr/AGENTS.md` | WAMR-specific: two-phase load, exec env, WASI, tiers, trampolines, shim | | `packages/ns-wasm-kit-runtime/AGENTS.md` | WasmKit-specific: iOS-only Swift interpreter, Android unsupported stub | +| `packages/ns-wasm-edge/AGENTS.md` | (none — no per-package AGENTS yet; follows the Rust/UniFFI architecture of wasm3/wamr, see Shared plugin architecture above) | +| `packages/ns-wasm-chicory/AGENTS.md` | (none — no per-package AGENTS yet; pure-Java Android runtime, no NDK/Rust needed) | +| `packages/ns-endive/AGENTS.md` | (none — no per-package AGENTS yet; Java/JNI Android runtime with TypeScript adapter) | +| `packages/ns-rspack/AGENTS.md` | rspack bundler — CLI/IPC, layout, invariants (see also `MEMORY.md`, `README.md`) | | `packages/ns-wry/AGENTS.md` | wry scaffold: Rust + UniFFI architecture, platform stubs, extension guide | | `apps/ns-wasm-test/AGENTS.md` | test app: layout, design decisions, running the suites, adding specs | +| `apps/rspack-test-app` | rspack-bundled test app — workspace member, installs with the root `pnpm install`; see `packages/ns-rspack/README.md` | | `apps/ns-wry-app` | test app: WebView demo, build-plugin-and-run workflow | ## MCP Tools: code-review-graph -**IMPORTANT: This project has a knowledge graph. ALWAYS use the -code-review-graph MCP tools BEFORE using Grep/Glob/Read to explore -the codebase.** The graph is faster, cheaper (fewer tokens), and gives -you structural context (callers, dependents, test coverage) that file -scanning cannot. - -### When to use graph tools FIRST - -- **Exploring code**: `semantic_search_nodes_tool` or `query_graph_tool` instead of Grep -- **Understanding impact**: `get_impact_radius_tool` instead of manually tracing imports -- **Code review**: `detect_changes_tool` + `get_review_context_tool` instead of reading entire files -- **Finding relationships**: `query_graph_tool` with callers_of/callees_of/imports_of/tests_for -- **Architecture questions**: `get_architecture_overview_tool` + `list_communities_tool` - -Fall back to Grep/Glob/Read **only** when the graph doesn't cover what you need. - -### Key Tools - -| Tool | Use when | -| -------------------------------- | ------------------------------------------------------ | -| `detect_changes_tool` | Reviewing code changes — gives risk-scored analysis | -| `get_review_context_tool` | Need source snippets for review — token-efficient | -| `get_impact_radius_tool` | Understanding blast radius of a change | -| `get_affected_flows_tool` | Finding which execution paths are impacted | -| `query_graph_tool` | Tracing callers, callees, imports, tests, dependencies | -| `semantic_search_nodes_tool` | Finding functions/classes by name or keyword | -| `get_architecture_overview_tool` | Understanding high-level codebase structure | -| `refactor_tool` | Planning renames, finding dead code | - -### Workflow - -1. The graph auto-updates on file changes (via hooks). -2. Use `detect_changes_tool` for code review. -3. Use `get_affected_flows_tool` to understand impact. -4. Use `query_graph_tool` pattern="tests_for" to check coverage. +This repository has a code knowledge graph, served over MCP by +`code-review-graph` (registered at user scope, so it follows whichever project +is open). **It is scoped to reviewing changes, not to general exploration** — +for ordinary "where does this live / what does this do" work, use Grep, Glob +and Read, or the graph tools this repo's other servers provide. + +Reach for it when the question is about a *change*: what a diff touches, what +it can break, and whether the affected code is covered. + +### When to use it + +| Situation | Tool | +| -------------------------------------------------- | ----------------------------------------------- | +| Reviewing a diff — what changed and how risky | `detect_changes_tool` | +| Need the source behind a finding, cheaply | `get_review_context_tool`, `get_minimal_context_tool` | +| Blast radius of a change | `get_impact_radius_tool` | +| Which execution paths a change sits on | `get_affected_flows_tool` | +| Callers, callees, imports, tests of a changed symbol | `query_graph_tool` (`callers_of`, `tests_for`, …) | +| Resolving a name in the diff to a graph node | `semantic_search_nodes_tool` | +| Is the graph fresh enough to trust | `list_graph_stats_tool` | + +The server also exposes architecture, community, wiki and refactor tools. They +are there for the generated skills below; do not reach for them to answer +ordinary questions about the codebase. + +### Review workflow + +1. `detect_changes_tool` — risk-scored view of what the branch touched. +2. `get_impact_radius_tool` / `get_affected_flows_tool` — what else is exposed. +3. `query_graph_tool` with `pattern="tests_for"` — is the changed code covered. +4. `get_review_context_tool` — pull only the snippets a finding needs. + +The graph is kept current by the hooks in `.claude/settings.json` +(`code-review-graph update` after each Edit/Write, `status` at session start), +so review answers reflect the working tree rather than the last full build. If +`list_graph_stats_tool` looks stale, run `code-review-graph update`. + +### Generated skills + +`.claude/skills/` holds skills generated by the installer: `review-changes` +(the review flow above), `debug-issue`, `explore-codebase` and +`refactor-safely`. Only `review-changes` reflects the scope described here — +treat the other three as opt-in, for when you deliberately want the graph +outside a review. diff --git a/CLAUDE.md b/CLAUDE.md index befb208e..854676c9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,12 +5,13 @@ AGENTS.md — read it before working in this repo. --> This project's canonical instructions for AI agents are in **`AGENTS.md`** (the industry-standard agent guidance file; Claude Code reads it -automatically). This file exists only so older tools that look for -`CLAUDE.md` find a path to the same content. +automatically). Durable gotchas live in **`MEMORY.md`**. This file exists +only so older tools that look for `CLAUDE.md` find a path to the same +content. -**Read `AGENTS.md` before exploring, building, or editing anything.** -It covers: Nx task conventions, the NativeScript plugins (ns-wamr, -ns-wasm3, ns-wry), the vitest-ns device test pool, the shared wire -protocol, native build pipelines (prebuilt xcframeworks, cargo-ndk, -Buck2 via `nx-buck2`), testing layers, and the code-review-graph MCP -tools that should be used before Grep/Glob/Read. +**Read `AGENTS.md` and `MEMORY.md` before exploring, building, or editing +anything.** They cover: Nx task conventions, the NativeScript plugins +(ns-wamr, ns-wasm3, ns-wry), the ns-rstest device test runner, the +ns-rspack bundler, the shared wire protocol, native build pipelines +(prebuilt xcframeworks, cargo-ndk, Buck2 via `nx-buck2`), testing layers, +and the code-review-graph MCP tools (review-only). diff --git a/CODEX.md b/CODEX.md new file mode 100644 index 00000000..9bcab4c9 --- /dev/null +++ b/CODEX.md @@ -0,0 +1,7 @@ +# CODEX.md + +All project guidance for AI agents lives in **`AGENTS.md`** — read it +before working in this repo. Durable gotchas live in **`MEMORY.md`**. + +This file exists only so agents that look for `CODEX.md` find a path +to the canonical guidance. diff --git a/GEMINI.md b/GEMINI.md index 0d8647db..55b1bef8 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -1,9 +1,10 @@ # GEMINI.md All project guidance for AI agents lives in **`AGENTS.md`** — read it -before working in this repo. It covers Nx task conventions, the -NativeScript plugins, native build pipelines, testing layers, and the -code-review-graph MCP tools. +before working in this repo. Durable gotchas live in **`MEMORY.md`**. +They cover Nx task conventions, the NativeScript plugins, native build +pipelines, testing layers, and the code-review-graph MCP tools +(review-only). This file exists only so agents that look for `GEMINI.md` find a path to the canonical guidance. diff --git a/MEMORY.md b/MEMORY.md new file mode 100644 index 00000000..a0897006 --- /dev/null +++ b/MEMORY.md @@ -0,0 +1,53 @@ +# MEMORY.md + +Read [AGENTS.md](AGENTS.md) first. This file is durable gotchas and decisions +that are easy to miss — not a second copy of the architecture guide. + +Pointer files (`CLAUDE.md`, `GEMINI.md`, `CODEX.md`, `.cursorrules`) all lead +here via `AGENTS.md`. + +## Where to read + +| Path | What it is | +| ---- | ---------- | +| [AGENTS.md](AGENTS.md) | Canonical agent instructions (Nx, NativeScript, plugins, testing) | +| [README.md](README.md) | Human-facing overview and commands | +| `packages//AGENTS.md` | Package-specific workflow and invariants | +| [packages/ns-rspack/MEMORY.md](packages/ns-rspack/MEMORY.md) | Bundler decisions and pitfalls | +| [packages/ns-rspack/AGENTS.md](packages/ns-rspack/AGENTS.md) | Bundler layout, CLI/IPC contract, invariants | + +## Cross-cutting + +- Run NativeScript through `npx ns` from the app directory. Never a global `ns`. +- Sandboxed shells often lack `pnpm` on `PATH`. Put `~/.local/share/mise/shims` + first, then `pnpm exec nx …`. +- `code-review-graph` MCP is **review-only** (registered at user scope). Ordinary + “where does this live” work uses Grep/Glob/Read, not that graph. +- Do **not** run `nx run ns-rspack:format`. The installed oxfmt restyles the + whole tree (semicolons, double quotes) away from the committed prettier + `--no-semi --single-quote` style. +- `ns typings android` dumps under `packages/*/typings/android/` are not + source-of-truth until curated and referenced from adapters. Generated files + can contain invalid TypeScript and Kotlin-mangled parameter names — do not + commit them raw. +- `ns-lynx` cannot currently finish a stock `ns build ios`: NativeScript’s + metadata generator parses PrimJS public C++ headers as Objective-C. See + `packages/ns-lynx/AGENTS.md`. +- Device rstest suites on iOS and Android share port **17878**. Run them + serially. +- Buck2 Android genrules write AARs into the srcs sandbox (`.buck-out/.../srcs`). + NativeScript only picks up `packages//platforms/android/**/*.aar`. The + genrule must run `tools/install-nativescript-aar.sh` after `deployAar` or + device tests fail with "native runtime not found" while the app still + builds (it will have linked `gradle-wrapper.jar` instead). +- `apps/ns-lynx-app`, `apps/ns-wasm-test`, and `apps/ns-wry-app` each have their + own pnpm lockfile. A dependency change in a linked workspace package (for + example `packages/ns-rspack/package.json`) needs `pnpm install` at the repo + root **and** in every nested app that links it — then commit each + `pnpm-lock.yaml`. CI runs `pnpm run verify:lockfiles` to catch stale nested + lockfiles before the device jobs install `apps/ns-wasm-test`. +- NativeScript host apps must stay in the `@nx/js/typescript` plugin `exclude` + list (`apps/ns-wasm-test/*`, `apps/ns-wry-app/*`, `apps/ns-lynx-app/*`). The + plugin infers `tsc --build --emitDeclarationOnly`, which is wrong for a + nested-lockfile NS app. `ns-lynx-app-lynx` (the rspeedy subproject) is a + different folder and should keep plugin inference. diff --git a/README.md b/README.md index 697898d6..a948b8bf 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # cross-code An Nx monorepo for running WebAssembly on [NativeScript](https://nativescript.org) — -three WASM runtime plugins built on a shared TypeScript foundation (wire protocol, +WASM runtime plugins built on a shared TypeScript foundation (wire protocol, adapter interfaces, base Runtime/Module/Function classes): - [`wasm3`](https://github.com/wasm3/wasm3) — lightweight interpreter (v0.5.2) @@ -9,13 +9,23 @@ adapter interfaces, base Runtime/Module/Function classes): Micro Runtime (2.3.0): interpreter, Fast JIT, LLVM JIT, AOT, WASI - [WasmKit](https://github.com/swiftwasm/WasmKit) — Swift-based WebAssembly runtime with WASI support, iOS-native through SwiftPM +- [WasmEdge](https://github.com/WasmEdge/WasmEdge) — high-performance runtime + (Swift Package on iOS, Kotlin + Rust JNI (cargo-ndk) on Android) +- [Chicory](https://github.com/dylibso/chicory) — pure-Java interpreter, + Android-only +- [Endive](https://github.com/bytecodealliance/endive) — Java-native + interpreter, Android-only Also in the monorepo: [`ns-wry`](packages/ns-wry) — a general-purpose NativeScript plugin scaffold built on Rust + [UniFFI](https://github.com/mozilla/uniffi-rs) -(uniffi-rs) auto-generated Kotlin/Swift bindings and cargo-ndk Android pipeline. +(uniffi-rs) auto-generated Kotlin/Swift bindings and cargo-ndk Android pipeline — +and [`ns-rspack`](packages/ns-rspack), an rspack bundler for NativeScript apps. > **Project status: Active development.** APIs and project layout may change without notice; expect breaking changes between releases. +AI agents: start at [AGENTS.md](AGENTS.md), then [MEMORY.md](MEMORY.md). +`CLAUDE.md`, `GEMINI.md`, `CODEX.md`, and `.cursorrules` point at those. + ## Packages | Package | Description | @@ -24,16 +34,22 @@ plugin scaffold built on Rust + [UniFFI](https://github.com/mozilla/uniffi-rs) | [`@cross-code/ns-wasm3`](packages/ns-wasm3) | NativeScript plugin — Swift Package on iOS, Kotlin + Rust JNI (cargo-ndk) on Android (wasm3 interpreter) | | [`@cross-code/ns-wamr`](packages/ns-wamr) | NativeScript plugin — Swift Package on iOS, Kotlin + Rust JNI (cargo-ndk) on Android (WAMR: interpreter, Fast JIT, LLVM JIT, AOT, WASI) | | [`@cross-code/ns-wasm-kit-runtime`](packages/ns-wasm-kit-runtime) | NativeScript plugin — Swift Package on iOS (WasmKit interpreter); Android throws a clear unsupported error | +| [`@cross-code/ns-wasm-edge`](packages/ns-wasm-edge) | NativeScript plugin — Swift Package on iOS, Kotlin + Rust JNI (cargo-ndk) on Android (WasmEdge runtime) | +| [`@cross-code/ns-wasm-chicory`](packages/ns-wasm-chicory) | NativeScript plugin — pure-Java Chicory interpreter on Android (no native toolchain needed) | +| [`@cross-code/ns-endive`](packages/ns-endive) | NativeScript plugin — Java/JNI Endive interpreter on Android | | [`@cross-code/ns-wasm-fixture`](packages/ns-wasm-fixture) | Rust/wasm-pack test fixtures (committed `.wasm` binaries) | -| [`@cross-code/vitest-ns`](packages/vitest-ns) | Vitest custom pool and NativeScript Worker runtime for on-device unit tests | -| [`@cross-code/vitest-ns-ui`](packages/vitest-ns-ui) | Optional NativeScript Core results page for device-side Vitest progress | +| [`@cross-code/ns-lynx`](packages/ns-lynx) | NativeScript plugin — embeds the [LynxJS](https://lynxjs.org) engine as a ``, rendering React on Lynx bundles inside a NativeScript page | +| [`@cross-code/ns-rstest`](packages/ns-rstest) | [Rstest](https://rstest.rs) device test runner for NativeScript — Node host, Worker runtime, and optional on-device results page | | [`@cross-code/ns-wry`](packages/ns-wry) | NativeScript plugin — Rust + UniFFI (uniffi-rs) Kotlin/Swift bindings, cargo-ndk Android pipeline | +| [`@cross-code/ns-rspack`](packages/ns-rspack) | [rspack](https://rspack.rs) bundler for NativeScript apps — the whole NativeScript build pipeline, natively on rspack | | [`@cross-code/nx-buck2`](packages/nx-buck2) | Nx plugin for Buck2 native builds — debug/release profiles, cross-compilation, size optimization | -| [`ns-wasm-test`](apps/ns-wasm-test) | NativeScript test app — runs the plugins on a simulator/emulator from a demo page and through Vitest + `vitest-ns` | +| [`ns-wasm-test`](apps/ns-wasm-test) | NativeScript test app — runs the plugins on a simulator/emulator from a demo page and through Rstest + `ns-rstest` | +| [`ns-lynx-app`](apps/ns-lynx-app) | NativeScript host app for @cross-code/ns-lynx — a React on Lynx UI embedded beside native NativeScript views | +| [`rspack-test-app`](apps/rspack-test-app) | NativeScript app bundled with `@cross-code/ns-rspack` (workspace member; installs with the root `pnpm install`) | | [`ns-wry-app`](apps/ns-wry-app) | NativeScript test app for @cross-code/ns-wry — WebView demo with google.com on iOS/Android | The WASM runtime plugins expose the same TypeScript API — see [WASM.md](WASM.md). All runtime -plugins (wasm3, WAMR, WasmKit) share the same foundation (`@cross-code/ns-wasm-core`) +plugins (wasm3, WAMR, WasmKit, WasmEdge, Chicory, Endive) share the same foundation (`@cross-code/ns-wasm-core`) which provides the wire protocol, error classes, adapter interfaces and generic `WasmRuntime`/`WasmModule`/`WasmFunction` classes. Each package README covers its own layout, development workflow and troubleshooting @@ -61,6 +77,8 @@ Before running its suite, install it separately: cd apps/ns-wasm-test && pnpm install ``` +`rspack-test-app` _is_ a workspace member, so the root `pnpm install` covers it. + Run TypeScript build and unit tests (no native toolchain required): ```bash @@ -145,24 +163,42 @@ Install Buck2 once (it is not on crates.io — the `buck2` crate is a placeholder): ```bash -curl -fsSL https://github.com/facebook/buck2/releases/latest/download/buck2-aarch64-apple-darwin.zst \ +curl -fsSL https://github.com/facebook/buck2/releases/download/latest/buck2-aarch64-apple-darwin.zst \ | zstd -d | sudo tee /usr/local/bin/buck2 > /dev/null && sudo chmod +x /usr/local/bin/buck2 ``` or `mise plugin install buck2 https://github.com/izaakschroeder/asdf-buck2`. -## WebAssembly plugins (wasm3, WAMR & WasmKit) +## rspack bundling + +[`@cross-code/ns-rspack`](packages/ns-rspack) is an [rspack](https://rspack.rs) +bundler for NativeScript apps. It owns every NativeScript-specific rule — entry +stubs, platform-suffixed resolution, XML/CSS loaders, copy rules, defines, HMR, +the `WatchStatePlugin` IPC — natively, with no `@nativescript/webpack` and no +webpack in its dependency tree. See +[packages/ns-rspack/README.md](packages/ns-rspack/README.md) for usage and the +differences table. Agent workflow: [AGENTS.md](packages/ns-rspack/AGENTS.md), +[MEMORY.md](packages/ns-rspack/MEMORY.md). + +The `rspack-test-app` exercises it end-to-end (`bundler: 'rspack'` in +`nativescript.config.ts`); `ns build ios` / `ns run ios` (watch + HMR) run the +bundler via the CLI bin and read build state over IPC. `ns-lynx-app`'s device +suite checks the bundle the app actually runs. + +## WebAssembly plugins (wasm3, WAMR, WasmKit, WasmEdge, Chicory & Endive) Usage and API documentation for the WebAssembly plugins — install, quick start, calling exports, linear memory, globals, host imports, value marshalling, error messages, the complete API reference, and shared troubleshooting — lives in **[WASM.md](WASM.md)**. -All three plugins expose the same TypeScript API; only the class names differ +All six plugins expose the same TypeScript API; only the class names differ (`Wasm3Runtime` / `Wasm3Module` / `Wasm3Function` vs `WamrRuntime` / `WamrModule` / `WamrFunction` vs -`WasmKitRuntime` / `WasmKitModule` / `WasmKitFunction`). +`WasmKitRuntime` / `WasmKitModule` / `WasmKitFunction`, and the WasmEdge, +Chicory and Endive equivalents). WasmKit is iOS-only (Swift-based runtime); Android throws a clear "not supported" error. +Chicory and Endive are Java-based and Android-only. ## Linting @@ -194,15 +230,20 @@ pnpm exec nx run ns-wamr:periphery.ios | Package | Docs | | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| WebAssembly plugins (wasm3, WAMR & WasmKit) | [WASM.md](WASM.md) — shared usage, API reference, marshalling, errors, troubleshooting | +| WebAssembly plugins (wasm3 & WAMR) | [WASM.md](WASM.md) — shared usage, API reference, marshalling, errors, troubleshooting | | `@cross-code/ns-wasm-core` | [AGENTS.md](AGENTS.md#shared-plugin-architecture) — wire protocol, WasmError, adapter interfaces, base classes | | `@cross-code/ns-wasm3` | [README](packages/ns-wasm3/README.md) — platform details, package layout, developing, troubleshooting, license | | `@cross-code/ns-wamr` | [README](packages/ns-wamr/README.md) — execution tiers, package layout, developing, troubleshooting, license | | `@cross-code/ns-wasm-kit-runtime` | [README](packages/ns-wasm-kit-runtime/README.md) — WasmKit Swift interpreter, iOS-only adapter, developing, troubleshooting | +| `@cross-code/ns-wasm-edge` | [package](packages/ns-wasm-edge) — WasmEdge runtime, follows the Rust/UniFFI architecture of wasm3/wamr | +| `@cross-code/ns-wasm-chicory` | [package](packages/ns-wasm-chicory) — Chicory pure-Java interpreter, Android-only, byte marshalling | +| `@cross-code/ns-endive` | [package](packages/ns-endive) — Endive Java interpreter, Android-only, developing, troubleshooting | | `@cross-code/ns-wasm-fixture` | [README](packages/ns-wasm-fixture/README.md) — exported subpaths, rebuilding the `.wasm` fixtures | -| `@cross-code/vitest-ns` | [README](packages/vitest-ns/README.md) — custom pool, Worker registry, concurrency, and transport | -| `@cross-code/vitest-ns-ui` | [README](packages/vitest-ns-ui/README.md) — optional NativeScript results UI | +| `@cross-code/ns-lynx` | [README](packages/ns-lynx/README.md) — why it needs no native layer, properties/events, producing a bundle | +| `@cross-code/ns-rstest` | [README](packages/ns-rstest/README.md) — Node host, Worker registry, concurrency, transport, and results UI | | `@cross-code/ns-wry` | [README](packages/ns-wry/README.md) — Rust + UniFFI architecture, platform stubs, developing, troubleshooting | +| `@cross-code/ns-rspack` | [README](packages/ns-rspack/README.md) — usage and webpack↔rspack gaps; [AGENTS.md](packages/ns-rspack/AGENTS.md) / [MEMORY.md](packages/ns-rspack/MEMORY.md) for agents | | `@cross-code/nx-buck2` | [README](packages/nx-buck2/README.md) — Buck2 executors/generators, CLI usage | | `ns-wasm-test` | [README](apps/ns-wasm-test/README.md) — running the demo page and the on-device Vitest suite, troubleshooting | +| `rspack-test-app` | rspack-bundled demo app (workspace member) — see [packages/ns-rspack/README.md](packages/ns-rspack/README.md) | | `ns-wry-app` | [README](apps/ns-wry-app/README.md) — WebView demo, build-plugin-and-run scripts, troubleshooting | diff --git a/apps/ns-lynx-app/.editorconfig b/apps/ns-lynx-app/.editorconfig new file mode 100644 index 00000000..84ba4fa6 --- /dev/null +++ b/apps/ns-lynx-app/.editorconfig @@ -0,0 +1,19 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.json] +indent_style = space +indent_size = 2 + +[*.js] +indent_style = space +indent_size = 2 + +[*.ts] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/apps/ns-lynx-app/.gitignore b/apps/ns-lynx-app/.gitignore new file mode 100644 index 00000000..1cb1231c --- /dev/null +++ b/apps/ns-lynx-app/.gitignore @@ -0,0 +1,28 @@ +# NativeScript +hooks/ +node_modules/ +platforms/ + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# General +.DS_Store +.AppleDouble +.LSOverride +.idea +.cloud +.project +tmp/ +typings/ + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json diff --git a/apps/ns-lynx-app/AGENTS.md b/apps/ns-lynx-app/AGENTS.md new file mode 100644 index 00000000..c4bb9bb5 --- /dev/null +++ b/apps/ns-lynx-app/AGENTS.md @@ -0,0 +1,74 @@ +# AGENTS.md — ns-lynx-app + +NativeScript host app for `@cross-code/ns-lynx`. Workspace-wide Nx and +NativeScript rules live in the root `AGENTS.md`. + +## Architecture + +Two build graphs meet here, and they do not share a toolchain: + +- `app/` compiles with `@cross-code/ns-rspack` for the NativeScript runtime. +- `lynx/` compiles with rspeedy for the Lynx runtime, emitting + `lynx/dist/main.lynx.bundle`. + +`rspack.config.ts` copies that bundle to `lynx/main.lynx.bundle` inside the app +folder, and `` loads it. The copy is a +build artifact, never committed. + +## Invariants + +- `lynx/` is excluded from the host `tsconfig.json`. It targets a different + runtime with different JSX settings (`jsxImportSource: '@lynx-js/react'`), + so a single TypeScript project cannot cover both. `typecheck` runs the host + app, `tsconfig.spec.json` (rstest entries + `app/tests/`), and `lynx/src`. +- Rstest files (`app/_ns-rstest.ts`, `app/_ns-rstest.worker.ts`, `app/tests/`) + belong only in `tsconfig.spec.json`. The production tsconfig must not include + them — same split as `apps/ns-wasm-test`. +- `build.ios` / `build.android` / `prepare` depend on `build.lynx`, and + `rspack.config.ts` throws when the bundle is missing. Keep both — the Nx + edge orders a normal build, the throw catches a direct `npx ns build`. +- Talk to Lynx only through the plugin's public API (`src`, `initData`, + `globalProps`, `sendGlobalEvent`, `updateData`, `reload`). Reaching into + `nativeViewProtected` from app code puts platform branches in the host. +- The Lynx page must not assume it owns the screen: it renders into whatever + box the NativeScript layout gives it. +- Keep `app/main-view-model.ts` free of platform branches; the plugin owns + those. + +## Device tests + +`app/tests/` runs on a real device through `@cross-code/ns-rstest` +(`nx run ns-lynx-app:test.ios` / `test.android`; they share port 17878, so run +them serially). `rspack.config.ts` calls `configureNativeScriptRstest`, which +swaps the application entry for the coordinator **only** under +`--env.rstestNativeScript` — a normal build is untouched. + +Keep the suite to things only the device can answer: the bundler's defines and +resolution rules, the assets the copy rules produced, `@NativeClass` +downleveling, and paths resolved against the synced app folder. Specs run +inside a NativeScript Worker, which has no view tree — `` rendering +cannot be covered here, and the plugin's platform-neutral rules belong in +`packages/ns-lynx`'s own unit tests. + +`app/tests/support/platform-marker.{ios,android}.ts` exist only to prove +platform-suffixed resolution; the neighbouring `.d.ts` is what TypeScript sees. + +## Verification + +Run through Nx from the repository root: + +```bash +pnpm exec nx run ns-lynx-app:typecheck +pnpm exec nx run ns-lynx-app:build.lynx +pnpm exec nx run ns-lynx-app:test.ios +pnpm exec nx run ns-lynx-app:run.ios +pnpm exec nx run ns-lynx-app:run.android +``` + +Use the project-local CLI through `npx ns` when diagnosing launch behavior. +Never use a bare global `ns` command. Export `LANG=en_US.UTF-8` on macOS if +CocoaPods misbehaves. + +Changing the Lynx SDK version means changing it in the plugin +(`platforms/ios/Podfile` and `platforms/android/include.gradle`) and rebuilding +this app on both platforms — the JS API surface differs between Lynx majors. diff --git a/apps/ns-lynx-app/App_Resources/Android/app.gradle b/apps/ns-lynx-app/App_Resources/Android/app.gradle new file mode 100644 index 00000000..9f60d1a5 --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/Android/app.gradle @@ -0,0 +1,25 @@ +// You can add your native dependencies here +dependencies { +// implementation 'androidx.multidex:multidex:2.0.1' +} + +android { + compileSdkVersion 35 + buildToolsVersion "35" + // ndkVersion "" + + defaultConfig { + minSdkVersion 24 + targetSdkVersion 35 + + // Version Information + versionCode 1 + versionName "1.0.0" + + generatedDensities = [] + } + + aaptOptions { + additionalParameters "--no-version-vectors" + } +} diff --git a/apps/ns-lynx-app/App_Resources/Android/before-plugins.gradle b/apps/ns-lynx-app/App_Resources/Android/before-plugins.gradle new file mode 100644 index 00000000..9faffb81 --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/Android/before-plugins.gradle @@ -0,0 +1,15 @@ +// this configurations is loaded before building plugins, as well as before building +// the app - this is where you can apply global settings and overrides + +project.ext { + // androidXAppCompat = "1.4.1" + // androidXExifInterface = "1.3.3" + // androidXFragment = "1.4.1" + // androidXMaterial = "1.5.0" + // androidXMultidex = "2.0.1" + // androidXTransition = "1.4.1" + // androidXViewPager = "1.0.0" + + // useKotlin = true + // kotlinVersion = "1.6.0" +} diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/AndroidManifest.xml b/apps/ns-lynx-app/App_Resources/Android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..66017732 --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/Android/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-hdpi/background.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-hdpi/background.png new file mode 100644 index 00000000..bbefbf42 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-hdpi/background.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png new file mode 100644 index 00000000..e788deb3 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-ldpi/background.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-ldpi/background.png new file mode 100644 index 00000000..f6a08eea Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-ldpi/background.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png new file mode 100644 index 00000000..e4cac1ad Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-mdpi/background.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-mdpi/background.png new file mode 100644 index 00000000..0c90f0f7 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-mdpi/background.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png new file mode 100644 index 00000000..ce3c3a4b Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml new file mode 100644 index 00000000..ada77f92 --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png new file mode 100644 index 00000000..3541570c Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png new file mode 100644 index 00000000..88267df0 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png new file mode 100644 index 00000000..abb0fc70 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png new file mode 100644 index 00000000..55800c9a Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png new file mode 100644 index 00000000..10897752 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png new file mode 100644 index 00000000..0703f90f Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable/ic_launcher_foreground.xml b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..fd826a31 --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/Android/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..7353dbd1 --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..69948d22 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..90a58cd7 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..70a2a0d0 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..1ee5a941 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..66e9d4bb Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/Android/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/values-v21/colors.xml b/apps/ns-lynx-app/App_Resources/Android/src/main/res/values-v21/colors.xml new file mode 100644 index 00000000..da5ca2fe --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/Android/src/main/res/values-v21/colors.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/values-v21/styles.xml b/apps/ns-lynx-app/App_Resources/Android/src/main/res/values-v21/styles.xml new file mode 100644 index 00000000..04d8a065 --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/Android/src/main/res/values-v21/styles.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/values-v29/styles.xml b/apps/ns-lynx-app/App_Resources/Android/src/main/res/values-v29/styles.xml new file mode 100644 index 00000000..9a2a79d5 --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/Android/src/main/res/values-v29/styles.xml @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/values/colors.xml b/apps/ns-lynx-app/App_Resources/Android/src/main/res/values/colors.xml new file mode 100644 index 00000000..78c4a519 --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/Android/src/main/res/values/colors.xml @@ -0,0 +1,14 @@ + + + + #F5F5F5 + + + #757575 + + + #65ADF1 + + + + diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/values/ic_launcher_background.xml b/apps/ns-lynx-app/App_Resources/Android/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 00000000..c5d5899f --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/Android/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/apps/ns-lynx-app/App_Resources/Android/src/main/res/values/styles.xml b/apps/ns-lynx-app/App_Resources/Android/src/main/res/values/styles.xml new file mode 100644 index 00000000..4f91b610 --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/Android/src/main/res/values/styles.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..1a8b0e64 --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "icon-20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "icon-20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "icon-29.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "icon-29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "icon-29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "icon-40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "icon-40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "icon-60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "icon-60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "icon-20.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "icon-20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "icon-29.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "icon-29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "icon-40.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "icon-40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "icon-76.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "icon-76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "icon-83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "icon-1024.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png new file mode 100644 index 00000000..b46c8bb2 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20.png new file mode 100644 index 00000000..d73288a7 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png new file mode 100644 index 00000000..c8d24cdc Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png new file mode 100644 index 00000000..1b00c848 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png new file mode 100644 index 00000000..72a16410 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png new file mode 100644 index 00000000..05ab752b Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png new file mode 100644 index 00000000..ee720825 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png new file mode 100644 index 00000000..2859288b Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png new file mode 100644 index 00000000..88824fa7 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png new file mode 100644 index 00000000..02a930cc Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png new file mode 100644 index 00000000..d7b077f1 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png new file mode 100644 index 00000000..2f872dd4 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png new file mode 100644 index 00000000..7fb23a70 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png new file mode 100644 index 00000000..cb04c367 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png new file mode 100644 index 00000000..e882226b Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/Contents.json b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json new file mode 100644 index 00000000..ab5edd0c --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchScreen-AspectFill.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchScreen-AspectFill@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchScreen-AspectFill@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png new file mode 100644 index 00000000..cb35cfae Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png new file mode 100644 index 00000000..6eefb9a7 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@3x.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@3x.png new file mode 100644 index 00000000..0ef51020 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@3x.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json new file mode 100644 index 00000000..444d7152 --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchScreen-Center.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchScreen-Center@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchScreen-Center@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png new file mode 100644 index 00000000..280c30e0 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png new file mode 100644 index 00000000..f984b9e4 Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@3x.png b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@3x.png new file mode 100644 index 00000000..95d86f3f Binary files /dev/null and b/apps/ns-lynx-app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@3x.png differ diff --git a/apps/ns-lynx-app/App_Resources/iOS/Info.plist b/apps/ns-lynx-app/App_Resources/iOS/Info.plist new file mode 100644 index 00000000..ea3e3ea2 --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/iOS/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiresFullScreen + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/apps/ns-lynx-app/App_Resources/iOS/LaunchScreen.storyboard b/apps/ns-lynx-app/App_Resources/iOS/LaunchScreen.storyboard new file mode 100644 index 00000000..c4e5a3f3 --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/iOS/LaunchScreen.storyboard @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/ns-lynx-app/App_Resources/iOS/build.xcconfig b/apps/ns-lynx-app/App_Resources/iOS/build.xcconfig new file mode 100644 index 00000000..80bc9923 --- /dev/null +++ b/apps/ns-lynx-app/App_Resources/iOS/build.xcconfig @@ -0,0 +1,7 @@ +// You can add custom settings here +// for example you can uncomment the following line to force distribution code signing +// CODE_SIGN_IDENTITY = iPhone Distribution +// To build for device with XCode you need to specify your development team. +// DEVELOPMENT_TEAM = YOUR_TEAM_ID; +ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; +IPHONEOS_DEPLOYMENT_TARGET = 16.0; diff --git a/apps/ns-lynx-app/README.md b/apps/ns-lynx-app/README.md new file mode 100644 index 00000000..f267fb72 --- /dev/null +++ b/apps/ns-lynx-app/README.md @@ -0,0 +1,113 @@ +# ns-lynx-app + +NativeScript host app for [`@cross-code/ns-lynx`](../../packages/ns-lynx). It +renders a [React on Lynx](https://lynxjs.org/react) UI inside a `` +that sits between ordinary NativeScript views on the same page. + +The app has two halves: + +- `app/` — the NativeScript host. It owns the page, the ActionBar, the status + bar above the Lynx surface, and the button below it. +- `lynx/` — a [rspeedy](https://lynxjs.org/rspeedy) + ReactLynx project. Its + build output (`lynx/dist/main.lynx.bundle`) is copied into the app bundle by + `rspack.config.ts` and loaded at runtime as `~/lynx/main.lynx.bundle`. + +Both directions of the host↔Lynx boundary are exercised: + +- `initData` flows host → Lynx and is read with `useInitData()`. +- The button calls `lynxView.sendGlobalEvent('hostPing', …)`, which the page + receives with `useLynxGlobalEventListener('hostPing', …)`. +- `lynxLoaded` / `lynxError` flow Lynx → host and drive the status label. + +## Layout + +```text +app/main-page.xml host page; declares +app/main-page.ts event wiring +app/main-view-model.ts initData and the global-event sender +app/_ns-rstest.ts device test entry (only used by --env.rstestNativeScript builds) +app/_ns-rstest.worker.ts the worker the specs run inside +app/tests/ the device suite +lynx/src/App.tsx the ReactLynx UI +lynx/lynx.config.ts rspeedy config +lynx/dist/main.lynx.bundle build output (gitignored) +rspack.config.ts copies the bundle into the app folder, wires the test entry +``` + +## Run + +From the repository root: + +```bash +pnpm exec nx run ns-lynx-app:run.ios +pnpm exec nx run ns-lynx-app:run.android +``` + +`build.lynx` runs first — the bundler config fails fast if the Lynx bundle is +missing rather than shipping an app that cannot render. + +From this directory: + +```bash +pnpm build.lynx # rebuild only the ReactLynx bundle +pnpm run.ios +pnpm run.android +``` + +While iterating on the Lynx UI alone, `pnpm --filter ns-lynx-app-lynx dev` +serves it to LynxExplorer without rebuilding the NativeScript app. + +## Test + +The app carries a device suite that runs with +[`@cross-code/ns-rstest`](../../packages/ns-rstest): real Rstest specs executed +inside a NativeScript Worker on a simulator or emulator. + +```bash +pnpm exec nx run ns-lynx-app:test.ios +pnpm exec nx run ns-lynx-app:test.android +``` + +The suites share port 17878, so run them one at a time. + +What they cover is what only the device can answer — the bundler's +compile-time defines and resolution rules, the assets the copy rules produced, +the `@NativeClass` downleveling `@cross-code/ns-rspack` performs, and the +plugin's bundle resolution against the app folder the CLI actually synced. The +plugin's platform-neutral rules are unit-tested in +[`packages/ns-lynx`](../../packages/ns-lynx); `` itself is not +covered, because a worker runtime has no view tree. + +## Package wiring + +The app is deliberately outside the root pnpm workspace and owns a separate +lockfile — the ns CLI needs its own `node_modules`. It is itself a pnpm +workspace root whose members are `lynx/` and the sibling `packages/*` it +consumes through the `workspace:` protocol, so one `pnpm install` here covers +both halves. + +When the plugin's `dist` output changes: + +```bash +pnpm exec nx run ns-lynx:build +cd apps/ns-lynx-app +pnpm install --force +``` + +## Troubleshooting + +- Export `LANG=en_US.UTF-8` if NativeScript reports a broken CocoaPods setup on + macOS. +- The first iOS build downloads the Lynx pod and takes several minutes. +- If `hooks/before-checkForChanges/nativescript-core.js` appears (a `.js`, not + `.mjs`), delete `hooks/` and re-run + `node node_modules/@nativescript/core/cli-hooks/postinstall.mjs`. The `.js` + trampoline is a stale artifact of an npm-based `ns create` and points at a + file @nativescript/core 9 no longer ships. +- If an old generated native project contains stale plugin metadata, remove + `platforms/` and rerun the target. + +## See also + +- [`@cross-code/ns-lynx`](../../packages/ns-lynx/README.md) +- [Workspace README](../../README.md) diff --git a/apps/ns-lynx-app/app/_ns-rstest.ts b/apps/ns-lynx-app/app/_ns-rstest.ts new file mode 100644 index 00000000..b7afcfbc --- /dev/null +++ b/apps/ns-lynx-app/app/_ns-rstest.ts @@ -0,0 +1,13 @@ +import '@valor/nativescript-websockets'; +import { Application } from '@nativescript/core'; +import { NativeScriptRstestCoordinator } from '@cross-code/ns-rstest/runtime'; +import { createRstestResultsPage } from '@cross-code/ns-rstest/ui'; + +const coordinator = new NativeScriptRstestCoordinator({ + createWorker: () => new Worker('./_ns-rstest.worker.ts'), +}); + +Application.run({ create: () => createRstestResultsPage(coordinator) }); +void coordinator.start().catch((error: unknown) => { + console.error('Unable to start the NativeScript Rstest coordinator', error); +}); diff --git a/apps/ns-lynx-app/app/_ns-rstest.worker.ts b/apps/ns-lynx-app/app/_ns-rstest.worker.ts new file mode 100644 index 00000000..74cc775b --- /dev/null +++ b/apps/ns-lynx-app/app/_ns-rstest.worker.ts @@ -0,0 +1,20 @@ +import '@nativescript/core/globals'; +import { + createBundlerTestRegistry, + registerNativeScriptRstestWorker, + type BundlerRequireContext, +} from '@cross-code/ns-rstest/runtime'; + +declare const require: { + context( + path: string, + recursive: boolean, + pattern: RegExp, + ): BundlerRequireContext; +}; + +const tests = require.context('./tests', true, /\.spec\.ts$/); + +registerNativeScriptRstestWorker({ + registry: createBundlerTestRegistry(tests), +}); diff --git a/apps/ns-lynx-app/app/app-root.xml b/apps/ns-lynx-app/app/app-root.xml new file mode 100644 index 00000000..54e70d97 --- /dev/null +++ b/apps/ns-lynx-app/app/app-root.xml @@ -0,0 +1,2 @@ + + diff --git a/apps/ns-lynx-app/app/app.css b/apps/ns-lynx-app/app/app.css new file mode 100644 index 00000000..aa97743a --- /dev/null +++ b/apps/ns-lynx-app/app/app.css @@ -0,0 +1,36 @@ +@import '@nativescript/theme/css/core.css'; +@import '@nativescript/theme/css/default.css'; + +.page { + background-color: #1a1b26; +} + +.action-bar { + background-color: #24283b; + color: #c0caf5; +} + +.host-bar { + padding: 12; + background-color: #24283b; +} + +.host-label { + color: #7aa2f7; + font-size: 13; + font-weight: bold; +} + +.host-status { + color: #565f89; + font-size: 12; + margin-top: 2; +} + +.host-button { + margin: 12; + background-color: #7aa2f7; + color: #1a1b26; + font-weight: bold; + border-radius: 8; +} diff --git a/apps/ns-lynx-app/app/app.ts b/apps/ns-lynx-app/app/app.ts new file mode 100644 index 00000000..83d01785 --- /dev/null +++ b/apps/ns-lynx-app/app/app.ts @@ -0,0 +1,14 @@ +/* +In NativeScript, the app.ts file is the entry point to your application. +You can use this file to perform app-level initialization, but the primary +purpose of the file is to pass control to the app’s first module. +*/ + +import { Application } from '@nativescript/core' + +Application.run({ moduleName: 'app-root' }) + +/* +Do not place any code after the application has been started as it will not +be executed on iOS. +*/ diff --git a/apps/ns-lynx-app/app/main-page.ts b/apps/ns-lynx-app/app/main-page.ts new file mode 100644 index 00000000..be5e7017 --- /dev/null +++ b/apps/ns-lynx-app/app/main-page.ts @@ -0,0 +1,24 @@ +import type { EventData, Page } from '@nativescript/core'; +import type { LynxErrorEventData, LynxView } from '@cross-code/ns-lynx'; +import { HelloWorldModel } from './main-view-model'; + +let model: HelloWorldModel | undefined; + +export function navigatingTo(args: EventData): void { + const page = args.object as Page; + model ??= new HelloWorldModel(); + page.bindingContext = model; + model.attach(page.getViewById('lynx')); +} + +export function onLynxLoaded(): void { + model?.onLynxLoaded(); +} + +export function onLynxError(args: LynxErrorEventData): void { + model?.onLynxError(args.message); +} + +export function onPing(): void { + model?.onPing(); +} diff --git a/apps/ns-lynx-app/app/main-page.xml b/apps/ns-lynx-app/app/main-page.xml new file mode 100644 index 00000000..d70a472f --- /dev/null +++ b/apps/ns-lynx-app/app/main-page.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + +