diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a06c9a0..a57edd6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -172,13 +172,13 @@ jobs: changed="$(git diff --name-only "$base...HEAD")" || run_all echo "$changed" - if grep -qE '^(packages/ns-wasm3/|\.github/workflows/ci\.yml$)' <<<"$changed"; then + 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/|\.github/workflows/ci\.yml$)' <<<"$changed"; then + 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" @@ -186,7 +186,7 @@ jobs: # 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-fixture/|\.github/workflows/ci\.yml$)' <<<"$changed"; then + 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" @@ -459,7 +459,7 @@ jobs: # 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-wasm3 ns-wamr vitest-ns vitest-ns-ui + 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 @@ -497,7 +497,7 @@ jobs: # 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-wasm3 ns-wamr vitest-ns vitest-ns-ui + 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 diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..91cac8e --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,12 @@ +{ + "ignorePatterns": [ + "**/node_modules/**", + "**/dist/**", + "**/.buck-out/**", + "**/target/**", + "**/.build/**", + "**/platforms/**", + "**/src/vendors/**", + "**/test-output/**" + ] +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..ddd1254 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/crates/oxc_linter/src/schema.json", + "plugins": ["typescript", "import", "unicorn"], + "rules": { + "no-unused-vars": "error", + "no-console": "warn", + "typescript/no-non-null-assertion": "warn", + "import/no-default-export": "off" + }, + "env": { + "node": true, + "es2022": true + }, + "globals": { + "globalThis": "readonly", + "NSData": "readonly", + "NSMutableArray": "readonly" + }, + "settings": {}, + "ignorePatterns": [ + "**/node_modules/**", + "**/dist/**", + "**/.buck-out/**", + "**/target/**", + "**/.build/**", + "**/platforms/**", + "**/src/vendors/**", + "**/test-output/**", + "**/*.d.ts" + ] +} diff --git a/AGENTS.md b/AGENTS.md index 33e652a..c5047c7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,13 +37,23 @@ ## NativeScript plugins in this repo -Three sibling plugins live under `packages/`, all sharing the same -architecture: Rust cargo workspace, UniFFI (uniffi-rs) Kotlin/Swift bindings, -and a TypeScript adapter. The two WASM plugins are mirror images of each -other (same class shapes, same wire protocol, same error mapping). Everything -the plugins share is documented in [Shared plugin architecture](#shared-plugin-architecture) -below; each package's AGENTS.md holds only engine-specific detail. - +Several sibling plugins live under `packages/`, all sharing the same +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 +`@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. + +- **`ns-wasm-core`** (`@cross-code/ns-wasm-core`) — shared foundation package + providing the wire protocol (`parseSignature`, `toWire`, `fromWire`, + `WasmError`), the native adapter interfaces (`NativeRuntimeAdapter`, + `NativeModuleAdapter`, `NativeFunctionAdapter`), and the generic + `WasmRuntime`/`WasmModule`/`WasmFunction` base classes that every WASM + runtime plugin extends. - **`ns-wasm3`** (`@cross-code/ns-wasm3`) — mature plugin binding the wasm3 interpreter (Swift Package on iOS, Kotlin + Rust JNI (cargo-ndk) on Android). See `packages/ns-wasm3/AGENTS.md`. @@ -53,6 +63,11 @@ below; each package's AGENTS.md holds only engine-specific detail. builds enable only the interpreter (see [Key differences](#key-differences-wasm3-vs-wamr)). WAMR-2.3.0 sources are vendored at `packages/ns-wamr/src/vendors/wamr/`. See `packages/ns-wamr/AGENTS.md`. +- **`ns-wasm-kit-runtime`** (`@cross-code/ns-wasm-kit-runtime`) — plugin wrapping + the [WasmKit](https://github.com/swiftwasm/WasmKit) Swift interpreter. + 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-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 @@ -80,7 +95,7 @@ until sources are present. ## Shared plugin architecture -Both plugins follow the same architecture: a platform-agnostic wire protocol +The WASM runtime plugins follow the same architecture: a platform-agnostic wire protocol (`src/lib/wire.ts`), per-platform TypeScript adapter files, Swift `@objc` classes on iOS, Kotlin + Rust JNI on Android, and Nx targets declared via `package.json`. Substitute `` = `Wasm3`-family @@ -129,6 +144,28 @@ Swapping those groups is a common mistake — verify the regex carefully. The native layers convert this notation to the engine's own format internally (WAMR's native format is `"(params)returns"`). +### TypeScript typing guidelines for native adapters + +The workspace uses `noImplicitAny: true` and `strict: true`. Follow these +conventions when writing TypeScript adapter code that bridges to native layers: + +- **Use `unknown` over `any`** for opaque bridge values (error refs, callback + handles, runtime proxy objects). Cast to a concrete interface only at the + call site. +- **Define proxy interfaces** in `src/lib/native-proxy.d.ts` for each native + class shape (e.g. `NativeChicoryRuntimeProxy`, `IosWasmEdgeFunctionProxy`). + The interfaces model the actual bridge methods; the native layer creates + the objects, TypeScript only calls them. +- **Error ref tuples**: iOS error out-params use `[unknown]` tuple typing + so they can be spread into method signatures. The `withErrorRef` helper + returns the error ref as `[unknown] | null`. +- **globalThis shape**: Define a `NativeScriptOrg` interface in + `native-proxy.d.ts` for `globalThis.org.nativescript.*` access. Cast + `globalThis as unknown as NativeScriptOrg` — never `as any`. +- **Callback narrowing**: When passing a `WireHostCallback` to a native + constructor that expects `(args: unknown[]) => unknown[]`, cast explicitly: + `cb as (args: unknown[]) => unknown[]`. + ### Missing imports surface at `findFunction` Both engines compile functions lazily. A missing imported function is @@ -542,8 +579,10 @@ wasm-pack-generated `.d.ts`. See `packages/ns-wasm-fixture/README.md`. | Path | Contents | | --------------------------------------- | ----------------------------------------------------------------------- | +| `packages/ns-wasm-core/AGENTS.md` | (none — shared foundation; see Shared plugin architecture above) | | `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-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/ns-wry-app` | test app: WebView demo, build-plugin-and-run workflow | diff --git a/README.md b/README.md index a9a03cc..697898d 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,18 @@ # cross-code An Nx monorepo for running WebAssembly on [NativeScript](https://nativescript.org) — -three sibling plugins built on Rust + UniFFI: +three 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) - [WAMR](https://github.com/bytecodealliance/wasm-micro-runtime) — WebAssembly Micro Runtime (2.3.0): interpreter, Fast JIT, LLVM JIT, AOT, WASI -- [`ns-wry`](packages/ns-wry) — Rust + [UniFFI](https://github.com/mozilla/uniffi-rs) - (uniffi-rs) auto-generated Kotlin/Swift bindings, cargo-ndk Android pipeline +- [WasmKit](https://github.com/swiftwasm/WasmKit) — Swift-based WebAssembly + runtime with WASI support, iOS-native through SwiftPM + +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. > **Project status: Active development.** APIs and project layout may change without notice; expect breaking changes between releases. @@ -15,8 +20,10 @@ three sibling plugins built on Rust + UniFFI: | Package | Description | | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| [`@cross-code/ns-wasm-core`](packages/ns-wasm-core) | Shared foundation — wire protocol, `WasmError`, adapter interfaces, base `WasmRuntime`/`WasmModule`/`WasmFunction` classes | | [`@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-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 | @@ -25,7 +32,10 @@ three sibling plugins built on Rust + UniFFI: | [`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-wry-app`](apps/ns-wry-app) | NativeScript test app for @cross-code/ns-wry — WebView demo with google.com on iOS/Android | -Both plugins expose the same TypeScript API — see [WASM.md](WASM.md). +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`) +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 (see [Per-package documentation](#per-package-documentation)). @@ -141,24 +151,54 @@ curl -fsSL https://github.com/facebook/buck2/releases/latest/download/buck2-aarc or `mise plugin install buck2 https://github.com/izaakschroeder/asdf-buck2`. -## WebAssembly plugins (wasm3 & WAMR) +## WebAssembly plugins (wasm3, WAMR & WasmKit) -Usage and API documentation for the two WebAssembly plugins — install, +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)**. -Both plugins expose the same TypeScript API; only the class names differ +All three plugins expose the same TypeScript API; only the class names differ (`Wasm3Runtime` / `Wasm3Module` / `Wasm3Function` vs -`WamrRuntime` / `WamrModule` / `WamrFunction`). +`WamrRuntime` / `WamrModule` / `WamrFunction` vs +`WasmKitRuntime` / `WasmKitModule` / `WasmKitFunction`). +WasmKit is iOS-only (Swift-based runtime); Android throws a clear "not supported" error. + +## Linting + +### Kotlin (Android wrappers) + +```bash +# Detekt (static analysis) + Ktlint (formatting) — hand-written sources only +pnpm exec nx run ns-wasm3:lint.android +pnpm exec nx run ns-wasm3:lint.android --configuration=format # auto-fix + +# Config: detekt.yml + .editorconfig at the repo root (shared by both engines) +``` + +### Swift (iOS wrappers) + +```bash +# SwiftLint — hand-written sources only +pnpm exec nx run ns-wasm3:lint.ios +pnpm exec nx run ns-wamr:lint.ios + +# Periphery — unused-code detection (builds the SwiftPM package) +pnpm exec nx run ns-wasm3:periphery.ios +pnpm exec nx run ns-wamr:periphery.ios + +# Config: .swiftlint.yml + .periphery.yml at the repo root (shared by all engines) +``` ## Per-package documentation | Package | Docs | | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| WebAssembly plugins (wasm3 & WAMR) | [WASM.md](WASM.md) — shared usage, API reference, marshalling, errors, troubleshooting | +| WebAssembly plugins (wasm3, WAMR & WasmKit) | [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-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 | diff --git a/apps/ns-wasm-test/AGENTS.md b/apps/ns-wasm-test/AGENTS.md index a65494a..1fde4df 100644 --- a/apps/ns-wasm-test/AGENTS.md +++ b/apps/ns-wasm-test/AGENTS.md @@ -33,6 +33,11 @@ app/ vitest-ns.worker.ts Worker registry tests/wasm3/ wasm3 Vitest specs tests/wamr/ WAMR Vitest specs + tests/wasmkit/ WasmKit specs (iOS-only engine) + tests/endive/ Endive specs (Android-only engine) + tests/chicory/ Chicory specs (Android-only engine) + tests/wasmedge/ WasmEdge specs + tests/runtime-support.ts per-engine platform matrix + suite gating wasm/fixture-suite.ts shared correctness checks wasm/wasm-assets.ts bundled fixture paths/byte reader vitest.ios.config.mts iOS simulator host config @@ -54,6 +59,12 @@ tsconfig.spec.json specs and test-only entries thread-safe. A worker is long-lived and files assigned to it share module and global state. - Dispose every `Wasm3Runtime`/`WamrRuntime` in `afterEach` or `finally`. +- A suite for a single-platform or not-yet-native engine goes through + `describeRuntime()` in `tests/runtime-support.ts`, never a bare + `describe.skip`. The matrix there is the one place recording which engines + target which platforms and which still lack a native layer. wasm3 and WAMR + ship natives on both platforms and must keep calling `describe` directly, so + a runtime that fails to load still fails the run rather than skipping. - `fixture-suite.ts` is the canonical shared marshalling specification. Add cross-plugin checks there; keep plugin-specific error/tier cases in specs. - The fixture suite must remain structurally typed and must not import either diff --git a/apps/ns-wasm-test/app/main-view-model.ts b/apps/ns-wasm-test/app/main-view-model.ts index 62acfc1..0bb5bf2 100644 --- a/apps/ns-wasm-test/app/main-view-model.ts +++ b/apps/ns-wasm-test/app/main-view-model.ts @@ -2,6 +2,9 @@ import { Observable } from '@nativescript/core'; import { WamrExecutionTier, WamrRuntime } from '@cross-code/ns-wamr'; import { Wasm3Runtime } from '@cross-code/ns-wasm3'; import { WasmKitRuntime } from '@cross-code/ns-wasm-kit-runtime'; +import { EndiveRuntime } from '@cross-code/ns-endive'; +import { WasmEdgeRuntime } from '@cross-code/ns-wasm-edge'; +import { ChicoryRuntime } from '@cross-code/ns-wasm-chicory'; import { createHostImports, @@ -59,13 +62,23 @@ export class WasmDemoModel extends Observable { } onRun() { - const sections = [runWasm3(), runWamr(), runWasmKit()]; + const sections = [runWasm3(), runWamr(), runWasmEdge()]; + // WasmKit is Swift-native — only include it on iOS. + if ((globalThis as any).isIOS) { + sections.push(runWasmKit()); + } + // Endive is Java-native — only include it on Android. + if ((globalThis as any).isAndroid) { + sections.push(runEndive()); + // Chicory is also pure-Java, Android-only. + sections.push(runChicory()); + } const checks = sections.flatMap((s) => s.checks); const summary = summarize(checks); this.status = summary.failed === 0 - ? `${summary.passed}/${summary.total} checks passed on both runtimes` + ? `${summary.passed}/${summary.total} checks passed on all runtimes` : `${summary.failed} of ${summary.total} checks FAILED`; this.report = sections .map((s) => [s.title, ...s.checks.map(formatCheck)].join('\n')) @@ -148,6 +161,14 @@ function runWasm3(): Section { ); } +function runWasmEdge(): Section { + return runSection( + 'WasmEdge', + () => WasmEdgeRuntime.version(), + () => new WasmEdgeRuntime(), + ); +} + function runWasmKit(): Section { return runSection( 'WasmKit', @@ -156,6 +177,22 @@ function runWasmKit(): Section { ); } +function runEndive(): Section { + return runSection( + 'Endive', + () => EndiveRuntime.version(), + () => new EndiveRuntime(), + ); +} + +function runChicory(): Section { + return runSection( + 'Chicory', + () => ChicoryRuntime.version(), + () => new ChicoryRuntime(), + ); +} + function runWamr(): Section { return runSection( `WAMR (${WamrExecutionTier[WamrExecutionTier.Interpreter]})`, diff --git a/apps/ns-wasm-test/app/tests/chicory/fixture-module.spec.ts b/apps/ns-wasm-test/app/tests/chicory/fixture-module.spec.ts new file mode 100644 index 0000000..7f37eea --- /dev/null +++ b/apps/ns-wasm-test/app/tests/chicory/fixture-module.spec.ts @@ -0,0 +1,22 @@ +/** + * The Rust fixture module driven through @cross-code/ns-wasm-chicory + * (Android-only — pure-Java runtime). + */ +import { afterEach, beforeEach, expect, it } from 'vitest'; +import { ChicoryError, ChicoryRuntime, type ChicoryModule } from '@cross-code/ns-wasm-chicory'; +import { callFixture, createHostImports, runFixtureChecks, summarize, type HostCall } from '../../wasm/fixture-suite'; +import { appWasmPath, FIXTURE_WASM } from '../../wasm/wasm-assets'; +import { CHICORY, describeRuntime } from '../runtime-support'; +// Chicory is pure Java, so this suite is Android-only — and skips even there +// until the plugin's .aar lands. See ../runtime-support.ts. +const describeChicory = describeRuntime(CHICORY); +describeChicory('the fixture module through @cross-code/ns-wasm-chicory', () => { + let runtime: ChicoryRuntime; let module: ChicoryModule; let log: HostCall[]; + beforeEach(() => { runtime = new ChicoryRuntime(); log = []; module = runtime.loadModule(appWasmPath(FIXTURE_WASM), createHostImports(log)); }); + afterEach(() => { if (runtime) runtime.dispose(); }); + it('reports version', () => { expect(ChicoryRuntime.version()).toMatch(/\d/); }); + it('all value types', () => { expect(callFixture(module, 'add_i32', 2, 40)).toBe(42); expect(callFixture(module, 'add_i64', 9007199254740993n, 2n)).toBe(9007199254740995n); expect(callFixture(module, 'mul_f32', 1.5, 2.0)).toBeCloseTo(3.0); expect(callFixture(module, 'add_f64', 0.1, 0.2)).toBe(0.1 + 0.2); }); + it('host imports', () => { expect(callFixture(module, 'call_transform_i32', 3)).toBe(6); }); + it('invalid bytes throw', () => { expect(() => runtime.loadModule(new Uint8Array([0, 1, 2, 3]))).toThrow(ChicoryError); }); + it('shared fixture suite passes', () => { const checks = runFixtureChecks(module, log); expect(summarize(checks).failed).toBe(0); }); +}); diff --git a/apps/ns-wasm-test/app/tests/endive/fixture-module.spec.ts b/apps/ns-wasm-test/app/tests/endive/fixture-module.spec.ts new file mode 100644 index 0000000..144b4eb --- /dev/null +++ b/apps/ns-wasm-test/app/tests/endive/fixture-module.spec.ts @@ -0,0 +1,75 @@ +/** + * The Rust fixture module driven through the plugin's public API, on the + * device's own Endive interpreter (Android-only; iOS throws unsupported). + * + * Vitest discovers this file in Node, then @cross-code/vitest-ns + * executes it inside a NativeScript Worker on the selected device. + */ +import { afterEach, beforeEach, expect, it } from 'vitest'; +import { + EndiveError, + EndiveRuntime, + type EndiveModule, +} from '@cross-code/ns-endive'; + +import { + callFixture, + createHostImports, + runFixtureChecks, + summarize, + type HostCall, +} from '../../wasm/fixture-suite'; +import { appWasmPath, FIXTURE_WASM } from '../../wasm/wasm-assets'; +import { describeRuntime, ENDIVE } from '../runtime-support'; + +// Endive runs on the JVM, so this suite is Android-only — and skips even there +// until the plugin's .aar lands. See ../runtime-support.ts. +const describeEndive = describeRuntime(ENDIVE); + +describeEndive('the fixture module through @cross-code/ns-endive', () => { + let runtime: EndiveRuntime; + let module: EndiveModule; + let log: HostCall[]; + + beforeEach(() => { + runtime = new EndiveRuntime(); + log = []; + module = runtime.loadModule( + appWasmPath(FIXTURE_WASM), + createHostImports(log), + ); + }); + + afterEach(() => { + if (runtime) runtime.dispose(); + }); + + it('reports the Endive version', () => { + expect(EndiveRuntime.version()).toMatch(/\d/); + }); + + it('all value types through the fixture module', () => { + expect(callFixture(module, 'add_i32', 2, 40)).toBe(42); + expect(callFixture(module, 'add_i64', 9007199254740993n, 2n)).toBe( + 9007199254740995n, + ); + expect(callFixture(module, 'mul_f32', 1.5, 2.0)).toBeCloseTo(3.0); + expect(callFixture(module, 'add_f64', 0.1, 0.2)).toBe(0.1 + 0.2); + }); + + it('host imports work', () => { + expect(callFixture(module, 'call_transform_i32', 3)).toBe(6); + }); + + it('invalid module bytes throw', () => { + expect(() => runtime.loadModule(new Uint8Array([0, 1, 2, 3]))).toThrow( + EndiveError, + ); + }); + + it('the shared fixture suite passes', () => { + const checks = runFixtureChecks(module, log); + const report = summarize(checks); + expect(report.failed).toBe(0); + }); +}); diff --git a/apps/ns-wasm-test/app/tests/runtime-support.ts b/apps/ns-wasm-test/app/tests/runtime-support.ts new file mode 100644 index 0000000..353841b --- /dev/null +++ b/apps/ns-wasm-test/app/tests/runtime-support.ts @@ -0,0 +1,119 @@ +/** + * Which platforms each WASM runtime targets, and whether its native layer + * exists yet — the two facts that decide whether a runtime's device suite + * runs, skips, or fails. + * + * A suite may bow out for exactly two reasons: + * + * 1. **Wrong platform.** Some engines are single-platform by construction: + * WasmKit is Swift, Endive and Chicory are Java. On the other platform the + * plugin's adapter throws "not supported" by design, so running the suite + * there would only assert against a deliberate stub. + * 2. **The native layer has not landed.** `nativeLayer: 'pending'` marks a + * package that is still TypeScript-only — no xcframework, no `.aar`. Those + * suites skip instead of failing five times apiece and drowning out the + * runtimes that do have natives. + * + * What this deliberately does *not* do is skip on "native runtime not found" + * alone. wasm3 and WAMR ship natives on both platforms, and their specs call + * `describe` directly rather than going through here — so if their xcframework + * or `.aar` ever fails to load, those suites still fail loudly. That is the + * regression signal these gates exist to protect. + * + * When a pending engine's native layer lands, flip its `nativeLayer` to + * `'shipped'`: from then on a missing runtime on a platform it targets is a + * failure rather than a skip. + */ +import { isIOS } from '@nativescript/core'; +import { describe } from 'vitest'; + +export type Platform = 'ios' | 'android'; + +/** The platform this worker is running on. */ +export const currentPlatform: Platform = isIOS ? 'ios' : 'android'; + +export interface RuntimeSupport { + /** Platforms the engine is meant to run on at all. */ + readonly platforms: readonly Platform[]; + /** + * `'shipped'` once the package carries a native layer. Only a `'pending'` + * engine is allowed to skip on a platform it targets. + */ + readonly nativeLayer: 'shipped' | 'pending'; + /** Whether this engine's native global is present on the device. */ + readonly isLoaded: () => boolean; +} + +function g(): any { + return globalThis as any; +} + +// The matrix. Each `isLoaded` probes the same globals the plugin's own +// `createAdapter` looks for, so a suite's verdict and the plugin's verdict can +// never disagree. + +/** WasmKit is Swift-native, served through SwiftPM. */ +export const WASMKIT: RuntimeSupport = { + platforms: ['ios'], + nativeLayer: 'pending', + isLoaded: () => + g().NSWasmKitRuntime != null || + g().org?.nativescript?.wasmkit?.NSWasmKitRuntime != null, +}; + +/** Endive runs on the JVM. */ +export const ENDIVE: RuntimeSupport = { + platforms: ['android'], + nativeLayer: 'pending', + isLoaded: () => + g().org?.nativescript?.endive?.NSCEndiveRuntime != null || + g().NSCEndiveRuntime != null, +}; + +/** Chicory is a pure-Java interpreter. */ +export const CHICORY: RuntimeSupport = { + platforms: ['android'], + nativeLayer: 'shipped', + isLoaded: () => + g().org?.nativescript?.chicory?.NSCChicoryRuntime != null || + g().NSCChicoryRuntime != null, +}; + +/** WasmEdge has a Swift package on iOS and Kotlin + Rust JNI on Android. */ +export const WASMEDGE: RuntimeSupport = { + platforms: ['ios', 'android'], + nativeLayer: 'pending', + isLoaded: () => + g().NSCWasmEdgeRuntime != null || + g().org?.nativescript?.wasmedge?.NSCWasmEdgeRuntime != null, +}; + +function skipReason(support: RuntimeSupport): string | undefined { + if (!support.platforms.includes(currentPlatform)) { + return `${currentPlatform} is not a supported platform for this runtime`; + } + // On a platform it does target, an engine may only bow out while its native + // layer is unbuilt — and only if the global really is absent, so that a + // `nativeLayer` left stale after the natives land makes the suite run rather + // than silently skip. + if (support.nativeLayer === 'pending' && !support.isLoaded()) { + return `native layer for ${currentPlatform} has not been built yet`; + } + return undefined; +} + +/** + * A `describe` for one runtime's device suite, gated by the matrix above. + * + * A gated-out suite is still collected, so its tests report as skipped with the + * reason in the suite name rather than vanishing from the run. + */ +export function describeRuntime( + support: RuntimeSupport, +): (name: string, fn: () => void) => void { + const reason = skipReason(support); + return (name, fn) => { + if (reason) describe.skip(`${name} — skipped: ${reason}`, fn); + else describe(name, fn); + }; +} diff --git a/apps/ns-wasm-test/app/tests/wamr/fixture-module.spec.ts b/apps/ns-wasm-test/app/tests/wamr/fixture-module.spec.ts index 30e54cd..b7c7b4f 100644 --- a/apps/ns-wasm-test/app/tests/wamr/fixture-module.spec.ts +++ b/apps/ns-wasm-test/app/tests/wamr/fixture-module.spec.ts @@ -137,7 +137,7 @@ describe('the fixture module through @cross-code/ns-wamr', () => { it('refuses to link an import the module does not declare', () => { expect(() => - module.linkHostFunction('env', 'not_imported', 'v()', () => undefined), + module.linkHostFunction('env', 'not_imported', 'v()', () => []), ).toThrow(WamrError); }); diff --git a/apps/ns-wasm-test/app/tests/wasm3/fixture-module.spec.ts b/apps/ns-wasm-test/app/tests/wasm3/fixture-module.spec.ts index ef7082f..922fbfc 100644 --- a/apps/ns-wasm-test/app/tests/wasm3/fixture-module.spec.ts +++ b/apps/ns-wasm-test/app/tests/wasm3/fixture-module.spec.ts @@ -126,7 +126,7 @@ describe('the fixture module through @cross-code/ns-wasm3', () => { it('refuses to link an import the module does not declare', () => { expect(() => - module.linkHostFunction('env', 'not_imported', 'v()', () => undefined), + module.linkHostFunction('env', 'not_imported', 'v()', () => []), ).toThrow(Wasm3Error); }); diff --git a/apps/ns-wasm-test/app/tests/wasmedge/fixture-module.spec.ts b/apps/ns-wasm-test/app/tests/wasmedge/fixture-module.spec.ts new file mode 100644 index 0000000..2c29168 --- /dev/null +++ b/apps/ns-wasm-test/app/tests/wasmedge/fixture-module.spec.ts @@ -0,0 +1,23 @@ +/** + * The Rust fixture module driven through @cross-code/ns-wasm-edge. + */ +import { afterEach, beforeEach, expect, it } from 'vitest'; +import { WasmEdgeError, WasmEdgeRuntime, type WasmEdgeModule } from '@cross-code/ns-wasm-edge'; +import { callFixture, createHostImports, runFixtureChecks, summarize, type HostCall } from '../../wasm/fixture-suite'; +import { appWasmPath, FIXTURE_WASM } from '../../wasm/wasm-assets'; +import { describeRuntime, WASMEDGE } from '../runtime-support'; + +// WasmEdge targets both platforms, so this suite skips only until the plugin's +// xcframework / .aar land. See ../runtime-support.ts. +const describeWasmEdge = describeRuntime(WASMEDGE); + +describeWasmEdge('the fixture module through @cross-code/ns-wasm-edge', () => { + let runtime: WasmEdgeRuntime; let module: WasmEdgeModule; let log: HostCall[]; + beforeEach(() => { runtime = new WasmEdgeRuntime(); log = []; module = runtime.loadModule(appWasmPath(FIXTURE_WASM), createHostImports(log)); }); + afterEach(() => { if (runtime) runtime.dispose(); }); + it('reports version', () => { expect(WasmEdgeRuntime.version()).toMatch(/\d/); }); + it('all value types', () => { expect(callFixture(module, 'add_i32', 2, 40)).toBe(42); expect(callFixture(module, 'add_i64', 9007199254740993n, 2n)).toBe(9007199254740995n); expect(callFixture(module, 'mul_f32', 1.5, 2.0)).toBeCloseTo(3.0); expect(callFixture(module, 'add_f64', 0.1, 0.2)).toBe(0.1 + 0.2); }); + it('host imports', () => { expect(callFixture(module, 'call_transform_i32', 3)).toBe(6); }); + it('invalid bytes throw', () => { expect(() => runtime.loadModule(new Uint8Array([0, 1, 2, 3]))).toThrow(WasmEdgeError); }); + it('shared fixture suite passes', () => { const checks = runFixtureChecks(module, log); expect(summarize(checks).failed).toBe(0); }); +}); diff --git a/apps/ns-wasm-test/app/tests/wasmkit/fixture-module.spec.ts b/apps/ns-wasm-test/app/tests/wasmkit/fixture-module.spec.ts index 3eb6398..609bce4 100644 --- a/apps/ns-wasm-test/app/tests/wasmkit/fixture-module.spec.ts +++ b/apps/ns-wasm-test/app/tests/wasmkit/fixture-module.spec.ts @@ -10,7 +10,7 @@ * their own assertions: declared signatures, i64 precision, host-import * round trips, and the error paths. */ -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { afterEach, beforeEach, expect, it } from 'vitest'; import { WasmKitError, WasmKitRuntime, @@ -24,60 +24,63 @@ import { summarize, type HostCall, } from '../../wasm/fixture-suite'; -import { appWasmPath, FIXTURE_WASM, readAppFile } from '../../wasm/wasm-assets'; +import { appWasmPath, FIXTURE_WASM } from '../../wasm/wasm-assets'; +import { describeRuntime, WASMKIT } from '../runtime-support'; -describe('the fixture module through @cross-code/ns-wasm-kit-runtime', () => { - let runtime: WasmKitRuntime; - let module: WasmKitModule; - let log: HostCall[]; +// WasmKit is Swift-native, so this suite is iOS-only — and skips even there +// until the plugin's xcframework lands. See ../runtime-support.ts. +const describeWasmKit = describeRuntime(WASMKIT); - beforeEach(() => { - runtime = new WasmKitRuntime(); - log = []; - module = runtime.loadModule( - appWasmPath(FIXTURE_WASM), - createHostImports(log), - ); - }); +describeWasmKit( + 'the fixture module through @cross-code/ns-wasm-kit-runtime', + () => { + let runtime: WasmKitRuntime; + let module: WasmKitModule; + let log: HostCall[]; - afterEach(() => { - // beforeEach may have thrown before the runtime existed (a missing native - // layer does exactly that), and an unguarded dispose would then report a - // second error. - if (runtime) runtime.dispose(); - }); + beforeEach(() => { + runtime = new WasmKitRuntime(); + log = []; + module = runtime.loadModule( + appWasmPath(FIXTURE_WASM), + createHostImports(log), + ); + }); - it('reports the WasmKit version', () => { - expect(WasmKitRuntime.version()).toMatch(/\d/); - }); + afterEach(() => { + // beforeEach may have thrown before the runtime existed (a missing native + // layer does exactly that), and an unguarded dispose would then report a + // second error. + if (runtime) runtime.dispose(); + }); - it('all value types through the fixture module', () => { - expect(callFixture('add_i32', module, 2, 40)).toBe(42); - expect(callFixture('add_i64', module, '9007199254740993', '2')).toBe( - 9007199254740995n, - ); - expect(callFixture('mul_f32', module, 1.5, 2.0)).toBeCloseTo(3.0); - expect(callFixture('div_f64', module, 1.0, 8.0)).toBeCloseTo(0.125); - }); + it('reports the WasmKit version', () => { + expect(WasmKitRuntime.version()).toMatch(/\d/); + }); - it('host imports work', () => { - // linkHostFunction with a simple addition - module.linkHostFunction('env', 'host_add', 'i(ii)', (args) => { - return (args[0] as number) + (args[1] as number); + it('all value types through the fixture module', () => { + expect(callFixture(module, 'add_i32', 2, 40)).toBe(42); + expect(callFixture(module, 'add_i64', 9007199254740993n, 2n)).toBe( + 9007199254740995n, + ); + expect(callFixture(module, 'mul_f32', 1.5, 2.0)).toBeCloseTo(3.0); + expect(callFixture(module, 'add_f64', 0.1, 0.2)).toBe(0.1 + 0.2); }); - const result = callFixture('call_host_add', module, 3, 4); - expect(result).toBe(7); - }); - it('invalid module bytes throw', () => { - expect(() => runtime.loadModule(new Uint8Array([0, 1, 2, 3]))).toThrow( - WasmKitError, - ); - }); + it('host imports work', () => { + expect(callFixture(module, 'call_transform_i32', 3)).toBe(6); + }); - it('the shared fixture suite passes', () => { - const checks = runFixtureChecks(module as any); - const report = summarize(checks); - expect(report.failed).toBe(0); - }); -}); + it('invalid module bytes throw', () => { + expect(() => runtime.loadModule(new Uint8Array([0, 1, 2, 3]))).toThrow( + WasmKitError, + ); + }); + + it('the shared fixture suite passes', () => { + const checks = runFixtureChecks(module, log); + const report = summarize(checks); + expect(report.failed).toBe(0); + }); + }, +); diff --git a/apps/ns-wasm-test/package.json b/apps/ns-wasm-test/package.json index 6550b4e..a79f431 100644 --- a/apps/ns-wasm-test/package.json +++ b/apps/ns-wasm-test/package.json @@ -29,7 +29,10 @@ "@nativescript/core": "~9.0.0", "@nativescript/theme": "^3.1.0", "@valor/nativescript-websockets": "2.0.3", - "@cross-code/ns-wasm-kit-runtime": "file:../../packages/ns-wasm-kit-runtime" + "@cross-code/ns-wasm-kit-runtime": "file:../../packages/ns-wasm-kit-runtime", + "@cross-code/ns-endive": "file:../../packages/ns-endive", + "@cross-code/ns-wasm-edge": "file:../../packages/ns-wasm-edge", + "@cross-code/ns-wasm-chicory": "file:../../packages/ns-wasm-chicory" }, "devDependencies": { "@nativescript/android": "9.0.5", diff --git a/apps/ns-wasm-test/pnpm-lock.yaml b/apps/ns-wasm-test/pnpm-lock.yaml index e24f96c..36aefcb 100644 --- a/apps/ns-wasm-test/pnpm-lock.yaml +++ b/apps/ns-wasm-test/pnpm-lock.yaml @@ -4,16 +4,31 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + '@cross-code/ns-wasm-core': file:../../packages/ns-wasm-core + importers: .: dependencies: + '@cross-code/ns-endive': + specifier: file:../../packages/ns-endive + version: file:../../packages/ns-endive '@cross-code/ns-wamr': specifier: file:../../packages/ns-wamr version: file:../../packages/ns-wamr + '@cross-code/ns-wasm-chicory': + specifier: file:../../packages/ns-wasm-chicory + version: file:../../packages/ns-wasm-chicory + '@cross-code/ns-wasm-edge': + specifier: file:../../packages/ns-wasm-edge + version: file:../../packages/ns-wasm-edge '@cross-code/ns-wasm-fixture': specifier: file:../../packages/ns-wasm-fixture version: file:../../packages/ns-wasm-fixture + '@cross-code/ns-wasm-kit-runtime': + specifier: file:../../packages/ns-wasm-kit-runtime + version: file:../../packages/ns-wasm-kit-runtime '@cross-code/ns-wasm3': specifier: file:../../packages/ns-wasm3 version: file:../../packages/ns-wasm3 @@ -137,12 +152,27 @@ packages: resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} + '@cross-code/ns-endive@file:../../packages/ns-endive': + resolution: {directory: ../../packages/ns-endive, type: directory} + '@cross-code/ns-wamr@file:../../packages/ns-wamr': resolution: {directory: ../../packages/ns-wamr, type: directory} + '@cross-code/ns-wasm-chicory@file:../../packages/ns-wasm-chicory': + resolution: {directory: ../../packages/ns-wasm-chicory, type: directory} + + '@cross-code/ns-wasm-core@file:../../packages/ns-wasm-core': + resolution: {directory: ../../packages/ns-wasm-core, type: directory} + + '@cross-code/ns-wasm-edge@file:../../packages/ns-wasm-edge': + resolution: {directory: ../../packages/ns-wasm-edge, type: directory} + '@cross-code/ns-wasm-fixture@file:../../packages/ns-wasm-fixture': resolution: {directory: ../../packages/ns-wasm-fixture, type: directory} + '@cross-code/ns-wasm-kit-runtime@file:../../packages/ns-wasm-kit-runtime': + resolution: {directory: ../../packages/ns-wasm-kit-runtime, type: directory} + '@cross-code/ns-wasm3@file:../../packages/ns-wasm3': resolution: {directory: ../../packages/ns-wasm3, type: directory} @@ -2245,12 +2275,37 @@ snapshots: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@cross-code/ns-wamr@file:../../packages/ns-wamr': {} + '@cross-code/ns-endive@file:../../packages/ns-endive': + dependencies: + '@cross-code/ns-wasm-core': file:../../packages/ns-wasm-core + tslib: 2.8.1 + + '@cross-code/ns-wamr@file:../../packages/ns-wamr': + dependencies: + '@cross-code/ns-wasm-core': file:../../packages/ns-wasm-core + + '@cross-code/ns-wasm-chicory@file:../../packages/ns-wasm-chicory': + dependencies: + '@cross-code/ns-wasm-core': file:../../packages/ns-wasm-core + tslib: 2.8.1 + + '@cross-code/ns-wasm-core@file:../../packages/ns-wasm-core': {} + + '@cross-code/ns-wasm-edge@file:../../packages/ns-wasm-edge': + dependencies: + '@cross-code/ns-wasm-core': file:../../packages/ns-wasm-core + tslib: 2.8.1 '@cross-code/ns-wasm-fixture@file:../../packages/ns-wasm-fixture': {} + '@cross-code/ns-wasm-kit-runtime@file:../../packages/ns-wasm-kit-runtime': + dependencies: + '@cross-code/ns-wasm-core': file:../../packages/ns-wasm-core + tslib: 2.8.1 + '@cross-code/ns-wasm3@file:../../packages/ns-wasm3': dependencies: + '@cross-code/ns-wasm-core': file:../../packages/ns-wasm-core tslib: 2.8.1 '@cross-code/vitest-ns-ui@file:../../packages/vitest-ns-ui(@cross-code/vitest-ns@file:../../packages/vitest-ns(@babel/core@7.29.7(supports-color@8.1.1))(@vitest/runner@4.1.10)(supports-color@8.1.1)(vitest@4.1.10)(webpack@5.109.2))(@nativescript/core@9.0.20)': diff --git a/apps/ns-wasm-test/pnpm-workspace.yaml b/apps/ns-wasm-test/pnpm-workspace.yaml index e3a488f..2acb0ff 100644 --- a/apps/ns-wasm-test/pnpm-workspace.yaml +++ b/apps/ns-wasm-test/pnpm-workspace.yaml @@ -4,6 +4,12 @@ # its own pnpm-lock.yaml. packages: [] +# The app consumes local plugins through `file:`. Their source manifests use +# `workspace:*` for the shared core, which is otherwise resolved against this +# intentionally isolated workspace. Pin that transitive dependency locally. +overrides: + '@cross-code/ns-wasm-core': file:../../packages/ns-wasm-core + # pnpm 11 blocks dependency build scripts by default (ERR_PNPM_IGNORED_BUILDS). # These packages ship native binaries or CLI glue used by the app build. allowBuilds: diff --git a/apps/ns-wasm-test/tsconfig.json b/apps/ns-wasm-test/tsconfig.json index b8a1919..8aa0ed8 100644 --- a/apps/ns-wasm-test/tsconfig.json +++ b/apps/ns-wasm-test/tsconfig.json @@ -29,19 +29,31 @@ ], "references": [ { - "path": "../../packages/ns-wamr" + "path": "../../packages/ns-wasm-fixture" }, { - "path": "../../packages/ns-wasm3" + "path": "../../packages/vitest-ns-ui" }, { - "path": "../../packages/ns-wasm-fixture" + "path": "../../packages/vitest-ns" }, { - "path": "../../packages/vitest-ns" + "path": "../../packages/ns-wasm-chicory" }, { - "path": "../../packages/vitest-ns-ui" + "path": "../../packages/ns-wasm-edge" + }, + { + "path": "../../packages/ns-endive" + }, + { + "path": "../../packages/ns-wasm-kit-runtime" + }, + { + "path": "../../packages/ns-wasm3" + }, + { + "path": "../../packages/ns-wamr" } ] } diff --git a/detekt.yml b/detekt.yml index 76e6568..ab0da50 100644 --- a/detekt.yml +++ b/detekt.yml @@ -13,9 +13,11 @@ build: complexity: # NativeWasm3/NativeWamr are flat JNI declaration facades: one # `external fun` per native entry point (~30). The default object - # threshold of 11 doesn't fit. + # threshold of 11 doesn't fit. NSCChicory* wrapper classes also + # exceed the default class threshold. TooManyFunctions: thresholdInObjects: 40 + thresholdInClasses: 20 style: # HostTrampoline.invoke returns early on each trap condition (bad result diff --git a/nx.json b/nx.json index c153a40..714fed5 100644 --- a/nx.json +++ b/nx.json @@ -38,6 +38,12 @@ "ciTargetName": "test-ci", "testMode": "watch" } + }, + { + "plugin": "./tools/nx-oxlint.js" + }, + { + "plugin": "./tools/nx-oxfmt.js" } ], "analytics": true, @@ -98,6 +104,12 @@ "production", "^production" ] + }, + "lint": { + "cache": true + }, + "format": { + "cache": true } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 7a13bec..d639a99 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "@vitest/coverage-v8": "~4.1.0", "nativescript": "^9.0.6", "nx": "23.1.0", + "oxfmt": "^0.62.0", + "oxlint": "^1.77.0", "prettier": "^3.8.1", "tslib": "^2.3.0", "typescript": "~6.0.3", diff --git a/packages/ns-endive/package.json b/packages/ns-endive/package.json new file mode 100644 index 0000000..043fa4b --- /dev/null +++ b/packages/ns-endive/package.json @@ -0,0 +1,50 @@ +{ + "name": "@cross-code/ns-endive", + "version": "0.1.0", + "description": "NativeScript plugin that runs WebAssembly modules with the Endive interpreter (https://github.com/bytecodealliance/endive) — Java/JNI on Android, TypeScript adapter with platform-native classes.", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "@cross-code/source": "./src/index.ts", + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "files": [ + "dist", + "nativescript.config.ts", + "platforms/android/include.gradle", + "platforms/ios/NSCEndive/Package.swift", + "platforms/ios/NSCEndive/Sources", + "!**/*.tsbuildinfo" + ], + "nativescript": { + "platforms": { + "ios": "8.6.0", + "android": "8.6.0" + } + }, + "nx": { + "name": "ns-endive", + "targets": { + "coverage": { + "executor": "nx:run-script", + "options": { + "script": "coverage" + } + } + } + }, + "scripts": { + "coverage": "vitest run --coverage" + }, + "dependencies": { + "@cross-code/ns-wasm-core": "workspace:*", + "tslib": "^2.3.0" + } +} diff --git a/packages/ns-endive/src/index.ts b/packages/ns-endive/src/index.ts new file mode 100644 index 0000000..59d5c87 --- /dev/null +++ b/packages/ns-endive/src/index.ts @@ -0,0 +1,16 @@ +export { + EndiveError, + type ParsedSignature, + type WasmArg, + type WasmValue, + type WasmValueType, +} from './lib/wire.js'; +export { + EndiveFunction, + EndiveModule, + EndiveRuntime, + type EndiveHostFunction, + type EndiveImports, + type EndiveModuleSource, + type EndiveRuntimeOptions, +} from './lib/endive.js'; diff --git a/packages/ns-endive/src/lib/endive-android.ts b/packages/ns-endive/src/lib/endive-android.ts new file mode 100644 index 0000000..6dc2d9e --- /dev/null +++ b/packages/ns-endive/src/lib/endive-android.ts @@ -0,0 +1,221 @@ +// Android (Java/JNI) platform adapter for Endive. +// Endive runs natively on the JVM through Kotlin + JNI, following the same +// pattern as ns-wasm3 and ns-wamr. + +import { EndiveError, type WasmValueType, type WireValue } from './wire.js'; +import type { + WireHostCallback, + NativeFunctionAdapter, + NativeModuleAdapter, + NativeRuntimeAdapter, +} from '@cross-code/ns-wasm-core'; + +// --------------------------------------------------------------------------- +// Android helpers +// --------------------------------------------------------------------------- + +function ns(): any { + return (globalThis as any).org?.nativescript?.endive; +} + +function arrayList(): any { + const g = globalThis as any; + return new (g.java.util.ArrayList)(); +} + +function javaArrayToJs(list: any): any[] { + const result: any[] = []; + const size = list.size(); + for (let i = 0; i < size; i++) result.push(list.get(i)); + return result; +} + +function toJavaBytes(source: Uint8Array): any { + try { + const n = ns(); + return n.NSCEndiveRuntime.jsByteArrayToJava(source.buffer, source.byteOffset, source.byteLength); + } catch { + // Older Android API: thread the bytes through an NSArray. + const bytes = arrayList(); + for (let i = 0; i < source.length; i++) bytes.add(source[i]); + return bytes; + } +} + +function fromJavaBytes(javaBytes: any): Uint8Array { + try { + const n = ns(); + const buf: ArrayBuffer = n.NSCEndiveRuntime.javaByteArrayToJs(javaBytes); + return new Uint8Array(buf); + } catch { + return Uint8Array.from(javaArrayToJs(javaBytes) as number[]); + } +} + +function normalizeAndroidValue(value: any): WireValue | null { + if (value === undefined || value === null) return null; + if (typeof value === 'number' || typeof value === 'string') return value; + const javaLang = (globalThis as any).java?.lang; + if (javaLang != null && value instanceof javaLang.Number) { + // i64 globals cross as java.lang.Long — stringify before unboxing + // so values above Number.MAX_SAFE_INTEGER stay exact. + if (value instanceof javaLang.Long) return String(value.toString()); + return value.doubleValue(); + } + return String(value); +} + +function toJavaWireValue(val: WireValue): any { + // Use the static factory so the NS bridge gets a java.lang.Double object + // not a primitive (NativeScript boxes primitives as Float, losing f64). + if (typeof val === 'number') return (globalThis as any).java.lang.Double.valueOf(val); + // i64 crosses the wire as a decimal string — leave it as-is. + return String(val); +} + +function rethrow(error: unknown, context: string): never { + if (error instanceof EndiveError) throw error; + const raw = error instanceof Error ? error.message : String(error); + const message = raw.replace(/NSCEndiveException:\s*/, ''); + throw new EndiveError(`${context}: ${message}`); +} + +// --------------------------------------------------------------------------- +// Android adapter classes +// --------------------------------------------------------------------------- + +class AndroidFunction implements NativeFunctionAdapter { + constructor(private readonly fn: any) {} + + name(): string { return String(this.fn.name()); } + paramTypes(): WasmValueType[] { + return javaArrayToJs(this.fn.paramTypes()).map(String) as WasmValueType[]; + } + returnTypes(): WasmValueType[] { + return javaArrayToJs(this.fn.returnTypes()).map(String) as WasmValueType[]; + } + + call(args: WireValue[]): WireValue[] { + const context = `call ${this.name()}`; + try { + const list = arrayList(); + for (const arg of args) list.add(toJavaWireValue(arg)); + const result = this.fn.call(list); + if (result == null) throw new EndiveError(`${context}: returned null`); + return (javaArrayToJs(result) as any[]).map((v: any) => { + const n = normalizeAndroidValue(v); + if (n === null) throw new EndiveError(`${context}: unexpected null result slot`); + return n; + }); + } catch (error) { + rethrow(error, context); + } + } +} + +function makeAndroidHostCallback(cb: WireHostCallback): any { + const ns = (globalThis as any).org?.nativescript?.endive; + if (!ns || !ns.NSCEndiveHostCallback) { + throw new EndiveError('NSCEndiveHostCallback not available'); + } + return new ns.NSCEndiveHostCallback(cb); +} + +class AndroidModule implements NativeModuleAdapter { + constructor(private readonly module: any) {} + + name(): string { return String(this.module.name()); } + + linkHostFunction(mod: string, name: string, signature: string, cb: WireHostCallback): void { + try { + this.module.linkHostFunction(mod, name, signature, makeAndroidHostCallback(cb)); + } catch (error) { + rethrow(error, `linkHostFunction ${mod}.${name}`); + } + } + + getGlobal(name: string): WireValue { + try { + const value = normalizeAndroidValue(this.module.getGlobal(name)); + if (value === null) throw new EndiveError(`getGlobal ${name}: returned null`); + return value; + } catch (error) { + rethrow(error, `getGlobal ${name}`); + } + } + + setGlobal(name: string, value: WireValue): void { + try { + this.module.setGlobal(name, toJavaWireValue(value)); + } catch (error) { + rethrow(error, `setGlobal ${name}`); + } + } +} + +export class AndroidRuntime implements NativeRuntimeAdapter { + private readonly runtime: any; + + constructor(stackSizeInBytes: number) { + const n = ns(); + if (!n || !n.NSCEndiveRuntime) { + throw new EndiveError( + 'ns-endive native runtime not found on Android — is the plugin installed?', + ); + } + this.runtime = new n.NSCEndiveRuntime(stackSizeInBytes); + } + + loadModuleFromBytes(bytes: Uint8Array): NativeModuleAdapter { + try { + const module = this.runtime.loadModuleFromBytes(toJavaBytes(bytes)); + return new AndroidModule(module); + } catch (error) { + rethrow(error, 'loadModule'); + throw null as never; + } + } + + loadModuleFromFile(path: string): NativeModuleAdapter { + try { + const module = this.runtime.loadModuleFromFile(path); + return new AndroidModule(module); + } catch (error) { + rethrow(error, 'loadModule'); + throw null as never; + } + } + + findFunction(name: string): NativeFunctionAdapter { + try { + const fn = this.runtime.findFunction(name); + return new AndroidFunction(fn); + } catch (error) { + rethrow(error, 'findFunction'); + throw null as never; + } + } + + memorySize(): number { return Number(this.runtime.memorySize()); } + + readMemory(offset: number, length: number): Uint8Array { + try { + return fromJavaBytes(this.runtime.readMemory(offset, length)); + } catch (error) { + rethrow(error, 'readMemory'); + throw null as never; + } + } + + writeMemory(offset: number, bytes: Uint8Array): void { + try { + this.runtime.writeMemory(offset, toJavaBytes(bytes)); + } catch (error) { + rethrow(error, 'writeMemory'); + } + } + + dispose(): void { + try { this.runtime.dispose(); } catch { /* best-effort */ } + } +} diff --git a/packages/ns-endive/src/lib/endive-ios.ts b/packages/ns-endive/src/lib/endive-ios.ts new file mode 100644 index 0000000..f1e9f42 --- /dev/null +++ b/packages/ns-endive/src/lib/endive-ios.ts @@ -0,0 +1,36 @@ +// iOS platform adapter for Endive. +// Endive is a Java-based WebAssembly runtime (JVM/JNI) and does not support +// iOS. The adapter throws a clear message instead of silently failing. + +import { EndiveError } from './wire.js'; +import type { + NativeRuntimeAdapter, + NativeModuleAdapter, + NativeFunctionAdapter, +} from '@cross-code/ns-wasm-core'; + +export class IosRuntime implements NativeRuntimeAdapter { + constructor(_stackSizeInBytes: number) { + throw new EndiveError( + 'Endive is a Java-based runtime and does not support iOS — use ns-wasm3, ns-wamr or ns-wasm-kit-runtime for iOS targets', + ); + } + + loadModuleFromBytes(_bytes: Uint8Array): NativeModuleAdapter { + throw new EndiveError('Endive: iOS is not supported'); + } + loadModuleFromFile(_path: string): NativeModuleAdapter { + throw new EndiveError('Endive: iOS is not supported'); + } + findFunction(_name: string): NativeFunctionAdapter { + throw new EndiveError('Endive: iOS is not supported'); + } + memorySize(): number { throw new EndiveError('Endive: iOS not supported'); } + readMemory(_offset: number, _length: number): Uint8Array { + throw new EndiveError('Endive: iOS not supported'); + } + writeMemory(_offset: number, _bytes: Uint8Array): void { + throw new EndiveError('Endive: iOS not supported'); + } + dispose(): void {} +} diff --git a/packages/ns-endive/src/lib/endive.spec.ts b/packages/ns-endive/src/lib/endive.spec.ts new file mode 100644 index 0000000..b6f3070 --- /dev/null +++ b/packages/ns-endive/src/lib/endive.spec.ts @@ -0,0 +1,123 @@ +import { afterEach, describe, expect, it } from 'vitest'; + +import { EndiveError } from './wire.js'; +import { EndiveRuntime } from './endive.js'; + +// These specs exercise the Android platform adapter against fakes that mimic +// the JS-visible shape of the native Kotlin classes. The real Endive +// implementation is covered by the JVM host tests. + +const g = globalThis as any; + +afterEach(() => { + delete g.org; + delete g.java; +}); + +// ------------------------------------------------------------------ fakes + +function installAndroidFake() { + const state: any = { + memory: new Uint8Array(64 * 1024), + version: '0.1.0', + }; + + g.java = { + util: { + ArrayList: class { + private items: any[] = []; + constructor(...init: any[]) { this.items = init; } + add(v: any) { this.items.push(v); } + get(i: number) { return this.items[i]; } + size() { return this.items.length; } + }, + }, + lang: { + Double: class { + private v: number; + constructor(v: number) { this.v = v; } + doubleValue() { return this.v; } + floatValue() { return this.v; } + }, + }, + }; + + const ns = (g.org = { nativescript: { endive: {} } } as any).nativescript.endive; + const module = { name: () => 'fake.wasm' }; + + ns.NSCEndiveRuntime = class { + private _memory: Uint8Array; + constructor(stackSize: number) { + state.stackSize = stackSize; + this._memory = state.memory; + } + static endiveVersion() { return state.version; } + static jsByteArrayToJava(buf: ArrayBuffer, off: number, len: number) { + return new (g.java.util.ArrayList)(...Array.from(new Uint8Array(buf, off, len))); + } + static javaByteArrayToJs(bytes: any) { + const arr = new Uint8Array(bytes.size()); + for (let i = 0; i < arr.length; i++) arr[i] = bytes.get(i); + return arr.buffer; + } + loadModuleFromBytes(_bytes: any) { return module; } + loadModuleFromFile(_path: string) { return module; } + findFunction(name: string) { return { name() { return name; } }; } + memorySize() { return this._memory.length; } + readMemory(offset: number, length: number) { + return new (g.java.util.ArrayList)(...Array.from(this._memory.slice(offset, offset + length))); + } + writeMemory(offset: number, bytes: any) { + const arr = new Uint8Array(bytes.size()); + for (let i = 0; i < arr.length; i++) arr[i] = bytes.get(i); + this._memory.set(arr, offset); + } + dispose() {} + }; + + (ns.NSCEndiveHostCallback as any) = class { + constructor(private cb: (args: any[]) => any[]) {} + invoke(args: any[]) { return this.cb(args); } + }; +} + +// ------------------------------------------------------------------ tests + +describe('EndiveRuntime (Android fake)', () => { + beforeEach(() => { installAndroidFake(); }); + + it('reports the Endive version', () => { + const runtime = new EndiveRuntime(); + expect(EndiveRuntime.version()).toBe('0.1.0'); + runtime.dispose(); + }); + + it('loads a module from bytes', () => { + const runtime = new EndiveRuntime(); + const wasm = new Uint8Array([0, 0x61, 0x73, 0x6d, 1, 0, 0, 0]); + const module = runtime.loadModule(wasm); + expect(module).toBeDefined(); + expect(module.name).toBeDefined(); + runtime.dispose(); + }); + + it('finds a function by name', () => { + const runtime = new EndiveRuntime(); + const fn = runtime.findFunction('test'); + expect(fn.name).toBe('test'); + runtime.dispose(); + }); + + it('reads and writes memory', () => { + const runtime = new EndiveRuntime(); + runtime.writeMemory(0, new Uint8Array([1, 2, 3])); + expect(runtime.readMemory(0, 3)).toEqual(new Uint8Array([1, 2, 3])); + expect(runtime.memorySize).toBe(64 * 1024); + runtime.dispose(); + }); + + it('throws when native runtime is not found', () => { + delete g.org; + expect(() => new EndiveRuntime()).toThrow(EndiveError); + }); +}); diff --git a/packages/ns-endive/src/lib/endive.ts b/packages/ns-endive/src/lib/endive.ts new file mode 100644 index 0000000..15379e3 --- /dev/null +++ b/packages/ns-endive/src/lib/endive.ts @@ -0,0 +1,108 @@ +// ns-endive — Endive (https://github.com/bytecodealliance/endive) +// Java-based WebAssembly runtime plugin for NativeScript. +// +// Endive runs on the JVM, so the Android adapter is the real one and iOS +// throws a clear unsupported error. The Runtime / Module / Function classes +// extend the generic base classes from @cross-code/ns-wasm-core. + +import type { WasmValue } from '@cross-code/ns-wasm-core'; +import { + WasmRuntime, + WasmModule, + WasmFunction, + type NativeRuntimeAdapter, +} from '@cross-code/ns-wasm-core'; +import { EndiveError } from './wire.js'; +import { IosRuntime } from './endive-ios.js'; +import { AndroidRuntime } from './endive-android.js'; + +// --------------------------------------------------------------------------- +// Re-exports (adapter interfaces) +// --------------------------------------------------------------------------- + +export { + type WireHostCallback, + type NativeFunctionAdapter, + type NativeModuleAdapter, + type NativeRuntimeAdapter, +} from '@cross-code/ns-wasm-core'; + +// --------------------------------------------------------------------------- +// Engine-specific types +// --------------------------------------------------------------------------- + +export type EndiveHostFunction = ( + ...args: WasmValue[] +) => WasmValue | WasmValue[] | void; + +export type EndiveModuleSource = string | ArrayBuffer | Uint8Array | number[]; + +export interface EndiveImports { + [module: string]: { + [name: string]: { signature: string; fn: EndiveHostFunction }; + }; +} + +export interface EndiveRuntimeOptions { + /** Endive stack size, in bytes. Default 64 KiB. */ + stackSizeInBytes?: number; +} + +// --------------------------------------------------------------------------- +// Factory +// --------------------------------------------------------------------------- + +function createAdapter(stackSizeInBytes: number): NativeRuntimeAdapter { + const g = globalThis as any; + // Android: Endive runs on the JVM. + if (g.org?.nativescript?.endive?.NSCEndiveRuntime) { + return new AndroidRuntime(stackSizeInBytes); + } + // iOS: Endive is Java-based; falls through to unsupported. + if (typeof g.NSCEndiveRuntime !== 'undefined' && g.NSCEndiveRuntime !== null) { + return new IosRuntime(stackSizeInBytes); + } + throw new EndiveError( + 'ns-endive native runtime not found — is the plugin installed and the app rebuilt?', + ); +} + +function endiveVersionNative(): string { + const g = globalThis as any; + if (g.org?.nativescript?.endive?.NSCEndiveRuntime) { + return String(g.org.nativescript.endive.NSCEndiveRuntime.endiveVersion()); + } + if (typeof g.NSCEndiveRuntime !== 'undefined' && g.NSCEndiveRuntime !== null) { + return String(g.NSCEndiveRuntime.endiveVersion()); + } + throw new EndiveError('ns-endive native runtime not found'); +} + +// --------------------------------------------------------------------------- +// Public classes +// --------------------------------------------------------------------------- + +export class EndiveFunction extends WasmFunction {} +export class EndiveModule extends WasmModule {} + +export class EndiveRuntime extends WasmRuntime { + constructor(options?: EndiveRuntimeOptions) { + super(createAdapter(options?.stackSizeInBytes ?? 64 * 1024), { + moduleCtor: EndiveModule, + functionCtor: EndiveFunction, + }); + } + + /** The Endive interpreter version. */ + static version(): string { + return endiveVersionNative(); + } + + override loadModule(source: EndiveModuleSource, imports?: EndiveImports): EndiveModule { + return super.loadModule(source, imports) as unknown as EndiveModule; + } + + override findFunction(name: string): EndiveFunction { + return super.findFunction(name) as unknown as EndiveFunction; + } +} diff --git a/packages/ns-endive/src/lib/native-api.d.ts b/packages/ns-endive/src/lib/native-api.d.ts new file mode 100644 index 0000000..29000d6 --- /dev/null +++ b/packages/ns-endive/src/lib/native-api.d.ts @@ -0,0 +1,38 @@ +// Native type declarations for the Endive Kotlin classes exposed on globalThis +// by the NativeScript Android runtime (org.nativescript.endive.*). +// +// Generate with: cd apps/ns-wasm-test && ns typings android + +declare namespace org.nativescript.endive { + class NSCEndiveRuntime { + static endiveVersion(): string; + constructor(stackSizeInBytes: number); + loadModuleFromBytes(bytes: java.util.ArrayList): NSCEndiveModule; + loadModuleFromFile(path: string): NSCEndiveModule; + findFunction(name: string): NSCEndiveFunction; + memorySize(): number; + readMemory(offset: number, length: number): java.util.ArrayList; + writeMemory(offset: number, bytes: java.util.ArrayList): void; + dispose(): void; + static jsByteArrayToJava(buffer: ArrayBuffer, offset: number, length: number): java.util.ArrayList; + static javaByteArrayToJs(bytes: java.util.ArrayList): ArrayBuffer; + } + + class NSCEndiveModule { + name(): string; + linkHostFunction(mod: string, name: string, signature: string, callback: NSCEndiveHostCallback): void; + getGlobal(name: string): any; + setGlobal(name: string, value: any): void; + } + + class NSCEndiveFunction { + name(): string; + paramTypes(): string[]; + returnTypes(): string[]; + call(args: java.util.ArrayList): java.util.ArrayList; + } + + class NSCEndiveHostCallback { + constructor(callback: (args: any[]) => any[]); + } +} diff --git a/packages/ns-endive/src/lib/wire.ts b/packages/ns-endive/src/lib/wire.ts new file mode 100644 index 0000000..47b558d --- /dev/null +++ b/packages/ns-endive/src/lib/wire.ts @@ -0,0 +1,18 @@ +// Re-exports from the shared wire protocol in @cross-code/ns-wasm-core, +// plus an engine-specific error class alias for the Endive runtime. + +export { WasmError as EndiveError } from '@cross-code/ns-wasm-core'; +export { WasmError } from '@cross-code/ns-wasm-core'; +export { + hostResultToWire, + fromWireAll, + parseSignature, + toWire, + fromWire, + unwrapResults, + type WasmArg, + type WasmValue, + type WasmValueType, + type WireValue, + type ParsedSignature, +} from '@cross-code/ns-wasm-core'; diff --git a/packages/ns-endive/tsconfig.json b/packages/ns-endive/tsconfig.json new file mode 100644 index 0000000..c823650 --- /dev/null +++ b/packages/ns-endive/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.lib.json" }, + { "path": "./tsconfig.spec.json" } + ] +} diff --git a/packages/ns-endive/tsconfig.lib.json b/packages/ns-endive/tsconfig.lib.json new file mode 100644 index 0000000..940c1b5 --- /dev/null +++ b/packages/ns-endive/tsconfig.lib.json @@ -0,0 +1,31 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "emitDeclarationOnly": false, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "references": [ + { + "path": "../ns-wasm-core/tsconfig.lib.json" + } + ], + "exclude": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx" + ] +} diff --git a/packages/ns-endive/tsconfig.spec.json b/packages/ns-endive/tsconfig.spec.json new file mode 100644 index 0000000..943d0f2 --- /dev/null +++ b/packages/ns-endive/tsconfig.spec.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.spec.tsbuildinfo", + "emitDeclarationOnly": false, + "types": ["vitest/globals", "node"] + }, + "include": [ + "vite.config.ts","vite.config.mts","vitest.config.ts","vitest.config.mts", + "src/**/*.test.ts","src/**/*.spec.ts","src/**/*.test.tsx","src/**/*.spec.tsx", + "src/**/*.test.js","src/**/*.spec.js","src/**/*.test.jsx","src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "references": [{ "path": "./tsconfig.lib.json" }] +} diff --git a/packages/ns-endive/vitest.config.mts b/packages/ns-endive/vitest.config.mts new file mode 100644 index 0000000..2fb9d7d --- /dev/null +++ b/packages/ns-endive/vitest.config.mts @@ -0,0 +1,20 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/ns-endive', + test: { + name: 'ns-endive', + watch: false, + globals: true, + environment: 'node', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + provider: 'v8' as const, + reportsDirectory: './test-output/vitest/coverage', + include: ['src/**/*.ts'], + exclude: ['src/**/*.spec.ts', 'src/**/*.test.ts', 'src/**/*.d.ts'], + }, + }, +})); diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-arm64-simulator/NSCWamr.framework.dSYM/Contents/Resources/DWARF/NSCWamr b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-arm64-simulator/NSCWamr.framework.dSYM/Contents/Resources/DWARF/NSCWamr deleted file mode 100644 index 59d7d51..0000000 Binary files a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-arm64-simulator/NSCWamr.framework.dSYM/Contents/Resources/DWARF/NSCWamr and /dev/null differ diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-arm64-simulator/NSCWamr.framework.dSYM/Contents/Info.plist b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWamr.framework.dSYM/Contents/Info.plist similarity index 100% rename from packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-arm64-simulator/NSCWamr.framework.dSYM/Contents/Info.plist rename to packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWamr.framework.dSYM/Contents/Info.plist diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWamr.framework.dSYM/Contents/Resources/DWARF/NSCWamr b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWamr.framework.dSYM/Contents/Resources/DWARF/NSCWamr new file mode 100644 index 0000000..cf9e532 Binary files /dev/null and b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWamr.framework.dSYM/Contents/Resources/DWARF/NSCWamr differ diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-arm64-simulator/NSCWamr.framework.dSYM/Contents/Resources/Relocations/aarch64/NSCWamr.yml b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWamr.framework.dSYM/Contents/Resources/Relocations/aarch64/NSCWamr.yml similarity index 100% rename from packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-arm64-simulator/NSCWamr.framework.dSYM/Contents/Resources/Relocations/aarch64/NSCWamr.yml rename to packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWamr.framework.dSYM/Contents/Resources/Relocations/aarch64/NSCWamr.yml diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-x86_64-simulator/NSCWamr.framework.dSYM/Contents/Resources/Relocations/x86_64/NSCWamr.yml b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWamr.framework.dSYM/Contents/Resources/Relocations/x86_64/NSCWamr.yml similarity index 100% rename from packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-x86_64-simulator/NSCWamr.framework.dSYM/Contents/Resources/Relocations/x86_64/NSCWamr.yml rename to packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWamr.framework.dSYM/Contents/Resources/Relocations/x86_64/NSCWamr.yml diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-x86_64-simulator/NSCWamr.framework.dSYM/Contents/Info.plist b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-x86_64-simulator/NSCWamr.framework.dSYM/Contents/Info.plist deleted file mode 100644 index 979bdeb..0000000 --- a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-x86_64-simulator/NSCWamr.framework.dSYM/Contents/Info.plist +++ /dev/null @@ -1,20 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleIdentifier - com.apple.xcode.dsym.org.nativescript.NSCWamr - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - dSYM - CFBundleSignature - ???? - CFBundleShortVersionString - 0.1.0 - CFBundleVersion - 1 - - diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-x86_64-simulator/NSCWamr.framework.dSYM/Contents/Resources/DWARF/NSCWamr b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-x86_64-simulator/NSCWamr.framework.dSYM/Contents/Resources/DWARF/NSCWamr deleted file mode 100644 index f9aedf8..0000000 Binary files a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework.dSYMs/ios-x86_64-simulator/NSCWamr.framework.dSYM/Contents/Resources/DWARF/NSCWamr and /dev/null differ diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/Info.plist b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/Info.plist index f64ca6b..6006af0 100644 --- a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/Info.plist +++ b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/Info.plist @@ -16,23 +16,11 @@ LibraryIdentifier - ios-arm64-simulator + ios-arm64_x86_64-simulator LibraryPath NSCWamr.framework SupportedArchitectures - arm64 - SupportedPlatform - ios - SupportedPlatformVariant - simulator - - - LibraryIdentifier - ios-x86_64-simulator - LibraryPath - NSCWamr.framework - SupportedArchitectures - x86_64 + arm64x86_64 SupportedPlatform ios SupportedPlatformVariant diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64-simulator/NSCWamr.framework/NSCWamr b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64-simulator/NSCWamr.framework/NSCWamr deleted file mode 100755 index d134b19..0000000 Binary files a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64-simulator/NSCWamr.framework/NSCWamr and /dev/null differ diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64-simulator/NSCWamr.framework/Headers/NSCWamr.h b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64_x86_64-simulator/NSCWamr.framework/Headers/NSCWamr.h similarity index 100% rename from packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64-simulator/NSCWamr.framework/Headers/NSCWamr.h rename to packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64_x86_64-simulator/NSCWamr.framework/Headers/NSCWamr.h diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64-simulator/NSCWamr.framework/Headers/NSCWamrClasses.h b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64_x86_64-simulator/NSCWamr.framework/Headers/NSCWamrClasses.h similarity index 100% rename from packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64-simulator/NSCWamr.framework/Headers/NSCWamrClasses.h rename to packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64_x86_64-simulator/NSCWamr.framework/Headers/NSCWamrClasses.h diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64-simulator/NSCWamr.framework/Info.plist b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64_x86_64-simulator/NSCWamr.framework/Info.plist similarity index 100% rename from packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64-simulator/NSCWamr.framework/Info.plist rename to packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64_x86_64-simulator/NSCWamr.framework/Info.plist diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64-simulator/NSCWamr.framework/Modules/module.modulemap b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64_x86_64-simulator/NSCWamr.framework/Modules/module.modulemap similarity index 100% rename from packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64-simulator/NSCWamr.framework/Modules/module.modulemap rename to packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64_x86_64-simulator/NSCWamr.framework/Modules/module.modulemap diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64_x86_64-simulator/NSCWamr.framework/NSCWamr b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64_x86_64-simulator/NSCWamr.framework/NSCWamr new file mode 100755 index 0000000..a41a15b Binary files /dev/null and b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-arm64_x86_64-simulator/NSCWamr.framework/NSCWamr differ diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-x86_64-simulator/NSCWamr.framework/Headers/NSCWamr.h b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-x86_64-simulator/NSCWamr.framework/Headers/NSCWamr.h deleted file mode 100644 index a441f09..0000000 --- a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-x86_64-simulator/NSCWamr.framework/Headers/NSCWamr.h +++ /dev/null @@ -1,10 +0,0 @@ -// NSCWamr.h -// -// Umbrella header for the NSCWamr framework. The implementation is Swift -// (@objc classes in NSCWamr.swift); these headers mirror the @objc surface so -// the NativeScript iOS metadata generator can see it. -// -// NOTE: the class declarations live in NSCWamrClasses.h, NOT here — the -// metadata generator only records classes declared in subheaders imported by -// the umbrella header; classes declared inline in the umbrella are skipped. -#import "NSCWamrClasses.h" diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-x86_64-simulator/NSCWamr.framework/Headers/NSCWamrClasses.h b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-x86_64-simulator/NSCWamr.framework/Headers/NSCWamrClasses.h deleted file mode 100644 index 30558e3..0000000 --- a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-x86_64-simulator/NSCWamr.framework/Headers/NSCWamrClasses.h +++ /dev/null @@ -1,64 +0,0 @@ -// NSCWamr.h -// -// Public header for the NSCWamr framework. The implementation is Swift -// (@objc classes in NSCWamr.swift); this header exists so the NativeScript -// iOS metadata generator (a clang-based parser) can see the class surface — -// the exact class names and selectors below mirror the Swift @objc -// declarations, including the trailing `error:` out-parameter convention -// (JS: `loadModuleError(bytes)` etc.). -#import - -@class NSCWamrModule; -@class NSCWamrFunction; - -NS_ASSUME_NONNULL_BEGIN - -/// Host import callback: subclasses override `invoke(_:)`. -@interface NSCWamrHostCallback : NSObject -- (NSArray * _Nullable)invoke:(NSArray *)args; -@end - -/// A WAMR runtime: owns module instances, exec environments and the -/// registered host callbacks. -@interface NSCWamrRuntime : NSObject -- (instancetype)initWithStackSize:(NSUInteger)stackSizeInBytes - wasiEnabled:(BOOL)wasiEnabled - executionTier:(NSString *)executionTier; -- (instancetype)init; -+ (NSString *)wamrVersion; -- (NSCWamrModule * _Nullable)loadModule:(NSData *)bytes error:(NSError **)error; -- (NSCWamrModule * _Nullable)loadModuleFromFile:(NSString *)path error:(NSError **)error; -- (NSCWamrFunction * _Nullable)findFunction:(NSString *)name error:(NSError **)error; -@property (nonatomic, readonly) NSUInteger memorySize; -- (NSData * _Nullable)readMemoryAtOffset:(NSUInteger)offset - length:(NSUInteger)length - error:(NSError **)error; -- (BOOL)writeMemoryAtOffset:(NSUInteger)offset - data:(NSData *)data - error:(NSError **)error; -@end - -/// A loaded (and lazily instantiated) WebAssembly module. -@interface NSCWamrModule : NSObject -@property (nonatomic, weak, nullable) NSCWamrRuntime *runtime; -@property (nonatomic, readonly) NSString *name; -- (NSCWamrFunction * _Nullable)findFunction:(NSString *)name error:(NSError **)error; -- (BOOL)linkHostFunction:(NSString *)moduleName - name:(NSString *)name - signature:(NSString *)signature - callback:(NSCWamrHostCallback *)callback - error:(NSError **)error; -- (id _Nullable)getGlobal:(NSString *)name error:(NSError **)error; -- (BOOL)setGlobal:(NSString *)name value:(id)value error:(NSError **)error; -@end - -/// A callable function of a loaded module. -@interface NSCWamrFunction : NSObject -@property (nonatomic, readonly) NSCWamrRuntime *runtime; -@property (nonatomic, readonly) NSString *name; -@property (nonatomic, readonly) NSArray *paramTypes; -@property (nonatomic, readonly) NSArray *returnTypes; -- (NSArray * _Nullable)callWithArguments:(NSArray *)arguments error:(NSError **)error; -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-x86_64-simulator/NSCWamr.framework/Info.plist b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-x86_64-simulator/NSCWamr.framework/Info.plist deleted file mode 100644 index 26d5861..0000000 --- a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-x86_64-simulator/NSCWamr.framework/Info.plist +++ /dev/null @@ -1,18 +0,0 @@ - - - - - CFBundleExecutable - NSCWamr - CFBundleIdentifier - org.nativescript.NSCWamr - CFBundleName - NSCWamr - CFBundlePackageType - FMWK - CFBundleShortVersionString - 0.1.0 - CFBundleVersion - 1 - - diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-x86_64-simulator/NSCWamr.framework/Modules/module.modulemap b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-x86_64-simulator/NSCWamr.framework/Modules/module.modulemap deleted file mode 100644 index 6dff50a..0000000 --- a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-x86_64-simulator/NSCWamr.framework/Modules/module.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module NSCWamr { - umbrella header "NSCWamr.h" - - export * - module * { export * } -} diff --git a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-x86_64-simulator/NSCWamr.framework/NSCWamr b/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-x86_64-simulator/NSCWamr.framework/NSCWamr deleted file mode 100755 index 6ec602a..0000000 Binary files a/packages/ns-wamr/platforms/ios/NSCWamr.xcframework/ios-x86_64-simulator/NSCWamr.framework/NSCWamr and /dev/null differ diff --git a/packages/ns-wamr/src/lib/wamr.spec.ts b/packages/ns-wamr/src/lib/wamr.spec.ts index 7394274..0983f4a 100644 --- a/packages/ns-wamr/src/lib/wamr.spec.ts +++ b/packages/ns-wamr/src/lib/wamr.spec.ts @@ -367,9 +367,9 @@ describe('WamrRuntime on Android', () => { const module = runtime.loadModule([1]); const received: unknown[] = []; - module.linkHostFunction('env', 'host_add', 'i(ii)', (a, b) => { - received.push(a, b); - return (a as number) + (b as number); + module.linkHostFunction('env', 'host_add', 'i(ii)', (args) => { + received.push(...args); + return [(args[0] as number) + (args[1] as number)]; }); expect(runtime.call('call_host_add', 3, 4)).toBe(7); expect(received).toEqual([3, 4]); @@ -434,9 +434,9 @@ describe('WamrRuntime on Android', () => { const module = runtime.loadModule([1]); const received: unknown[] = []; - module.linkHostFunction('env', 'host_add', 'i(ii)', (a, b) => { - received.push(a, b); - return (a as number) + (b as number); + module.linkHostFunction('env', 'host_add', 'i(ii)', (args) => { + received.push(...args); + return [(args[0] as number) + (args[1] as number)]; }); expect(runtime.call('call_host_add', 3, 4)).toBe(7); @@ -490,7 +490,9 @@ describe('WamrRuntime on iOS', () => { const runtime = new WamrRuntime(); const module = runtime.loadModule(new Uint8Array([0, 97, 115, 109])); - module.linkHostFunction('env', 'host_add', 'i(ii)', (a, b) => (a as number) + (b as number)); + module.linkHostFunction('env', 'host_add', 'i(ii)', (args) => [ + (args[0] as number) + (args[1] as number), + ]); expect(runtime.call('call_host_add', 20, 22)).toBe(42); }); diff --git a/packages/ns-wamr/tsconfig.lib.json b/packages/ns-wamr/tsconfig.lib.json index eff080e..940c1b5 100644 --- a/packages/ns-wamr/tsconfig.lib.json +++ b/packages/ns-wamr/tsconfig.lib.json @@ -1,27 +1,31 @@ { - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", - "emitDeclarationOnly": false, - "forceConsistentCasingInFileNames": true, - "types": ["node"] - }, - "include": ["src/**/*.ts"], - "references": [], - "exclude": [ - "vite.config.ts", - "vite.config.mts", - "vitest.config.ts", - "vitest.config.mts", - "src/**/*.test.ts", - "src/**/*.spec.ts", - "src/**/*.test.tsx", - "src/**/*.spec.tsx", - "src/**/*.test.js", - "src/**/*.spec.js", - "src/**/*.test.jsx", - "src/**/*.spec.jsx" - ] + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "emitDeclarationOnly": false, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "references": [ + { + "path": "../ns-wasm-core/tsconfig.lib.json" + } + ], + "exclude": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx" + ] } diff --git a/packages/ns-wasm-chicory/.gitignore b/packages/ns-wasm-chicory/.gitignore new file mode 100644 index 0000000..bbde7c3 --- /dev/null +++ b/packages/ns-wasm-chicory/.gitignore @@ -0,0 +1,5 @@ +/platforms/android/chicory-android/.gradle/ +/platforms/android/chicory-android/.kotlin/ +/platforms/android/chicory-android/build/ +/platforms/android/chicory-android/gradle/ +/platforms/android/chicory-android/library/build/ diff --git a/packages/ns-wasm-chicory/BUCK b/packages/ns-wasm-chicory/BUCK new file mode 100644 index 0000000..392f2f5 --- /dev/null +++ b/packages/ns-wasm-chicory/BUCK @@ -0,0 +1,39 @@ +# Buck2 build definitions for @cross-code/ns-wasm-chicory + +genrule( + name = "chicory-android", + srcs = glob([ + "platforms/android/chicory-android/library/src/**/*.kt", + "platforms/android/chicory-android/library/build.gradle.kts", + "platforms/android/chicory-android/build.gradle.kts", + "platforms/android/chicory-android/settings.gradle.kts", + ]), + out = "nativescript-chicory.aar", + cmd = "cd $SRCDIR/platforms/android/chicory-android && ./gradlew :library:deployAar && cp $SRCDIR/platforms/android/nativescript-chicory.aar $OUT", + visibility = ["PUBLIC"], +) + +genrule( + name = "chicory-test", + srcs = glob([ + "platforms/android/chicory-android/library/src/**/*.kt", + "platforms/android/chicory-android/library/build.gradle.kts", + "platforms/android/chicory-android/build.gradle.kts", + "platforms/android/chicory-android/settings.gradle.kts", + ]), + out = "test-results.stamp", + cmd = "cd $SRCDIR/platforms/android/chicory-android && ./gradlew :library:test && touch $OUT", + visibility = ["PUBLIC"], +) + +genrule( + name = "all", + srcs = [], + out = "all.stamp", + cmd = "echo 'ns-wasm-chicory: all targets' && touch $OUT", + deps = [ + ":chicory-android", + ":chicory-test", + ], + visibility = ["PUBLIC"], +) diff --git a/packages/ns-wasm-chicory/package.json b/packages/ns-wasm-chicory/package.json new file mode 100644 index 0000000..bb9b85a --- /dev/null +++ b/packages/ns-wasm-chicory/package.json @@ -0,0 +1,74 @@ +{ + "name": "@cross-code/ns-wasm-chicory", + "version": "0.1.0", + "description": "NativeScript plugin that runs WebAssembly modules with the Chicory interpreter (https://github.com/dylibso/chicory) — a pure-Java runtime, Android-only.", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "@cross-code/source": "./src/index.ts", + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "files": [ + "dist", + "platforms/android/include.gradle", + "platforms/android/nativescript-chicory.aar", + "!**/*.tsbuildinfo" + ], + "nativescript": { + "platforms": { + "android": "8.6.0" + } + }, + "nx": { + "name": "ns-wasm-chicory", + "targets": { + "test.android": { + "executor": "nx:run-script", + "options": { + "script": "test.android" + } + }, + "build.android": { + "executor": "nx:run-script", + "options": { + "script": "build.android" + } + }, + "lint.android": { + "executor": "nx:run-script", + "options": { + "script": "lint.android" + } + }, + "coverage": { + "executor": "nx:run-script", + "options": { + "script": "coverage" + } + }, + "buck2-build": { + "executor": "@cross-code/nx-buck2:build", + "options": { + "configuration": "release" + } + } + } + }, + "scripts": { + "test.android": "cd platforms/android/chicory-android && ./gradlew :library:test", + "build.android": "cd platforms/android/chicory-android && ./gradlew :library:deployAar", + "lint.android": "cd platforms/android/chicory-android && ./gradlew detekt ktlintCheck", + "coverage": "vitest run --coverage" + }, + "dependencies": { + "@cross-code/ns-wasm-core": "workspace:*", + "tslib": "^2.3.0" + } +} diff --git a/packages/ns-wasm-chicory/platforms/android/chicory-android/build.gradle.kts b/packages/ns-wasm-chicory/platforms/android/chicory-android/build.gradle.kts new file mode 100644 index 0000000..ca8233b --- /dev/null +++ b/packages/ns-wasm-chicory/platforms/android/chicory-android/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + id("com.android.library") version "9.3.1" apply false + id("org.jetbrains.kotlin.jvm") version "2.4.10" apply false + + // Kotlin linting for the hand-written wrapper sources. + id("io.gitlab.arturbosch.detekt") version "1.23.8" apply false + id("org.jlleitschuh.gradle.ktlint") version "14.2.0" apply false +} + +// Monorepo root: shared detekt.yml / .editorconfig live there. +val repoRoot: File = rootDir.parentFile.parentFile.parentFile.parentFile.parentFile + +subprojects { + apply(plugin = "io.gitlab.arturbosch.detekt") + apply(plugin = "org.jlleitschuh.gradle.ktlint") + + configure { + config.setFrom(files("$repoRoot/detekt.yml")) + buildUponDefaultConfig = true + source.setFrom(fileTree(projectDir) { + include("**/src/**/*.kt") + exclude("**/build/**", "**/bin/**", "**/.kotlin/**") + }) + } + + configure { + version.set("1.8.0") + filter { + include("**/src/**/*.kt") + exclude("**/build/**", "**/bin/**", "**/.kotlin/**") + } + } + + tasks.matching { it.name.contains("KotlinScript") }.configureEach { + enabled = false + } +} diff --git a/packages/ns-wasm-chicory/platforms/android/chicory-android/gradlew b/packages/ns-wasm-chicory/platforms/android/chicory-android/gradlew new file mode 100755 index 0000000..23d15a9 --- /dev/null +++ b/packages/ns-wasm-chicory/platforms/android/chicory-android/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/packages/ns-wasm-chicory/platforms/android/chicory-android/gradlew.bat b/packages/ns-wasm-chicory/platforms/android/chicory-android/gradlew.bat new file mode 100644 index 0000000..5eed7ee --- /dev/null +++ b/packages/ns-wasm-chicory/platforms/android/chicory-android/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/packages/ns-wasm-chicory/platforms/android/chicory-android/library/build.gradle.kts b/packages/ns-wasm-chicory/platforms/android/chicory-android/library/build.gradle.kts new file mode 100644 index 0000000..020f248 --- /dev/null +++ b/packages/ns-wasm-chicory/platforms/android/chicory-android/library/build.gradle.kts @@ -0,0 +1,50 @@ +plugins { + id("com.android.library") +} + +android { + namespace = "org.nativescript.chicory" + compileSdk = 35 + + defaultConfig { + minSdk = 21 + consumerProguardFiles("consumer-rules.pro") + + aarMetadata { + minCompileSdk = 1 + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + sourceSets { + getByName("main") { + java.directories.addAll(listOf("src/main/kotlin")) + } + } +} + +kotlin { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) + freeCompilerArgs.add("-Xmetadata-version=2.3.0") + } +} + +dependencies { + implementation("com.dylibso.chicory:runtime:1.7.5") +} + +val pluginRoot: File = projectDir.parentFile.parentFile.parentFile.parentFile + +// Copies the release AAR to where the NativeScript CLI picks it up +// (plugin platforms/android/*.aar). +tasks.register("deployAar") { + dependsOn("assembleRelease") + from(layout.buildDirectory.file("outputs/aar/library-release.aar")) + into(rootProject.projectDir.parentFile) + rename { "nativescript-chicory.aar" } +} diff --git a/packages/ns-wasm-chicory/platforms/android/chicory-android/library/consumer-rules.pro b/packages/ns-wasm-chicory/platforms/android/chicory-android/library/consumer-rules.pro new file mode 100644 index 0000000..4c91bd9 --- /dev/null +++ b/packages/ns-wasm-chicory/platforms/android/chicory-android/library/consumer-rules.pro @@ -0,0 +1 @@ +# Chicory is consumed as a transitive dependency; no special rules needed. diff --git a/packages/ns-wasm-chicory/platforms/android/chicory-android/library/src/main/kotlin/org/nativescript/chicory/NSCChicory.kt b/packages/ns-wasm-chicory/platforms/android/chicory-android/library/src/main/kotlin/org/nativescript/chicory/NSCChicory.kt new file mode 100644 index 0000000..2ab4618 --- /dev/null +++ b/packages/ns-wasm-chicory/platforms/android/chicory-android/library/src/main/kotlin/org/nativescript/chicory/NSCChicory.kt @@ -0,0 +1,365 @@ +package org.nativescript.chicory + +import com.dylibso.chicory.runtime.ExportFunction +import com.dylibso.chicory.runtime.HostFunction +import com.dylibso.chicory.runtime.Instance +import com.dylibso.chicory.runtime.Memory +import com.dylibso.chicory.runtime.Store +import com.dylibso.chicory.wasm.Parser +import com.dylibso.chicory.wasm.types.FunctionType +import com.dylibso.chicory.wasm.types.MutabilityType +import com.dylibso.chicory.wasm.types.ValType +import com.dylibso.chicory.wasm.types.Value +import java.io.ByteArrayInputStream +import java.io.File + +class NSCChicoryException(message: String) : RuntimeException(message) + +/** Host import callback. */ +fun interface NSCChicoryHostFunction { + fun invoke(args: Array): Any? +} + +// --------------------------------------------------------------------------- +// Wire protocol helpers (mirrors the TypeScript wire.ts) +// --------------------------------------------------------------------------- + +private object Wire { + /** Decodes a raw Chicory `long` into the wire value for `typeName`. */ + fun decode(typeName: String, raw: Long): Any = when (typeName) { + "i32" -> raw.toInt() + "i64" -> raw.toString() + "f32" -> Float.fromBits(raw.toInt()).toDouble() + "f64" -> Double.fromBits(raw) + else -> raw + } + + /** Encodes a JS-provided value into the wire format for `typeName`. */ + fun encode(typeName: String, value: Any?): Long = when (typeName) { + "i32" -> asLong(value)!!.toInt().toLong() + "i64" -> asLong(value)!! + "f32" -> asDouble(value)!!.toFloat().toRawBits().toLong() + "f64" -> asDouble(value)!!.toRawBits() + else -> asLong(value) ?: 0L + } + + private fun asLong(value: Any?): Long? = when (value) { + is Number -> { + value.toLong() + } + + is String -> { + try { + value.toLong() + } catch (_: NumberFormatException) { + try { + java.math.BigInteger(value).toLong() + } catch (_: NumberFormatException) { + null + } + } + } + + else -> { + null + } + } + + private fun asDouble(value: Any?): Double? = when (value) { + is Number -> value.toDouble() + is String -> value.toDoubleOrNull() + else -> null + } +} + +// --------------------------------------------------------------------------- +// Host trampoline — Chicory calls back into Kotlin via this object +// --------------------------------------------------------------------------- + +/** + * One trampoline per linked import. The [NSCChicoryRuntime] keeps a strong + * reference so it is not garbage-collected while the runtime lives. + */ +class HostTrampoline( + val callback: NSCChicoryHostFunction, + private val paramTypeNames: Array, + private val returnTypeNames: Array, +) { + /** + * Called by Chicory when the WASM module invokes the import. + * Chicory passes parameters as `long...` following its raw calling convention. + */ + @Suppress("unused") // called from Chicory + fun apply(instance: Instance, args: LongArray): LongArray? { + val jsArgs = Array(paramTypeNames.size) { i -> + Wire.decode(paramTypeNames[i], args[i]) + } + + val result: Any? = try { + callback.invoke(jsArgs) + } catch (_: Throwable) { + return null + } + + val returned: List = when (result) { + null -> emptyList() + is Array<*> -> result.toList() + is List<*> -> result + else -> listOf(result) + } + + if (returned.size != returnTypeNames.size) return null + val out = LongArray(returned.size) + for (i in returned.indices) { + out[i] = Wire.encode(returnTypeNames[i], returned[i]) + } + return out + } +} + +// --------------------------------------------------------------------------- +// Runtime +// --------------------------------------------------------------------------- + +class NSCChicoryRuntime( + @Suppress("UnusedPrivateProperty") stackSizeInBytes: Int, +) : AutoCloseable { + private val store = Store() + private val moduleBytes = mutableListOf() + internal val hostTrampolines = mutableListOf() + private val modules = mutableListOf() + private var closed = false + + companion object { + private const val CHICORY_VERSION = "chicory" + + @JvmStatic + fun chicoryVersion(): String = CHICORY_VERSION + } + + fun loadModuleFromBytes(bytes: ByteArray): NSCChicoryModule { + val copy = bytes.copyOf() + val wasmModule = Parser.parse(ByteArrayInputStream(copy)) + val module = NSCChicoryModule(wasmModule, this) + moduleBytes.add(copy) + modules.add(module) + return module + } + + fun loadModuleFromFile(path: String): NSCChicoryModule { + val bytes = File(path).readBytes() + return loadModuleFromBytes(bytes) + } + + fun findFunction(name: String): NSCChicoryFunction { + ensureInstantiated() + for (module in modules) { + try { + return module.findExportedFunction(name) + } catch (_: Exception) { + // not in this module, try the next + } + } + throw NSCChicoryException("function not found: $name") + } + + // ------------------------------------------------------------------ memory + + fun memorySize(): Int { + ensureInstantiated() + val inst = currentInstance() ?: return 0 + val mem = inst.memory() ?: return 0 + return mem.pages() * Memory.PAGE_SIZE + } + + fun readMemory(offset: Int, length: Int): ByteArray { + ensureInstantiated() + val inst = currentInstance() ?: throw NSCChicoryException("no memory") + val mem = inst.memory() ?: throw NSCChicoryException("no memory") + return mem.readBytes(offset, length) + } + + fun writeMemory(offset: Int, data: ByteArray) { + ensureInstantiated() + val inst = currentInstance() ?: throw NSCChicoryException("no memory") + val mem = inst.memory() ?: throw NSCChicoryException("no memory") + mem.write(offset, data) + } + + fun dispose() { + if (closed) return + closed = true + moduleBytes.clear() + hostTrampolines.clear() + } + + override fun close() = dispose() + + // ------------------------------------------------------------------ internal + + internal fun store(): Store = store + + internal fun ensureInstantiated() { + for (module in modules) module.ensureInstantiated() + } + + internal fun currentInstance(): Instance? { + for (module in modules) { + val inst = module.instance() + if (inst != null) return inst + } + return null + } +} + +// --------------------------------------------------------------------------- +// Module +// --------------------------------------------------------------------------- + +class NSCChicoryModule internal constructor( + private val wasmModule: com.dylibso.chicory.wasm.WasmModule, + private val runtime: NSCChicoryRuntime, +) { + private var instance: Instance? = null + private val linkedFunctions = mutableListOf() + + val name: String + get() { + val nameSection = wasmModule.nameSection() + return nameSection?.moduleName()?.orElse(null) ?: "module" + } + + fun linkHostFunction(moduleName: String, name: String, signature: String, callback: NSCChicoryHostFunction) { + val (paramTypeNames, returnTypeNames) = parseSignature(signature) + val trampoline = HostTrampoline(callback, paramTypeNames, returnTypeNames) + runtime.hostTrampolines.add(trampoline) + + val paramValTypes = paramTypeNames.map { it.toValType() } + val returnValTypes = returnTypeNames.map { it.toValType() } + + val hostFunc = HostFunction( + moduleName, + name, + FunctionType.of(paramValTypes, returnValTypes), + com.dylibso.chicory.runtime.WasmFunctionHandle { inst, args -> + trampoline.apply(inst, args) ?: longArrayOf() + }, + ) + linkedFunctions.add(hostFunc) + } + + fun ensureInstantiated() { + if (instance != null) return + val store = runtime.store() + for (hf in linkedFunctions) { + store.addFunction(hf) + } + instance = store.instantiate(name, wasmModule) + } + + fun instance(): Instance? = instance + + fun findExportedFunction(name: String): NSCChicoryFunction { + ensureInstantiated() + val inst = instance!! + val exportFunc = inst.export(name) + val exportType = inst.exportType(name) + val params = exportType.params().map { it.toTypeString() } + val returns = exportType.returns().map { it.toTypeString() } + return NSCChicoryFunction(name, exportFunc, params, returns) + } + + // ------------------------------------------------------------------ globals + + fun getGlobal(name: String): Any { + ensureInstantiated() + val inst = instance!! + val globalInst = inst.exports().global(name) + val typeName = globalInst.type.toTypeString() + return Wire.decode(typeName, globalInst.value) + } + + fun setGlobal(name: String, value: Any?) { + ensureInstantiated() + val inst = instance!! + val globalInst = inst.exports().global(name) + val typeName = globalInst.type.toTypeString() + globalInst.setValue(Wire.encode(typeName, value)) + } + + private fun parseSignature(signature: String): Pair, Array> { + val compact = signature.replace("\\s+".toRegex(), "") + val match = Regex("^([vifIF]*)\\(([vifIF]*)\\)$").find(compact) + ?: throw NSCChicoryException("invalid wasm signature: \"$signature\"") + fun toTypes(chars: String): Array = chars + .filter { it != 'v' } + .map { c -> + when (c) { + 'i' -> "i32" + 'I' -> "i64" + 'f' -> "f32" + 'F' -> "f64" + else -> throw NSCChicoryException("invalid signature character: $c") + } + }.toTypedArray() + return Pair(toTypes(match.groupValues[2]), toTypes(match.groupValues[1])) + } +} + +// --------------------------------------------------------------------------- +// Function +// --------------------------------------------------------------------------- + +class NSCChicoryFunction internal constructor( + val name: String, + private val exportFunction: ExportFunction, + val paramTypes: List, + val returnTypes: List, +) { + /** Calls the function. Accepts a Java ArrayList from NativeScript. */ + @Suppress("SpreadOperator") // Chicory's ExportFunction.apply() requires varargs + fun call(args: ArrayList): ArrayList { + val longArgs = LongArray(paramTypes.size) { i -> + Wire.encode(paramTypes[i], args[i]) + } + + val results = exportFunction.apply(*longArgs) + + val out = ArrayList(returnTypes.size) + for (i in returnTypes.indices) { + if (i < results.size) { + out.add(Wire.decode(returnTypes[i], results[i])) + } else { + out.add(0) + } + } + return out + } +} + +// --------------------------------------------------------------------------- +// ValType helpers +// --------------------------------------------------------------------------- + +private fun ValType.toTypeString(): String = when (this) { + ValType.I32 -> "i32" + ValType.I64 -> "i64" + ValType.F32 -> "f32" + ValType.F64 -> "f64" + else -> "i32" +} + +private fun String.toValType(): ValType = when (this) { + "i32" -> ValType.I32 + + "i64" -> ValType.I64 + + "f32" -> ValType.F32 + + "f64" -> ValType.F64 + + "v" -> ValType.BOT + + // void is represented as Bot in Chicory + else -> throw NSCChicoryException("unsupported wasm value type: $this") +} diff --git a/packages/ns-wasm-chicory/platforms/android/chicory-android/settings.gradle.kts b/packages/ns-wasm-chicory/platforms/android/chicory-android/settings.gradle.kts new file mode 100644 index 0000000..0fa1558 --- /dev/null +++ b/packages/ns-wasm-chicory/platforms/android/chicory-android/settings.gradle.kts @@ -0,0 +1,19 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "chicory-android" + +include(":library") diff --git a/packages/ns-wasm-chicory/platforms/android/include.gradle b/packages/ns-wasm-chicory/platforms/android/include.gradle new file mode 100644 index 0000000..9207894 --- /dev/null +++ b/packages/ns-wasm-chicory/platforms/android/include.gradle @@ -0,0 +1,4 @@ +dependencies { + // Chicory is a pure-Java Wasm runtime; the .aar bundles Chicory's + // transitive dependencies so no additional Maven dependencies are needed. +} diff --git a/packages/ns-wasm-chicory/platforms/android/nativescript-chicory.aar b/packages/ns-wasm-chicory/platforms/android/nativescript-chicory.aar new file mode 100644 index 0000000..f39e3ab Binary files /dev/null and b/packages/ns-wasm-chicory/platforms/android/nativescript-chicory.aar differ diff --git a/packages/ns-wasm-chicory/src/index.ts b/packages/ns-wasm-chicory/src/index.ts new file mode 100644 index 0000000..619dea1 --- /dev/null +++ b/packages/ns-wasm-chicory/src/index.ts @@ -0,0 +1,2 @@ +export { ChicoryError, type ParsedSignature, type WasmArg, type WasmValue, type WasmValueType } from './lib/wire.js'; +export { ChicoryFunction, ChicoryModule, ChicoryRuntime, type ChicoryHostFunction, type ChicoryImports, type ChicoryModuleSource, type ChicoryRuntimeOptions } from './lib/chicory.js'; diff --git a/packages/ns-wasm-chicory/src/lib/chicory-android.ts b/packages/ns-wasm-chicory/src/lib/chicory-android.ts new file mode 100644 index 0000000..4daecd8 --- /dev/null +++ b/packages/ns-wasm-chicory/src/lib/chicory-android.ts @@ -0,0 +1,209 @@ +// Android (Kotlin/JNI) platform adapter for Chicory. +import { ChicoryError, type WasmValueType, type WireValue } from './wire.js'; +import type { + WireHostCallback, + NativeFunctionAdapter, + NativeModuleAdapter, + NativeRuntimeAdapter, +} from '@cross-code/ns-wasm-core'; +import type { + JavaArrayList, + NativeChicoryRuntimeProxy, + NativeChicoryModuleProxy, + NativeChicoryFunctionProxy, + NativeScriptOrg, + NsChicoryNamespace, +} from './native-proxy.js'; + +function ns(): NsChicoryNamespace | undefined { + return (globalThis as unknown as NativeScriptOrg).org?.nativescript?.chicory; +} + +function arrayList(): JavaArrayList { + return new ( + (globalThis as unknown as { java: { util: { ArrayList: new () => JavaArrayList } } }).java.util + .ArrayList + )(); +} + +function javaArrayToJs(list: JavaArrayList): unknown[] { + const r: unknown[] = []; + for (let i = 0; i < list.size(); i++) r.push(list.get(i)); + return r; +} + +function toJavaBytes(source: Uint8Array): JavaArrayList { + try { + return ns()?.NSCChicoryRuntime.jsByteArrayToJava( + source.buffer as ArrayBuffer, + source.byteOffset, + source.byteLength, + )!; + } catch { + const bytes = arrayList(); + for (let i = 0; i < source.length; i++) bytes.add(source[i]); + return bytes; + } +} + +function fromJavaBytes(javaBytes: JavaArrayList): Uint8Array { + try { + return new Uint8Array(ns()!.NSCChicoryRuntime.javaByteArrayToJs(javaBytes)); + } catch { + return Uint8Array.from(javaArrayToJs(javaBytes) as number[]); + } +} + +function normalizeAndroidValue(value: unknown): WireValue | null { + if (value === undefined || value === null) return null; + if (typeof value === 'number' || typeof value === 'string') return value; + const jl = (globalThis as unknown as { java?: { lang?: { Number: unknown; Long: unknown } } }) + .java?.lang; + if (jl != null && value instanceof (jl.Number as abstract new () => unknown)) { + if (value instanceof (jl.Long as abstract new () => unknown)) return String(value); + return (value as { doubleValue(): number }).doubleValue(); + } + return String(value); +} + +function toJavaWireValue(val: WireValue): unknown { + if (typeof val === 'number') + return ( + globalThis as unknown as { + java: { lang: { Double: { valueOf(n: number): unknown } } }; + } + ).java.lang.Double.valueOf(val); + return String(val); +} + +function rethrow(error: unknown, context: string): never { + if (error instanceof ChicoryError) throw error; + throw new ChicoryError( + `${context}: ${(error instanceof Error ? error.message : String(error)).replace(/NSCChicoryException:\s*/, '')}`, + ); +} + +class AndroidFunction implements NativeFunctionAdapter { + constructor(private readonly fn: NativeChicoryFunctionProxy) {} + name(): string { + return String(this.fn.name()); + } + paramTypes(): WasmValueType[] { + return javaArrayToJs(this.fn.paramTypes()).map(String) as WasmValueType[]; + } + returnTypes(): WasmValueType[] { + return javaArrayToJs(this.fn.returnTypes()).map(String) as WasmValueType[]; + } + call(args: WireValue[]): WireValue[] { + const ctx = `call ${this.name()}`; + try { + const list = arrayList(); + for (const arg of args) list.add(toJavaWireValue(arg)); + const result = this.fn.call(list); + if (result == null) throw new ChicoryError(`${ctx}: returned null`); + return javaArrayToJs(result).map((v) => { + const n = normalizeAndroidValue(v); + if (n === null) throw new ChicoryError(`${ctx}: null slot`); + return n; + }); + } catch (error) { + rethrow(error, ctx); + } + } +} + +function makeAndroidHostCallback(cb: WireHostCallback): unknown { + const n = ns(); + if (!n?.NSCChicoryHostCallback) throw new ChicoryError('NSCChicoryHostCallback not available'); + // The Kotlin layer wraps the callback; pass through `unknown` to avoid narrowing. + return new n.NSCChicoryHostCallback(cb as (args: unknown[]) => unknown[]); +} + +class AndroidModule implements NativeModuleAdapter { + constructor(private readonly module: NativeChicoryModuleProxy) {} + name(): string { + return String(this.module.name()); + } + linkHostFunction(mod: string, name: string, signature: string, cb: WireHostCallback): void { + try { + this.module.linkHostFunction(mod, name, signature, makeAndroidHostCallback(cb)); + } catch (error) { + rethrow(error, `linkHostFunction ${mod}.${name}`); + } + } + getGlobal(name: string): WireValue { + try { + const value = normalizeAndroidValue(this.module.getGlobal(name)); + if (value === null) throw new ChicoryError(`getGlobal ${name}: returned null`); + return value; + } catch (error) { + rethrow(error, `getGlobal ${name}`); + } + } + setGlobal(name: string, value: WireValue): void { + try { + this.module.setGlobal(name, toJavaWireValue(value)); + } catch (error) { + rethrow(error, `setGlobal ${name}`); + } + } +} + +export class AndroidRuntime implements NativeRuntimeAdapter { + private readonly runtime: NativeChicoryRuntimeProxy; + constructor(stackSizeInBytes: number) { + const n = ns(); + if (!n?.NSCChicoryRuntime) + throw new ChicoryError('ns-wasm-chicory native runtime not found on Android'); + this.runtime = new n.NSCChicoryRuntime(stackSizeInBytes); + } + loadModuleFromBytes(bytes: Uint8Array): NativeModuleAdapter { + try { + return new AndroidModule(this.runtime.loadModuleFromBytes(toJavaBytes(bytes))); + } catch (e) { + rethrow(e, 'loadModule'); + throw null as never; + } + } + loadModuleFromFile(path: string): NativeModuleAdapter { + try { + return new AndroidModule(this.runtime.loadModuleFromFile(path)); + } catch (e) { + rethrow(e, 'loadModule'); + throw null as never; + } + } + findFunction(name: string): NativeFunctionAdapter { + try { + return new AndroidFunction(this.runtime.findFunction(name)); + } catch (e) { + rethrow(e, 'findFunction'); + throw null as never; + } + } + memorySize(): number { + return Number(this.runtime.memorySize()); + } + readMemory(o: number, len: number): Uint8Array { + try { + return fromJavaBytes(this.runtime.readMemory(o, len)); + } catch (e) { + rethrow(e, 'readMemory'); + throw null as never; + } + } + writeMemory(o: number, bytes: Uint8Array): void { + try { + this.runtime.writeMemory(o, toJavaBytes(bytes)); + } catch (e) { + rethrow(e, 'writeMemory'); + } + } + dispose(): void { + try { + this.runtime.dispose(); + } catch { + /* intentionally empty */ + } + } +} diff --git a/packages/ns-wasm-chicory/src/lib/chicory-ios.ts b/packages/ns-wasm-chicory/src/lib/chicory-ios.ts new file mode 100644 index 0000000..f742ae0 --- /dev/null +++ b/packages/ns-wasm-chicory/src/lib/chicory-ios.ts @@ -0,0 +1,13 @@ +// iOS platform adapter for Chicory — pure-Java runtime, not supported on iOS. +import { ChicoryError } from './wire.js'; +import type { NativeRuntimeAdapter, NativeModuleAdapter, NativeFunctionAdapter } from '@cross-code/ns-wasm-core'; +export class IosRuntime implements NativeRuntimeAdapter { + constructor(_s: number) { throw new ChicoryError('Chicory is pure-Java and does not support iOS — use ns-wasm3, ns-wamr or ns-wasm-kit-runtime for iOS targets'); } + loadModuleFromBytes(_b: Uint8Array): NativeModuleAdapter { throw new ChicoryError('Chicory: iOS not supported'); } + loadModuleFromFile(_p: string): NativeModuleAdapter { throw new ChicoryError('Chicory: iOS not supported'); } + findFunction(_n: string): NativeFunctionAdapter { throw new ChicoryError('Chicory: iOS not supported'); } + memorySize(): number { throw new ChicoryError('Chicory: iOS not supported'); } + readMemory(): Uint8Array { throw new ChicoryError('Chicory: iOS not supported'); } + writeMemory(): void { throw new ChicoryError('Chicory: iOS not supported'); } + dispose(): void {} +} diff --git a/packages/ns-wasm-chicory/src/lib/chicory.spec.ts b/packages/ns-wasm-chicory/src/lib/chicory.spec.ts new file mode 100644 index 0000000..f7129d3 --- /dev/null +++ b/packages/ns-wasm-chicory/src/lib/chicory.spec.ts @@ -0,0 +1,23 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { ChicoryError } from './wire.js'; +import { ChicoryRuntime } from './chicory.js'; +const g = globalThis as any; +afterEach(() => { delete g.org; delete g.java; }); +function installAndroidFake() { + g.java = { util: { ArrayList: class { private i: any[] = []; add(v: any) { this.i.push(v); } get(i: number) { return this.i[i]; } size() { return this.i.length; } } }, lang: { Double: class { private v: number; constructor(v: number) { this.v = v; } static valueOf(v: number) { return new g.java.lang.Double(v); } doubleValue() { return this.v; } } } }; + const ns = (g.org = { nativescript: { chicory: {} } } as any).nativescript.chicory; + const mem = new Uint8Array(64 * 1024); + ns.NSCChicoryRuntime = class { private _m = mem; constructor() {} static chicoryVersion() { return '0.1.0'; } static jsByteArrayToJava(buf: ArrayBuffer, off: number, len: number) { const a = new g.java.util.ArrayList(); const b = new Uint8Array(buf, off, len); for (let i = 0; i < b.length; i++) a.add(b[i]); return a; } static javaByteArrayToJs(bytes: any) { const arr = new Uint8Array(bytes.size()); for (let i = 0; i < arr.length; i++) arr[i] = bytes.get(i); return arr.buffer; } loadModuleFromBytes() { return {}; } loadModuleFromFile() { return {}; } findFunction(n: string) { return { name() { return n; } }; } memorySize() { return this._m.length; } readMemory(o: number, len: number) { const a = new g.java.util.ArrayList(); const b = this._m.slice(o, o + len); for (let i = 0; i < b.length; i++) a.add(b[i]); return a; } writeMemory(o: number, bytes: any) { const arr = new Uint8Array(bytes.size()); for (let i = 0; i < arr.length; i++) arr[i] = bytes.get(i); this._m.set(arr, o); } dispose() {} }; + ns.NSCChicoryHostCallback = class { + constructor(private readonly cb: (args: any[]) => any[]) {} + invoke(args: any[]) { return this.cb(args); } + }; +} +describe('ChicoryRuntime (Android fake)', () => { + beforeEach(() => { installAndroidFake(); }); + it('reports version', () => { const r = new ChicoryRuntime(); expect(ChicoryRuntime.version()).toBe('0.1.0'); r.dispose(); }); + it('loads module from bytes', () => { const r = new ChicoryRuntime(); const m = r.loadModule(new Uint8Array([0, 0x61, 0x73, 0x6d, 1, 0, 0, 0])); expect(m).toBeDefined(); r.dispose(); }); + it('finds function', () => { const r = new ChicoryRuntime(); expect(r.findFunction('t').name).toBe('t'); r.dispose(); }); + it('reads/writes memory', () => { const r = new ChicoryRuntime(); r.writeMemory(0, new Uint8Array([1, 2, 3])); expect(r.readMemory(0, 3)).toEqual(new Uint8Array([1, 2, 3])); expect(r.memorySize).toBe(64 * 1024); r.dispose(); }); + it('throws when native missing', () => { delete g.org; expect(() => new ChicoryRuntime()).toThrow(ChicoryError); }); +}); diff --git a/packages/ns-wasm-chicory/src/lib/chicory.ts b/packages/ns-wasm-chicory/src/lib/chicory.ts new file mode 100644 index 0000000..2e125fc --- /dev/null +++ b/packages/ns-wasm-chicory/src/lib/chicory.ts @@ -0,0 +1,79 @@ +// ns-wasm-chicory — Chicory (https://github.com/dylibso/chicory) +// pure-Java WebAssembly runtime plugin for NativeScript (Android-only). +import type { WasmValue } from '@cross-code/ns-wasm-core'; +import { + WasmRuntime, + WasmModule, + WasmFunction, + type NativeRuntimeAdapter, +} from '@cross-code/ns-wasm-core'; +import { ChicoryError } from './wire.js'; +import { IosRuntime } from './chicory-ios.js'; +import { AndroidRuntime } from './chicory-android.js'; +import type { NativeScriptOrg } from './native-proxy.js'; + +export { + type WireHostCallback, + type NativeFunctionAdapter, + type NativeModuleAdapter, + type NativeRuntimeAdapter, +} from '@cross-code/ns-wasm-core'; + +export type ChicoryHostFunction = (...args: WasmValue[]) => WasmValue | WasmValue[] | void; +export type ChicoryModuleSource = string | ArrayBuffer | Uint8Array | number[]; +export interface ChicoryImports { + [module: string]: { [name: string]: { signature: string; fn: ChicoryHostFunction } }; +} +export interface ChicoryRuntimeOptions { + stackSizeInBytes?: number; +} + +interface GlobalThisWithChicory { + org?: NativeScriptOrg['org']; + NSCChicoryRuntime?: unknown; +} + +function createAdapter(stackSizeInBytes: number): NativeRuntimeAdapter { + const g = globalThis as unknown as GlobalThisWithChicory; + if (g.org?.nativescript?.chicory?.NSCChicoryRuntime) + return new AndroidRuntime(stackSizeInBytes); + if (typeof g.NSCChicoryRuntime !== 'undefined' && g.NSCChicoryRuntime !== null) + return new IosRuntime(stackSizeInBytes); + throw new ChicoryError('ns-wasm-chicory native runtime not found'); +} + +function chicoryVersionNative(): string { + const g = globalThis as unknown as GlobalThisWithChicory; + if (g.org?.nativescript?.chicory?.NSCChicoryRuntime) + return String( + ( + g.org.nativescript.chicory.NSCChicoryRuntime as { chicoryVersion(): string } + ).chicoryVersion(), + ); + if (typeof g.NSCChicoryRuntime !== 'undefined' && g.NSCChicoryRuntime !== null) + return String( + (g.NSCChicoryRuntime as { chicoryVersion(): string }).chicoryVersion(), + ); + throw new ChicoryError('ns-wasm-chicory native runtime not found'); +} + +export class ChicoryFunction extends WasmFunction {} +export class ChicoryModule extends WasmModule {} + +export class ChicoryRuntime extends WasmRuntime { + constructor(options?: ChicoryRuntimeOptions) { + super(createAdapter(options?.stackSizeInBytes ?? 64 * 1024), { + moduleCtor: ChicoryModule, + functionCtor: ChicoryFunction, + }); + } + static version(): string { + return chicoryVersionNative(); + } + override loadModule(source: ChicoryModuleSource, imports?: ChicoryImports): ChicoryModule { + return super.loadModule(source, imports) as unknown as ChicoryModule; + } + override findFunction(name: string): ChicoryFunction { + return super.findFunction(name) as unknown as ChicoryFunction; + } +} diff --git a/packages/ns-wasm-chicory/src/lib/native-api.d.ts b/packages/ns-wasm-chicory/src/lib/native-api.d.ts new file mode 100644 index 0000000..5bfba0e --- /dev/null +++ b/packages/ns-wasm-chicory/src/lib/native-api.d.ts @@ -0,0 +1,41 @@ +// Native type declarations for Chicory Swift/Kotlin classes. +// These are ambient declarations for the NativeScript @objc bridge classes. +// `unknown` is used for opaque bridge values (error refs, callbacks, runtime values). + +declare class NSCChicoryRuntime { + static chicoryVersion(): string; + init(stackSizeInBytes: number): this; + loadModuleBytesError(data: NSData, error: unknown): NSCChicoryModule; + loadModuleFileError(path: string, error: unknown): NSCChicoryModule; + findFunctionError(name: string, error: unknown): NSCChicoryFunction; + memorySize(): number; + readMemoryAtOffsetLengthError(offset: number, length: number, error: unknown): NSData; + writeMemoryAtOffsetDataError(offset: number, data: NSData, error: unknown): void; +} + +declare class NSCChicoryModule { + readonly name: string; + linkHostFunctionModuleNameNameSignatureCallbackError( + mod: string, + name: string, + sig: string, + cb: unknown, + error: unknown, + ): void; + getGlobalNameError(name: string, error: unknown): unknown; + setGlobalNameValueError(name: string, value: unknown, error: unknown): void; +} + +declare class NSCChicoryFunction { + readonly name: string; + readonly paramTypes: string[]; + readonly returnTypes: string[]; + callWithArgumentsError(args: unknown[], error: unknown): unknown[]; +} + +declare class NSCChicoryHostCallback { + static extend(config: { invoke(args: unknown[]): unknown[] }): { + new (): NSCChicoryHostCallback; + }; + static new(): NSCChicoryHostCallback; +} diff --git a/packages/ns-wasm-chicory/src/lib/native-proxy.d.ts b/packages/ns-wasm-chicory/src/lib/native-proxy.d.ts new file mode 100644 index 0000000..f626a8e --- /dev/null +++ b/packages/ns-wasm-chicory/src/lib/native-proxy.d.ts @@ -0,0 +1,65 @@ +// Minimal TypeScript interfaces for the NativeScript-Kotlin bridge objects. +// These replace `any` with concrete shapes for compile-time safety. +// The actual objects are created by the Kotlin layer and accessed via +// globalThis.org.nativescript.chicory.* on Android. + +/** Opaque Java ArrayList proxy — methods are called through JNI. */ +export interface JavaArrayList { + add(value: unknown): void; + get(index: number): unknown; + size(): number; +} + +/** Shape of the NSCChicoryRuntime Kotlin class as seen from TypeScript. */ +export interface NativeChicoryRuntimeProxy { + chicoryVersion(): string; + jsByteArrayToJava(buffer: ArrayBuffer, offset: number, length: number): JavaArrayList; + javaByteArrayToJs(bytes: JavaArrayList): ArrayBuffer; + loadModuleFromBytes(bytes: JavaArrayList): NativeChicoryModuleProxy; + loadModuleFromFile(path: string): NativeChicoryModuleProxy; + findFunction(name: string): NativeChicoryFunctionProxy; + memorySize(): number; + readMemory(offset: number, length: number): JavaArrayList; + writeMemory(offset: number, bytes: JavaArrayList): void; + dispose(): void; +} + +/** Shape of the NSCChicoryModule Kotlin class. */ +export interface NativeChicoryModuleProxy { + name(): string; + linkHostFunction(mod: string, name: string, sig: string, cb: unknown): void; + getGlobal(name: string): unknown; + setGlobal(name: string, value: unknown): void; +} + +/** Shape of the NSCChicoryFunction Kotlin class. */ +export interface NativeChicoryFunctionProxy { + name(): string; + paramTypes(): JavaArrayList; + returnTypes(): JavaArrayList; + call(args: JavaArrayList): JavaArrayList; +} + +/** Shape of the NSCChicoryHostCallback Kotlin class. */ +export interface NativeChicoryHostCallbackProxy { + new (cb: (args: unknown[]) => unknown[]): NativeChicoryHostCallbackProxy; +} + +/** Namespace shape for globalThis.org.nativescript.chicory */ +export interface NsChicoryNamespace { + NSCChicoryRuntime: (new (stackSizeInBytes: number) => NativeChicoryRuntimeProxy) & { + chicoryVersion(): string; + jsByteArrayToJava(buffer: ArrayBuffer, offset: number, length: number): JavaArrayList; + javaByteArrayToJs(bytes: JavaArrayList): ArrayBuffer; + }; + NSCChicoryHostCallback: NativeChicoryHostCallbackProxy; +} + +/** Shape of the NativeScript globalThis org object */ +export interface NativeScriptOrg { + org?: { + nativescript?: { + chicory?: NsChicoryNamespace; + }; + }; +} diff --git a/packages/ns-wasm-chicory/src/lib/wire.ts b/packages/ns-wasm-chicory/src/lib/wire.ts new file mode 100644 index 0000000..de76644 --- /dev/null +++ b/packages/ns-wasm-chicory/src/lib/wire.ts @@ -0,0 +1,4 @@ +export { WasmError as ChicoryError } from '@cross-code/ns-wasm-core'; +export { WasmError } from '@cross-code/ns-wasm-core'; +export { hostResultToWire, fromWireAll, parseSignature, toWire, fromWire, unwrapResults, + type WasmArg, type WasmValue, type WasmValueType, type WireValue, type ParsedSignature } from '@cross-code/ns-wasm-core'; diff --git a/packages/ns-wasm-chicory/tsconfig.json b/packages/ns-wasm-chicory/tsconfig.json new file mode 100644 index 0000000..73d80f7 --- /dev/null +++ b/packages/ns-wasm-chicory/tsconfig.json @@ -0,0 +1 @@ +{"extends":"../../tsconfig.base.json","files":[],"include":[],"references":[{"path":"./tsconfig.lib.json"},{"path":"./tsconfig.spec.json"}]} diff --git a/packages/ns-wasm-chicory/tsconfig.lib.json b/packages/ns-wasm-chicory/tsconfig.lib.json new file mode 100644 index 0000000..940c1b5 --- /dev/null +++ b/packages/ns-wasm-chicory/tsconfig.lib.json @@ -0,0 +1,31 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "emitDeclarationOnly": false, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "references": [ + { + "path": "../ns-wasm-core/tsconfig.lib.json" + } + ], + "exclude": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx" + ] +} diff --git a/packages/ns-wasm-chicory/tsconfig.spec.json b/packages/ns-wasm-chicory/tsconfig.spec.json new file mode 100644 index 0000000..a71ffa7 --- /dev/null +++ b/packages/ns-wasm-chicory/tsconfig.spec.json @@ -0,0 +1 @@ +{"extends":"../../tsconfig.base.json","compilerOptions":{"rootDir":".","outDir":"dist","tsBuildInfoFile":"dist/tsconfig.spec.tsbuildinfo","emitDeclarationOnly":false,"types":["vitest/globals","node"]},"include":["vite.config.ts","vite.config.mts","vitest.config.ts","vitest.config.mts","src/**/*.test.ts","src/**/*.spec.ts","src/**/*.test.tsx","src/**/*.spec.tsx","src/**/*.test.js","src/**/*.spec.js","src/**/*.test.jsx","src/**/*.spec.jsx","src/**/*.d.ts"],"references":[{"path":"./tsconfig.lib.json"}]} diff --git a/packages/ns-wasm-chicory/vitest.config.mts b/packages/ns-wasm-chicory/vitest.config.mts new file mode 100644 index 0000000..512b9d7 --- /dev/null +++ b/packages/ns-wasm-chicory/vitest.config.mts @@ -0,0 +1,2 @@ +import { defineConfig } from 'vitest/config'; +export default defineConfig(() => ({ root: __dirname, cacheDir: '../../node_modules/.vite/packages/ns-wasm-chicory', test: { name: 'ns-wasm-chicory', watch: false, globals: true, environment: 'node', include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], reporters: ['default'], coverage: { provider: 'v8' as const, reportsDirectory: './test-output/vitest/coverage', include: ['src/**/*.ts'], exclude: ['src/**/*.spec.ts', 'src/**/*.test.ts', 'src/**/*.d.ts'] } } })); diff --git a/packages/ns-wasm-core/package.json b/packages/ns-wasm-core/package.json index 395fe73..290367e 100644 --- a/packages/ns-wasm-core/package.json +++ b/packages/ns-wasm-core/package.json @@ -3,16 +3,16 @@ "version": "0.1.0", "description": "Shared WebAssembly runtime abstractions for @cross-code/ns-wasm3 and @cross-code/ns-wamr — wire protocol, type definitions, and platform adapter interfaces.", "type": "module", - "main": "./src/index.ts", - "module": "./src/index.ts", - "types": "./src/index.ts", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { "./package.json": "./package.json", ".": { "@cross-code/source": "./src/index.ts", - "types": "./src/index.ts", - "import": "./src/index.ts", - "default": "./src/index.ts" + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" } }, "nx": { diff --git a/packages/ns-wasm-core/src/lib/runtime.ts b/packages/ns-wasm-core/src/lib/runtime.ts index 4c16814..3189f8a 100644 --- a/packages/ns-wasm-core/src/lib/runtime.ts +++ b/packages/ns-wasm-core/src/lib/runtime.ts @@ -6,7 +6,7 @@ // under engine-prefixed names (Wasm3Runtime, WamrRuntime, …). import { - WasmError, + fromWire, fromWireAll, hostResultToWire, parseSignature, @@ -15,7 +15,6 @@ import { type WasmArg, type WasmValue, type WasmValueType, - type WireValue, } from './wire.js'; import type { WireHostCallback, @@ -82,14 +81,16 @@ export class WasmRuntime { * byte array. Optionally links host imports before first use. */ loadModule(source: WasmModuleSource, imports?: WasmImports): WasmModule { + let module: WasmModule; if (typeof source === 'string') { // Cast: constructor-types stored in a variable need `as any` for // `new` in strict TS (the precise constructor overload isn't known // at the call-site). - return new (this._ModuleCtor as any)(this.adapter.loadModuleFromFile(source), this); + module = new (this._ModuleCtor as any)(this.adapter.loadModuleFromFile(source), this); + } else { + const adapter = this.adapter.loadModuleFromBytes(toBytes(source)); + module = new (this._ModuleCtor as any)(adapter, this); } - const adapter = this.adapter.loadModuleFromBytes(toBytes(source)); - const module = new (this._ModuleCtor as any)(adapter, this); if (imports) module.linkImports(imports); return module; } diff --git a/packages/ns-wasm-core/src/lib/wire.ts b/packages/ns-wasm-core/src/lib/wire.ts index 1b3881f..c51531d 100644 --- a/packages/ns-wasm-core/src/lib/wire.ts +++ b/packages/ns-wasm-core/src/lib/wire.ts @@ -70,15 +70,22 @@ export function toWire(value: WasmArg, context: string): WireValue { export function fromWire(type: WasmValueType, value: WireValue): WasmValue { if (type === 'i64') { const s = String(value); - const big = BigInt(s); + let big: bigint; + try { + big = BigInt(s); + } catch { + throw new WasmError(`invalid i64 wire value: "${s}"`); + } if (String(big) !== s) { throw new WasmError(`invalid i64 wire value: "${s}"`); } return big; } const n = Number(value); - if (!Number.isFinite(n)) { - throw new WasmError(`invalid ${type} wire value: ${String(value)}`); + if (type === 'i32') { + if (!Number.isFinite(n)) { + throw new WasmError(`invalid i32 wire value: ${String(value)}`); + } } return n; } diff --git a/packages/ns-wasm-edge/package.json b/packages/ns-wasm-edge/package.json new file mode 100644 index 0000000..fb4cb87 --- /dev/null +++ b/packages/ns-wasm-edge/package.json @@ -0,0 +1,49 @@ +{ + "name": "@cross-code/ns-wasm-edge", + "version": "0.1.0", + "description": "NativeScript plugin that runs WebAssembly modules with the WasmEdge runtime (https://github.com/WasmEdge/WasmEdge) — Swift Package on iOS, Kotlin + Rust JNI (cargo-ndk) on Android.", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "@cross-code/source": "./src/index.ts", + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "files": [ + "dist", + "nativescript.config.ts", + "platforms/ios/NSCWasmEdge/Package.swift", + "platforms/ios/NSCWasmEdge/Sources", + "platforms/android/include.gradle", + "platforms/android/wasmedge.aar", + "!**/*.tsbuildinfo" + ], + "nativescript": { + "platforms": { + "ios": "8.6.0", + "android": "8.6.0" + } + }, + "nx": { + "name": "ns-wasm-edge", + "targets": { + "coverage": { + "executor": "nx:run-script", + "options": { "script": "coverage" } + } + } + }, + "scripts": { + "coverage": "vitest run --coverage" + }, + "dependencies": { + "@cross-code/ns-wasm-core": "workspace:*", + "tslib": "^2.3.0" + } +} diff --git a/packages/ns-wasm-edge/src/index.ts b/packages/ns-wasm-edge/src/index.ts new file mode 100644 index 0000000..ec4a41a --- /dev/null +++ b/packages/ns-wasm-edge/src/index.ts @@ -0,0 +1,2 @@ +export { WasmEdgeError, type ParsedSignature, type WasmArg, type WasmValue, type WasmValueType } from './lib/wire.js'; +export { WasmEdgeFunction, WasmEdgeModule, WasmEdgeRuntime, type WasmEdgeHostFunction, type WasmEdgeImports, type WasmEdgeModuleSource, type WasmEdgeRuntimeOptions } from './lib/wasmedge.js'; diff --git a/packages/ns-wasm-edge/src/lib/native-api.d.ts b/packages/ns-wasm-edge/src/lib/native-api.d.ts new file mode 100644 index 0000000..d02354e --- /dev/null +++ b/packages/ns-wasm-edge/src/lib/native-api.d.ts @@ -0,0 +1,41 @@ +// Native type declarations for WasmEdge Swift/Kotlin classes. +// These are ambient declarations for the NativeScript @objc bridge classes. +// `unknown` is used for opaque bridge values (error refs, callbacks, runtime values). + +declare class NSCWasmEdgeRuntime { + static wasmedgeVersion(): string; + init(stackSizeInBytes: number): this; + loadModuleBytesError(data: NSData, error: unknown): NSCWasmEdgeModule; + loadModuleFileError(path: string, error: unknown): NSCWasmEdgeModule; + findFunctionError(name: string, error: unknown): NSCWasmEdgeFunction; + memorySize(): number; + readMemoryAtOffsetLengthError(offset: number, length: number, error: unknown): NSData; + writeMemoryAtOffsetDataError(offset: number, data: NSData, error: unknown): void; +} + +declare class NSCWasmEdgeModule { + readonly name: string; + linkHostFunctionModuleNameNameSignatureCallbackError( + mod: string, + name: string, + sig: string, + cb: unknown, + error: unknown, + ): void; + getGlobalNameError(name: string, error: unknown): unknown; + setGlobalNameValueError(name: string, value: unknown, error: unknown): void; +} + +declare class NSCWasmEdgeFunction { + readonly name: string; + readonly paramTypes: string[]; + readonly returnTypes: string[]; + callWithArgumentsError(args: unknown[], error: unknown): unknown[]; +} + +declare class NSCWasmEdgeHostCallback { + static extend(config: { invoke(args: unknown[]): unknown[] }): { + new (): NSCWasmEdgeHostCallback; + }; + static new(): NSCWasmEdgeHostCallback; +} diff --git a/packages/ns-wasm-edge/src/lib/native-proxy.d.ts b/packages/ns-wasm-edge/src/lib/native-proxy.d.ts new file mode 100644 index 0000000..e706d7a --- /dev/null +++ b/packages/ns-wasm-edge/src/lib/native-proxy.d.ts @@ -0,0 +1,106 @@ +// Minimal TypeScript interfaces for the NativeScript-Kotlin/Swift bridge objects. +// These replace `any` with concrete shapes for compile-time safety. +// The actual objects are created by the Kotlin/Swift layer and accessed via +// globalThis.org.nativescript.wasmedge.* (Android) or globalThis.NSCWasmEdge* (iOS). + +/** Opaque Java ArrayList proxy — methods are called through JNI. */ +export interface JavaArrayList { + add(value: unknown): void; + get(index: number): unknown; + size(): number; +} + +/** Shape of the NSCWasmEdgeRuntime Kotlin class as seen from TypeScript. */ +export interface NativeWasmEdgeRuntimeProxy { + wasmedgeVersion(): string; + jsByteArrayToJava(buffer: ArrayBuffer, offset: number, length: number): JavaArrayList; + javaByteArrayToJs(bytes: JavaArrayList): ArrayBuffer; + loadModuleFromBytes(bytes: JavaArrayList): NativeWasmEdgeModuleProxy; + loadModuleFromFile(path: string): NativeWasmEdgeModuleProxy; + findFunction(name: string): NativeWasmEdgeFunctionProxy; + memorySize(): number; + readMemory(offset: number, length: number): JavaArrayList; + writeMemory(offset: number, bytes: JavaArrayList): void; + dispose(): void; +} + +/** Shape of the NSCWasmEdgeModule Kotlin class. */ +export interface NativeWasmEdgeModuleProxy { + name(): string; + linkHostFunction(mod: string, name: string, sig: string, cb: unknown): void; + getGlobal(name: string): unknown; + setGlobal(name: string, value: unknown): void; +} + +/** Shape of the NSCWasmEdgeFunction Kotlin class. */ +export interface NativeWasmEdgeFunctionProxy { + name(): string; + paramTypes(): JavaArrayList; + returnTypes(): JavaArrayList; + call(args: JavaArrayList): JavaArrayList; +} + +/** Shape of the NSCWasmEdgeHostCallback Kotlin class. */ +export interface NativeWasmEdgeHostCallbackProxy { + new (cb: (args: unknown[]) => unknown[]): NativeWasmEdgeHostCallbackProxy; +} + +/** Namespace shape for globalThis.org.nativescript.wasmedge */ +export interface NsWasmEdgeNamespace { + NSCWasmEdgeRuntime: (new (stackSizeInBytes: number) => NativeWasmEdgeRuntimeProxy) & { + wasmedgeVersion(): string; + jsByteArrayToJava(buffer: ArrayBuffer, offset: number, length: number): JavaArrayList; + javaByteArrayToJs(bytes: JavaArrayList): ArrayBuffer; + }; + NSCWasmEdgeHostCallback: NativeWasmEdgeHostCallbackProxy; +} + +/** Shape of the NativeScript globalThis org object */ +export interface NativeScriptOrg { + org?: { + nativescript?: { + wasmedge?: NsWasmEdgeNamespace; + }; + }; +} + +/** Shape of the iOS NSCWasmEdgeRuntime class (Swift @objc). */ +export interface IosWasmEdgeRuntimeProxy { + wasmedgeVersion(): string; + loadModuleBytesError(data: unknown, error: [unknown]): IosWasmEdgeModuleProxy; + loadModuleFileError(path: string, error: [unknown]): IosWasmEdgeModuleProxy; + findFunctionError(name: string, error: [unknown]): IosWasmEdgeFunctionProxy; + memorySize(): number; + readMemoryAtOffsetLengthError(offset: number, length: number, error: [unknown]): unknown; + writeMemoryAtOffsetDataError(offset: number, data: Uint8Array, error: [unknown]): void; +} + +/** Shape of the iOS NSCWasmEdgeModule class. */ +export interface IosWasmEdgeModuleProxy { + name: string; + linkHostFunctionModuleNameNameSignatureCallbackError( + mod: string, + name: string, + sig: string, + cb: unknown, + error: [unknown], + ): void; + getGlobalNameError(name: string, error: [unknown]): unknown; + setGlobalNameValueError(name: string, value: unknown, error: [unknown]): void; +} + +/** Shape of the iOS NSCWasmEdgeFunction class. */ +export interface IosWasmEdgeFunctionProxy { + name: string; + paramTypes: unknown; + returnTypes: unknown; + callWithArgumentsError(args: unknown[], error: [unknown]): unknown; +} + +/** Shape of the iOS NSCWasmEdgeHostCallback class. */ +export interface IosWasmEdgeHostCallbackProxy { + extend(config: { invoke(args: unknown[]): unknown }): { + new (): IosWasmEdgeHostCallbackProxy; + }; + new (): IosWasmEdgeHostCallbackProxy; +} diff --git a/packages/ns-wasm-edge/src/lib/wasmedge-android.ts b/packages/ns-wasm-edge/src/lib/wasmedge-android.ts new file mode 100644 index 0000000..9b34d5b --- /dev/null +++ b/packages/ns-wasm-edge/src/lib/wasmedge-android.ts @@ -0,0 +1,209 @@ +// Android (Kotlin/JNI) platform adapter for WasmEdge. +import { WasmEdgeError, type WasmValueType, type WireValue } from './wire.js'; +import type { + WireHostCallback, + NativeFunctionAdapter, + NativeModuleAdapter, + NativeRuntimeAdapter, +} from '@cross-code/ns-wasm-core'; +import type { + JavaArrayList, + NativeWasmEdgeRuntimeProxy, + NativeWasmEdgeModuleProxy, + NativeWasmEdgeFunctionProxy, + NativeScriptOrg, + NsWasmEdgeNamespace, +} from './native-proxy.js'; + +function ns(): NsWasmEdgeNamespace | undefined { + return (globalThis as unknown as NativeScriptOrg).org?.nativescript?.wasmedge; +} + +function arrayList(): JavaArrayList { + return new ( + (globalThis as unknown as { java: { util: { ArrayList: new () => JavaArrayList } } }).java.util + .ArrayList + )(); +} + +function javaArrayToJs(list: JavaArrayList): unknown[] { + const r: unknown[] = []; + for (let i = 0; i < list.size(); i++) r.push(list.get(i)); + return r; +} + +function toJavaBytes(source: Uint8Array): JavaArrayList { + try { + return ns()?.NSCWasmEdgeRuntime.jsByteArrayToJava( + source.buffer as ArrayBuffer, + source.byteOffset, + source.byteLength, + )!; + } catch { + const bytes = arrayList(); + for (let i = 0; i < source.length; i++) bytes.add(source[i]); + return bytes; + } +} + +function fromJavaBytes(javaBytes: JavaArrayList): Uint8Array { + try { + return new Uint8Array(ns()!.NSCWasmEdgeRuntime.javaByteArrayToJs(javaBytes)); + } catch { + return Uint8Array.from(javaArrayToJs(javaBytes) as number[]); + } +} + +function normalizeAndroidValue(value: unknown): WireValue | null { + if (value === undefined || value === null) return null; + if (typeof value === 'number' || typeof value === 'string') return value; + const jl = (globalThis as unknown as { java?: { lang?: { Number: unknown; Long: unknown } } }) + .java?.lang; + if (jl != null && value instanceof (jl.Number as abstract new () => unknown)) { + if (value instanceof (jl.Long as abstract new () => unknown)) return String(value); + return (value as { doubleValue(): number }).doubleValue(); + } + return String(value); +} + +function toJavaWireValue(val: WireValue): unknown { + if (typeof val === 'number') + return ( + globalThis as unknown as { + java: { lang: { Double: { valueOf(n: number): unknown } } }; + } + ).java.lang.Double.valueOf(val); + return String(val); +} + +function rethrow(error: unknown, context: string): never { + if (error instanceof WasmEdgeError) throw error; + const raw = error instanceof Error ? error.message : String(error); + throw new WasmEdgeError(`${context}: ${raw.replace(/NSCWasmEdgeException:\s*/, '')}`); +} + +class AndroidFunction implements NativeFunctionAdapter { + constructor(private readonly fn: NativeWasmEdgeFunctionProxy) {} + name(): string { + return String(this.fn.name()); + } + paramTypes(): WasmValueType[] { + return javaArrayToJs(this.fn.paramTypes()).map(String) as WasmValueType[]; + } + returnTypes(): WasmValueType[] { + return javaArrayToJs(this.fn.returnTypes()).map(String) as WasmValueType[]; + } + call(args: WireValue[]): WireValue[] { + const ctx = `call ${this.name()}`; + try { + const list = arrayList(); + for (const arg of args) list.add(toJavaWireValue(arg)); + const result = this.fn.call(list); + if (result == null) throw new WasmEdgeError(`${ctx}: returned null`); + return javaArrayToJs(result).map((v) => { + const n = normalizeAndroidValue(v); + if (n === null) throw new WasmEdgeError(`${ctx}: null slot`); + return n; + }); + } catch (error) { + rethrow(error, ctx); + } + } +} + +function makeAndroidHostCallback(cb: WireHostCallback): unknown { + const n = ns(); + if (!n?.NSCWasmEdgeHostCallback) + throw new WasmEdgeError('NSCWasmEdgeHostCallback not available'); + // The Kotlin layer wraps the callback; pass through `unknown` to avoid narrowing. + return new n.NSCWasmEdgeHostCallback(cb as (args: unknown[]) => unknown[]); +} + +class AndroidModule implements NativeModuleAdapter { + constructor(private readonly module: NativeWasmEdgeModuleProxy) {} + name(): string { + return String(this.module.name()); + } + linkHostFunction(mod: string, name: string, signature: string, cb: WireHostCallback): void { + try { + this.module.linkHostFunction(mod, name, signature, makeAndroidHostCallback(cb)); + } catch (error) { + rethrow(error, `linkHostFunction ${mod}.${name}`); + } + } + getGlobal(name: string): WireValue { + try { + const value = normalizeAndroidValue(this.module.getGlobal(name)); + if (value === null) throw new WasmEdgeError(`getGlobal ${name}: returned null`); + return value; + } catch (error) { + rethrow(error, `getGlobal ${name}`); + } + } + setGlobal(name: string, value: WireValue): void { + try { + this.module.setGlobal(name, toJavaWireValue(value)); + } catch (error) { + rethrow(error, `setGlobal ${name}`); + } + } +} + +export class AndroidRuntime implements NativeRuntimeAdapter { + private readonly runtime: NativeWasmEdgeRuntimeProxy; + constructor(stackSizeInBytes: number) { + const n = ns(); + if (!n?.NSCWasmEdgeRuntime) + throw new WasmEdgeError('ns-wasm-edge native runtime not found on Android'); + this.runtime = new n.NSCWasmEdgeRuntime(stackSizeInBytes); + } + loadModuleFromBytes(bytes: Uint8Array): NativeModuleAdapter { + try { + return new AndroidModule(this.runtime.loadModuleFromBytes(toJavaBytes(bytes))); + } catch (e) { + rethrow(e, 'loadModule'); + throw null as never; + } + } + loadModuleFromFile(path: string): NativeModuleAdapter { + try { + return new AndroidModule(this.runtime.loadModuleFromFile(path)); + } catch (e) { + rethrow(e, 'loadModule'); + throw null as never; + } + } + findFunction(name: string): NativeFunctionAdapter { + try { + return new AndroidFunction(this.runtime.findFunction(name)); + } catch (e) { + rethrow(e, 'findFunction'); + throw null as never; + } + } + memorySize(): number { + return Number(this.runtime.memorySize()); + } + readMemory(o: number, len: number): Uint8Array { + try { + return fromJavaBytes(this.runtime.readMemory(o, len)); + } catch (e) { + rethrow(e, 'readMemory'); + throw null as never; + } + } + writeMemory(o: number, bytes: Uint8Array): void { + try { + this.runtime.writeMemory(o, toJavaBytes(bytes)); + } catch (e) { + rethrow(e, 'writeMemory'); + } + } + dispose(): void { + try { + this.runtime.dispose(); + } catch { + /* intentionally empty */ + } + } +} diff --git a/packages/ns-wasm-edge/src/lib/wasmedge-ios.ts b/packages/ns-wasm-edge/src/lib/wasmedge-ios.ts new file mode 100644 index 0000000..440f566 --- /dev/null +++ b/packages/ns-wasm-edge/src/lib/wasmedge-ios.ts @@ -0,0 +1,196 @@ +// iOS (Swift/NSCWasmEdge) platform adapter. +import { WasmEdgeError, type WasmValueType, type WireValue } from './wire.js'; +import type { + WireHostCallback, + NativeFunctionAdapter, + NativeModuleAdapter, + NativeRuntimeAdapter, +} from '@cross-code/ns-wasm-core'; +import type { + IosWasmEdgeRuntimeProxy, + IosWasmEdgeModuleProxy, + IosWasmEdgeFunctionProxy, +} from './native-proxy.js'; + +function nsArrayToJs(value: unknown): unknown[] { + if (value == null) return []; + if (Array.isArray(value)) return value; + const typed = value as { count?: number; objectAtIndex(i: number): unknown }; + const result: unknown[] = []; + const count = typed.count ?? 0; + for (let i = 0; i < count; i++) result.push(typed.objectAtIndex(i)); + return result; +} + +function iosInterop(): + | { + Reference?: new () => { value: unknown }; + bufferFromData(data: unknown): ArrayBuffer; + } + | undefined { + return ( + globalThis as unknown as { + interop?: { + Reference?: new () => { value: unknown }; + bufferFromData(data: unknown): ArrayBuffer; + }; + } + ).interop; +} + +function toNsArray(values: WireValue[]): unknown { + const array = ( + globalThis as unknown as { + NSMutableArray: { alloc(): { init(): { addObject(v: unknown): void } } }; + } + ).NSMutableArray.alloc().init(); + for (const value of values) array.addObject(value); + return array; +} + +function newErrorRef(): [unknown] | null { + const interop = iosInterop(); + if (!interop?.Reference) return null; + return [new interop.Reference()]; +} + +function checkErrorRef(errorRef: [unknown] | null, context: string): void { + if (!errorRef) return; + const val = errorRef[0]; + if (!val) return; + const errObj = val as { localizedDescription?: string }; + const msg = errObj.localizedDescription ?? String(val); + throw new WasmEdgeError( + `${context}: ${String(msg).replace(/^[\w.]*NSCWasmEdgeException:\s*/, '')}`, + ); +} + +function rethrow(error: unknown, context: string): never { + if (error instanceof WasmEdgeError) throw error; + const raw = error instanceof Error ? error.message : String(error); + throw new WasmEdgeError(`${context}: ${raw.replace(/^[\w.]*NSCWasmEdgeException:\s*/, '')}`); +} + +function withErrorRef(context: string, call: (errorArgs: [unknown]) => T): T { + const errorRef = newErrorRef(); + try { + const result = call(errorRef!); + checkErrorRef(errorRef, context); + return result; + } catch (error) { + rethrow(error, context); + } +} + +class IosFunction implements NativeFunctionAdapter { + constructor(private readonly fn: IosWasmEdgeFunctionProxy) {} + name(): string { + return String(this.fn.name); + } + paramTypes(): WasmValueType[] { + return nsArrayToJs(this.fn.paramTypes).map(String) as WasmValueType[]; + } + returnTypes(): WasmValueType[] { + return nsArrayToJs(this.fn.returnTypes).map(String) as WasmValueType[]; + } + call(args: WireValue[]): WireValue[] { + const ctx = `call ${this.name()}`; + const result = withErrorRef(ctx, (err) => this.fn.callWithArgumentsError(args, err)); + if (result == null) throw new WasmEdgeError(`${ctx}: returned null`); + return nsArrayToJs(result) as WireValue[]; + } +} + +function makeIosHostCallback(cb: WireHostCallback): unknown { + const Base = ( + globalThis as unknown as { + NSCWasmEdgeHostCallback: { + extend(config: { invoke(args: unknown[]): unknown }): new () => unknown; + }; + } + ).NSCWasmEdgeHostCallback; + if (!Base) throw new WasmEdgeError('NSCWasmEdgeHostCallback not available'); + const Subclass = Base.extend({ + invoke(nativeArgs: unknown): unknown { + return toNsArray(cb(nsArrayToJs(nativeArgs) as WireValue[])); + }, + }); + return new Subclass(); +} + +class IosModule implements NativeModuleAdapter { + constructor(private readonly module: IosWasmEdgeModuleProxy) {} + name(): string { + return String(this.module.name); + } + linkHostFunction(mod: string, name: string, signature: string, cb: WireHostCallback): void { + withErrorRef(`linkHostFunction ${mod}.${name}`, (err) => + this.module.linkHostFunctionModuleNameNameSignatureCallbackError( + mod, + name, + signature, + makeIosHostCallback(cb), + err, + ), + ); + } + getGlobal(name: string): WireValue { + return withErrorRef(`getGlobal ${name}`, (err) => + this.module.getGlobalNameError(name, err), + ) as WireValue; + } + setGlobal(name: string, value: WireValue): void { + withErrorRef(`setGlobal ${name}`, (err) => + this.module.setGlobalNameValueError(name, value, err), + ); + } +} + +export class IosRuntime implements NativeRuntimeAdapter { + private readonly runtime: IosWasmEdgeRuntimeProxy; + constructor(stackSizeInBytes: number) { + this.runtime = new ( + globalThis as unknown as { + NSCWasmEdgeRuntime: new (stackSize: number) => IosWasmEdgeRuntimeProxy; + } + ).NSCWasmEdgeRuntime(stackSizeInBytes); + } + loadModuleFromBytes(bytes: Uint8Array): NativeModuleAdapter { + const data = ( + globalThis as unknown as { + NSData: { dataWithBytesLength(buffer: ArrayBuffer, length: number): unknown }; + } + ).NSData.dataWithBytesLength(bytes.buffer as ArrayBuffer, bytes.length); + return new IosModule( + withErrorRef('loadModule', (err) => this.runtime.loadModuleBytesError(data, err)), + ); + } + loadModuleFromFile(path: string): NativeModuleAdapter { + return new IosModule( + withErrorRef('loadModule', (err) => this.runtime.loadModuleFileError(path, err)), + ); + } + findFunction(name: string): NativeFunctionAdapter { + return new IosFunction( + withErrorRef('findFunction', (err) => this.runtime.findFunctionError(name, err)), + ); + } + memorySize(): number { + return Number(this.runtime.memorySize()); + } + readMemory(offset: number, length: number): Uint8Array { + const data = withErrorRef('readMemory', (err) => + this.runtime.readMemoryAtOffsetLengthError(offset, length, err), + ); + if (!data) throw new WasmEdgeError('readMemory: returned null'); + return new Uint8Array(iosInterop()!.bufferFromData(data)); + } + writeMemory(offset: number, bytes: Uint8Array): void { + withErrorRef('writeMemory', (err) => + this.runtime.writeMemoryAtOffsetDataError(offset, bytes, err), + ); + } + dispose(): void { + /* iOS runtime is managed by the NativeScript runtime */ + } +} diff --git a/packages/ns-wasm-edge/src/lib/wasmedge.spec.ts b/packages/ns-wasm-edge/src/lib/wasmedge.spec.ts new file mode 100644 index 0000000..5be8083 --- /dev/null +++ b/packages/ns-wasm-edge/src/lib/wasmedge.spec.ts @@ -0,0 +1,27 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { WasmEdgeError } from './wire.js'; +import { WasmEdgeRuntime } from './wasmedge.js'; + +const g = globalThis as any; +afterEach(() => { delete g.NSCWasmEdgeRuntime; delete g.NSCWasmEdgeHostCallback; delete g.NSMutableArray; delete g.NSData; delete g.interop; }); + +function installIosFake() { + g.interop = { + Reference: class { value: any = null; }, + bufferFromData: (data: any) => data.bytes, + }; + g.NSMutableArray = class { private items: any[] = []; static alloc() { return { init() { return new g.NSMutableArray(); } }; } addObject(v: any) { this.items.push(v); } get count() { return 1; } objectAtIndex() { return this.items[0]; } }; + g.NSData = class { private data: Uint8Array; constructor(data: Uint8Array) { this.data = data; } static dataWithBytesLength(bytes: Uint8Array) { return new g.NSData(bytes); } get bytes() { return this.data.buffer; } get length() { return this.data.length; } }; + g.NSCWasmEdgeHostCallback = class { static extend(c: { invoke(args: any[]): any[] }) { return class { static new() { const i = Object.create(this.prototype); (i as any).invoke = c.invoke; return i; } }; } }; + const mem = new Uint8Array(64 * 1024); + g.NSCWasmEdgeRuntime = class { private _m = mem; constructor() {} static wasmedgeVersion() { return '0.1.0'; } loadModuleBytesError(d: any) { return { bytes: d.bytes }; } loadModuleFileError() { return {}; } findFunctionError(n: string) { return { name: n }; } memorySize() { return this._m.length; } readMemoryAtOffsetLengthError(o: number, len: number) { return new g.NSData(this._m.slice(o, o + len)); } writeMemoryAtOffsetDataError(o: number, d: any) { this._m.set(new Uint8Array(d.bytes ?? d.buffer), o); } }; +} + +describe('WasmEdgeRuntime (iOS fake)', () => { + beforeEach(() => { installIosFake(); }); + it('reports version', () => { const r = new WasmEdgeRuntime(); expect(WasmEdgeRuntime.version()).toBe('0.1.0'); r.dispose(); }); + it('loads module from bytes', () => { const r = new WasmEdgeRuntime(); const m = r.loadModule(new Uint8Array([0, 0x61, 0x73, 0x6d, 1, 0, 0, 0])); expect(m).toBeDefined(); r.dispose(); }); + it('finds function', () => { const r = new WasmEdgeRuntime(); expect(r.findFunction('t').name).toBe('t'); r.dispose(); }); + it('reads/writes memory', () => { const r = new WasmEdgeRuntime(); r.writeMemory(0, new Uint8Array([1, 2, 3])); expect(r.readMemory(0, 3)).toEqual(new Uint8Array([1, 2, 3])); expect(r.memorySize).toBe(64 * 1024); r.dispose(); }); + it('throws when native missing', () => { delete g.NSCWasmEdgeRuntime; expect(() => new WasmEdgeRuntime()).toThrow(WasmEdgeError); }); +}); diff --git a/packages/ns-wasm-edge/src/lib/wasmedge.ts b/packages/ns-wasm-edge/src/lib/wasmedge.ts new file mode 100644 index 0000000..4e85b3b --- /dev/null +++ b/packages/ns-wasm-edge/src/lib/wasmedge.ts @@ -0,0 +1,81 @@ +// ns-wasm-edge — WasmEdge (https://github.com/WasmEdge/WasmEdge) +// high-performance WebAssembly runtime plugin for NativeScript. +import type { WasmValue } from '@cross-code/ns-wasm-core'; +import { + WasmRuntime, + WasmModule, + WasmFunction, + type NativeRuntimeAdapter, +} from '@cross-code/ns-wasm-core'; +import { WasmEdgeError } from './wire.js'; +import { IosRuntime } from './wasmedge-ios.js'; +import { AndroidRuntime } from './wasmedge-android.js'; +import type { NativeScriptOrg } from './native-proxy.js'; + +export { + type WireHostCallback, + type NativeFunctionAdapter, + type NativeModuleAdapter, + type NativeRuntimeAdapter, +} from '@cross-code/ns-wasm-core'; + +export type WasmEdgeHostFunction = (...args: WasmValue[]) => WasmValue | WasmValue[] | void; +export type WasmEdgeModuleSource = string | ArrayBuffer | Uint8Array | number[]; +export interface WasmEdgeImports { + [module: string]: { [name: string]: { signature: string; fn: WasmEdgeHostFunction } }; +} +export interface WasmEdgeRuntimeOptions { + stackSizeInBytes?: number; +} + +interface GlobalThisWithWasmEdge { + org?: NativeScriptOrg['org']; + NSCWasmEdgeRuntime?: unknown; +} + +function createAdapter(stackSizeInBytes: number): NativeRuntimeAdapter { + const g = globalThis as unknown as GlobalThisWithWasmEdge; + if (typeof g.NSCWasmEdgeRuntime !== 'undefined' && g.NSCWasmEdgeRuntime !== null) + return new IosRuntime(stackSizeInBytes); + if (g.org?.nativescript?.wasmedge?.NSCWasmEdgeRuntime) + return new AndroidRuntime(stackSizeInBytes); + throw new WasmEdgeError( + 'ns-wasm-edge native runtime not found — is the plugin installed and the app rebuilt?', + ); +} + +function wasmEdgeVersionNative(): string { + const g = globalThis as unknown as GlobalThisWithWasmEdge; + if (typeof g.NSCWasmEdgeRuntime !== 'undefined' && g.NSCWasmEdgeRuntime !== null) + return String( + (g.NSCWasmEdgeRuntime as { wasmedgeVersion(): string }).wasmedgeVersion(), + ); + if (g.org?.nativescript?.wasmedge?.NSCWasmEdgeRuntime) + return String( + ( + g.org.nativescript.wasmedge.NSCWasmEdgeRuntime as { wasmedgeVersion(): string } + ).wasmedgeVersion(), + ); + throw new WasmEdgeError('ns-wasm-edge native runtime not found'); +} + +export class WasmEdgeFunction extends WasmFunction {} +export class WasmEdgeModule extends WasmModule {} + +export class WasmEdgeRuntime extends WasmRuntime { + constructor(options?: WasmEdgeRuntimeOptions) { + super(createAdapter(options?.stackSizeInBytes ?? 64 * 1024), { + moduleCtor: WasmEdgeModule, + functionCtor: WasmEdgeFunction, + }); + } + static version(): string { + return wasmEdgeVersionNative(); + } + override loadModule(source: WasmEdgeModuleSource, imports?: WasmEdgeImports): WasmEdgeModule { + return super.loadModule(source, imports) as unknown as WasmEdgeModule; + } + override findFunction(name: string): WasmEdgeFunction { + return super.findFunction(name) as unknown as WasmEdgeFunction; + } +} diff --git a/packages/ns-wasm-edge/src/lib/wire.ts b/packages/ns-wasm-edge/src/lib/wire.ts new file mode 100644 index 0000000..b367d1b --- /dev/null +++ b/packages/ns-wasm-edge/src/lib/wire.ts @@ -0,0 +1,5 @@ +export { WasmError as WasmEdgeError } from '@cross-code/ns-wasm-core'; +export { WasmError } from '@cross-code/ns-wasm-core'; +export { hostResultToWire, fromWireAll, parseSignature, toWire, fromWire, unwrapResults, + type WasmArg, type WasmValue, type WasmValueType, type WireValue, type ParsedSignature, +} from '@cross-code/ns-wasm-core'; diff --git a/packages/ns-wasm-edge/tsconfig.json b/packages/ns-wasm-edge/tsconfig.json new file mode 100644 index 0000000..2ef9b2f --- /dev/null +++ b/packages/ns-wasm-edge/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [{ "path": "./tsconfig.lib.json" }, { "path": "./tsconfig.spec.json" }] +} diff --git a/packages/ns-wasm-edge/tsconfig.lib.json b/packages/ns-wasm-edge/tsconfig.lib.json new file mode 100644 index 0000000..940c1b5 --- /dev/null +++ b/packages/ns-wasm-edge/tsconfig.lib.json @@ -0,0 +1,31 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "emitDeclarationOnly": false, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "references": [ + { + "path": "../ns-wasm-core/tsconfig.lib.json" + } + ], + "exclude": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx" + ] +} diff --git a/packages/ns-wasm-edge/tsconfig.spec.json b/packages/ns-wasm-edge/tsconfig.spec.json new file mode 100644 index 0000000..943d0f2 --- /dev/null +++ b/packages/ns-wasm-edge/tsconfig.spec.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.spec.tsbuildinfo", + "emitDeclarationOnly": false, + "types": ["vitest/globals", "node"] + }, + "include": [ + "vite.config.ts","vite.config.mts","vitest.config.ts","vitest.config.mts", + "src/**/*.test.ts","src/**/*.spec.ts","src/**/*.test.tsx","src/**/*.spec.tsx", + "src/**/*.test.js","src/**/*.spec.js","src/**/*.test.jsx","src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "references": [{ "path": "./tsconfig.lib.json" }] +} diff --git a/packages/ns-wasm-edge/vitest.config.mts b/packages/ns-wasm-edge/vitest.config.mts new file mode 100644 index 0000000..da3dca7 --- /dev/null +++ b/packages/ns-wasm-edge/vitest.config.mts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vitest/config'; +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/ns-wasm-edge', + test: { + name: 'ns-wasm-edge', + watch: false, globals: true, environment: 'node', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + provider: 'v8' as const, + reportsDirectory: './test-output/vitest/coverage', + include: ['src/**/*.ts'], + exclude: ['src/**/*.spec.ts', 'src/**/*.test.ts', 'src/**/*.d.ts'], + }, + }, +})); diff --git a/packages/ns-wasm-kit-runtime/src/lib/wasmkit-ios.ts b/packages/ns-wasm-kit-runtime/src/lib/wasmkit-ios.ts index fb2d920..21d3bed 100644 --- a/packages/ns-wasm-kit-runtime/src/lib/wasmkit-ios.ts +++ b/packages/ns-wasm-kit-runtime/src/lib/wasmkit-ios.ts @@ -140,10 +140,11 @@ export class IosRuntime implements NativeRuntimeAdapter { } memorySize(): number { return Number(this.runtime.memorySize()); } readMemory(offset: number, length: number): Uint8Array { - const buffer = withErrorRef('readMemory', (err) => + const data = withErrorRef('readMemory', (err) => this.runtime.readMemoryAtOffsetLengthError(offset, length, ...err), ); - return new Uint8Array(buffer); + if (!data) throw new WasmKitError('readMemory: returned null'); + return new Uint8Array(iosInterop()?.bufferFromData(data)); } writeMemory(offset: number, bytes: Uint8Array): void { withErrorRef('writeMemory', (err) => diff --git a/packages/ns-wasm-kit-runtime/src/lib/wasmkit.spec.ts b/packages/ns-wasm-kit-runtime/src/lib/wasmkit.spec.ts index dc7a399..6e6568e 100644 --- a/packages/ns-wasm-kit-runtime/src/lib/wasmkit.spec.ts +++ b/packages/ns-wasm-kit-runtime/src/lib/wasmkit.spec.ts @@ -32,6 +32,7 @@ function installIosFake() { Reference: class { value: any = null; }, + bufferFromData: (data: any) => data.bytes, }; g.NSMutableArray = class { diff --git a/packages/ns-wasm-kit-runtime/tsconfig.lib.json b/packages/ns-wasm-kit-runtime/tsconfig.lib.json index eff080e..940c1b5 100644 --- a/packages/ns-wasm-kit-runtime/tsconfig.lib.json +++ b/packages/ns-wasm-kit-runtime/tsconfig.lib.json @@ -1,27 +1,31 @@ { - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", - "emitDeclarationOnly": false, - "forceConsistentCasingInFileNames": true, - "types": ["node"] - }, - "include": ["src/**/*.ts"], - "references": [], - "exclude": [ - "vite.config.ts", - "vite.config.mts", - "vitest.config.ts", - "vitest.config.mts", - "src/**/*.test.ts", - "src/**/*.spec.ts", - "src/**/*.test.tsx", - "src/**/*.spec.tsx", - "src/**/*.test.js", - "src/**/*.spec.js", - "src/**/*.test.jsx", - "src/**/*.spec.jsx" - ] + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "emitDeclarationOnly": false, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "references": [ + { + "path": "../ns-wasm-core/tsconfig.lib.json" + } + ], + "exclude": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx" + ] } diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-arm64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/DWARF/NSCWasm3 b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-arm64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/DWARF/NSCWasm3 deleted file mode 100644 index c7bdf19..0000000 Binary files a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-arm64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/DWARF/NSCWasm3 and /dev/null differ diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-arm64-simulator/NSCWasm3.framework.dSYM/Contents/Info.plist b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Info.plist similarity index 100% rename from packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-arm64-simulator/NSCWasm3.framework.dSYM/Contents/Info.plist rename to packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Info.plist diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/DWARF/NSCWasm3 b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/DWARF/NSCWasm3 similarity index 50% rename from packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/DWARF/NSCWasm3 rename to packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/DWARF/NSCWasm3 index 338c9d5..0ac50e1 100644 Binary files a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/DWARF/NSCWasm3 and b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/DWARF/NSCWasm3 differ diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-arm64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/Relocations/aarch64/NSCWasm3.yml b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/Relocations/aarch64/NSCWasm3.yml similarity index 100% rename from packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-arm64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/Relocations/aarch64/NSCWasm3.yml rename to packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/Relocations/aarch64/NSCWasm3.yml diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/Relocations/x86_64/NSCWasm3.yml b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/Relocations/x86_64/NSCWasm3.yml similarity index 100% rename from packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/Relocations/x86_64/NSCWasm3.yml rename to packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Resources/Relocations/x86_64/NSCWasm3.yml diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Info.plist b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Info.plist deleted file mode 100644 index 805fd3b..0000000 --- a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework.dSYMs/ios-x86_64-simulator/NSCWasm3.framework.dSYM/Contents/Info.plist +++ /dev/null @@ -1,20 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleIdentifier - com.apple.xcode.dsym.org.nativescript.NSCWasm3 - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - dSYM - CFBundleSignature - ???? - CFBundleShortVersionString - 0.1.0 - CFBundleVersion - 1 - - diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/Info.plist b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/Info.plist index 5551b4b..971a7d7 100644 --- a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/Info.plist +++ b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/Info.plist @@ -16,23 +16,11 @@ LibraryIdentifier - ios-arm64-simulator + ios-arm64_x86_64-simulator LibraryPath NSCWasm3.framework SupportedArchitectures - arm64 - SupportedPlatform - ios - SupportedPlatformVariant - simulator - - - LibraryIdentifier - ios-x86_64-simulator - LibraryPath - NSCWasm3.framework - SupportedArchitectures - x86_64 + arm64x86_64 SupportedPlatform ios SupportedPlatformVariant diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64-simulator/NSCWasm3.framework/NSCWasm3 b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64-simulator/NSCWasm3.framework/NSCWasm3 deleted file mode 100755 index 73ecae7..0000000 Binary files a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64-simulator/NSCWasm3.framework/NSCWasm3 and /dev/null differ diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64-simulator/NSCWasm3.framework/Headers/NSCWasm3.h b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64_x86_64-simulator/NSCWasm3.framework/Headers/NSCWasm3.h similarity index 100% rename from packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64-simulator/NSCWasm3.framework/Headers/NSCWasm3.h rename to packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64_x86_64-simulator/NSCWasm3.framework/Headers/NSCWasm3.h diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64-simulator/NSCWasm3.framework/Headers/NSCWasm3Classes.h b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64_x86_64-simulator/NSCWasm3.framework/Headers/NSCWasm3Classes.h similarity index 100% rename from packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64-simulator/NSCWasm3.framework/Headers/NSCWasm3Classes.h rename to packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64_x86_64-simulator/NSCWasm3.framework/Headers/NSCWasm3Classes.h diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64-simulator/NSCWasm3.framework/Info.plist b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64_x86_64-simulator/NSCWasm3.framework/Info.plist similarity index 100% rename from packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64-simulator/NSCWasm3.framework/Info.plist rename to packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64_x86_64-simulator/NSCWasm3.framework/Info.plist diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64-simulator/NSCWasm3.framework/Modules/module.modulemap b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64_x86_64-simulator/NSCWasm3.framework/Modules/module.modulemap similarity index 100% rename from packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64-simulator/NSCWasm3.framework/Modules/module.modulemap rename to packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64_x86_64-simulator/NSCWasm3.framework/Modules/module.modulemap diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64_x86_64-simulator/NSCWasm3.framework/NSCWasm3 b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64_x86_64-simulator/NSCWasm3.framework/NSCWasm3 new file mode 100755 index 0000000..deec5be Binary files /dev/null and b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-arm64_x86_64-simulator/NSCWasm3.framework/NSCWasm3 differ diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-x86_64-simulator/NSCWasm3.framework/Headers/NSCWasm3.h b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-x86_64-simulator/NSCWasm3.framework/Headers/NSCWasm3.h deleted file mode 100644 index d2eabb2..0000000 --- a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-x86_64-simulator/NSCWasm3.framework/Headers/NSCWasm3.h +++ /dev/null @@ -1,10 +0,0 @@ -// NSCWasm3.h -// -// Umbrella header for the NSCWasm3 framework. The implementation is Swift -// (@objc classes in NSCWasm3.swift); these headers mirror the @objc surface so -// the NativeScript iOS metadata generator can see it. -// -// NOTE: the class declarations live in NSCWasm3Classes.h, NOT here — the -// metadata generator only records classes declared in subheaders imported by -// the umbrella header; classes declared inline in the umbrella are skipped. -#import "NSCWasm3Classes.h" diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-x86_64-simulator/NSCWasm3.framework/Headers/NSCWasm3Classes.h b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-x86_64-simulator/NSCWasm3.framework/Headers/NSCWasm3Classes.h deleted file mode 100644 index 9b35810..0000000 --- a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-x86_64-simulator/NSCWasm3.framework/Headers/NSCWasm3Classes.h +++ /dev/null @@ -1,62 +0,0 @@ -// NSCWasm3.h -// -// Public header for the NSCWasm3 framework. The implementation is Swift -// (@objc classes in NSCWasm3.swift); this header exists so the NativeScript -// iOS metadata generator (a clang-based parser) can see the class surface — -// the exact class names and selectors below mirror the Swift @objc -// declarations, including the trailing `error:` out-parameter convention -// (JS: `loadModuleError(bytes)` etc.). -#import - -@class NSCWasm3Module; -@class NSCWasm3Function; - -NS_ASSUME_NONNULL_BEGIN - -/// Host import callback: subclasses override `invoke(_:)`. -@interface NSCWasm3HostCallback : NSObject -- (NSArray * _Nullable)invoke:(NSArray *)args; -@end - -/// A wasm3 runtime: owns module instances, exec environments and the -/// registered host callbacks. -@interface NSCWasm3Runtime : NSObject -- (instancetype)initWithStackSize:(NSUInteger)stackSizeInBytes; -- (instancetype)init; -+ (NSString *)wasm3Version; -- (NSCWasm3Module * _Nullable)loadModule:(NSData *)bytes error:(NSError **)error; -- (NSCWasm3Module * _Nullable)loadModuleFromFile:(NSString *)path error:(NSError **)error; -- (NSCWasm3Function * _Nullable)findFunction:(NSString *)name error:(NSError **)error; -@property (nonatomic, readonly) NSUInteger memorySize; -- (NSData * _Nullable)readMemoryAtOffset:(NSUInteger)offset - length:(NSUInteger)length - error:(NSError **)error; -- (BOOL)writeMemoryAtOffset:(NSUInteger)offset - data:(NSData *)data - error:(NSError **)error; -@end - -/// A loaded (and lazily instantiated) WebAssembly module. -@interface NSCWasm3Module : NSObject -@property (nonatomic, weak, nullable) NSCWasm3Runtime *runtime; -@property (nonatomic, readonly) NSString *name; -- (NSCWasm3Function * _Nullable)findFunction:(NSString *)name error:(NSError **)error; -- (BOOL)linkHostFunction:(NSString *)moduleName - name:(NSString *)name - signature:(NSString *)signature - callback:(NSCWasm3HostCallback *)callback - error:(NSError **)error; -- (id _Nullable)getGlobal:(NSString *)name error:(NSError **)error; -- (BOOL)setGlobal:(NSString *)name value:(id)value error:(NSError **)error; -@end - -/// A callable function of a loaded module. -@interface NSCWasm3Function : NSObject -@property (nonatomic, readonly) NSCWasm3Runtime *runtime; -@property (nonatomic, readonly) NSString *name; -@property (nonatomic, readonly) NSArray *paramTypes; -@property (nonatomic, readonly) NSArray *returnTypes; -- (NSArray * _Nullable)callWithArguments:(NSArray *)arguments error:(NSError **)error; -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-x86_64-simulator/NSCWasm3.framework/Info.plist b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-x86_64-simulator/NSCWasm3.framework/Info.plist deleted file mode 100644 index 7cf418d..0000000 --- a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-x86_64-simulator/NSCWasm3.framework/Info.plist +++ /dev/null @@ -1,18 +0,0 @@ - - - - - CFBundleExecutable - NSCWasm3 - CFBundleIdentifier - org.nativescript.NSCWasm3 - CFBundleName - NSCWasm3 - CFBundlePackageType - FMWK - CFBundleShortVersionString - 0.1.0 - CFBundleVersion - 1 - - diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-x86_64-simulator/NSCWasm3.framework/Modules/module.modulemap b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-x86_64-simulator/NSCWasm3.framework/Modules/module.modulemap deleted file mode 100644 index 9aa98c6..0000000 --- a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-x86_64-simulator/NSCWasm3.framework/Modules/module.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module NSCWasm3 { - umbrella header "NSCWasm3.h" - - export * - module * { export * } -} diff --git a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-x86_64-simulator/NSCWasm3.framework/NSCWasm3 b/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-x86_64-simulator/NSCWasm3.framework/NSCWasm3 deleted file mode 100755 index 75d44d3..0000000 Binary files a/packages/ns-wasm3/platforms/ios/NSCWasm3.xcframework/ios-x86_64-simulator/NSCWasm3.framework/NSCWasm3 and /dev/null differ diff --git a/packages/ns-wasm3/src/lib/wasm3.spec.ts b/packages/ns-wasm3/src/lib/wasm3.spec.ts index be9be70..9929b1f 100644 --- a/packages/ns-wasm3/src/lib/wasm3.spec.ts +++ b/packages/ns-wasm3/src/lib/wasm3.spec.ts @@ -350,9 +350,9 @@ describe('Wasm3Runtime on Android', () => { const module = runtime.loadModule([1]); const received: unknown[] = []; - module.linkHostFunction('env', 'host_add', 'i(ii)', (a, b) => { - received.push(a, b); - return (a as number) + (b as number); + module.linkHostFunction('env', 'host_add', 'i(ii)', (args) => { + received.push(...args); + return [(args[0] as number) + (args[1] as number)]; }); expect(runtime.call('call_host_add', 3, 4)).toBe(7); expect(received).toEqual([3, 4]); @@ -422,9 +422,9 @@ describe('Wasm3Runtime on Android', () => { // toJavaWireValue wraps them as Double; normalizeAndroidValue must unbox // them before the JS host function sees them. const received: unknown[] = []; - module.linkHostFunction('env', 'host_add', 'i(ii)', (a, b) => { - received.push(a, b); - return (a as number) + (b as number); + module.linkHostFunction('env', 'host_add', 'i(ii)', (args) => { + received.push(...args); + return [(args[0] as number) + (args[1] as number)]; }); expect(runtime.call('call_host_add', 3, 4)).toBe(7); @@ -467,7 +467,9 @@ describe('Wasm3Runtime on iOS', () => { const runtime = new Wasm3Runtime(); const module = runtime.loadModule(new Uint8Array([0, 97, 115, 109])); - module.linkHostFunction('env', 'host_add', 'i(ii)', (a, b) => (a as number) + (b as number)); + module.linkHostFunction('env', 'host_add', 'i(ii)', (args) => [ + (args[0] as number) + (args[1] as number), + ]); expect(runtime.call('call_host_add', 20, 22)).toBe(42); }); diff --git a/packages/ns-wasm3/tsconfig.lib.json b/packages/ns-wasm3/tsconfig.lib.json index eff080e..940c1b5 100644 --- a/packages/ns-wasm3/tsconfig.lib.json +++ b/packages/ns-wasm3/tsconfig.lib.json @@ -1,27 +1,31 @@ { - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", - "emitDeclarationOnly": false, - "forceConsistentCasingInFileNames": true, - "types": ["node"] - }, - "include": ["src/**/*.ts"], - "references": [], - "exclude": [ - "vite.config.ts", - "vite.config.mts", - "vitest.config.ts", - "vitest.config.mts", - "src/**/*.test.ts", - "src/**/*.spec.ts", - "src/**/*.test.tsx", - "src/**/*.spec.tsx", - "src/**/*.test.js", - "src/**/*.spec.js", - "src/**/*.test.jsx", - "src/**/*.spec.jsx" - ] + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "emitDeclarationOnly": false, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "references": [ + { + "path": "../ns-wasm-core/tsconfig.lib.json" + } + ], + "exclude": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx" + ] } diff --git a/packages/ns-wry/AGENTS.md b/packages/ns-wry/AGENTS.md index 881cb07..fffb932 100644 --- a/packages/ns-wry/AGENTS.md +++ b/packages/ns-wry/AGENTS.md @@ -55,11 +55,8 @@ npm exec nx run-many -t build test -p ns-wry # iOS XCTests (macOS only) npm run test.ios -# Android JVM host tests (no emulator) -npm run test.android - -# Android: cross-compile all ABIs via cargo-ndk, refresh the .aar -npm run build.android +# Android support has not been scaffolded yet, so this package has no Android +# build or host-test target. ``` ## Test app diff --git a/packages/ns-wry/README.md b/packages/ns-wry/README.md index d10073e..e480141 100644 --- a/packages/ns-wry/README.md +++ b/packages/ns-wry/README.md @@ -29,11 +29,8 @@ npm exec nx run-many -t build test -p ns-wry # iOS XCTests (macOS only) npm run test.ios -# Android JVM host tests (no emulator) -npm run test.android - -# Android: cross-compile all ABIs via cargo-ndk -npm run build.android +# Android support is not scaffolded yet; no Android build or host-test target +# is available for this package. ``` ## Architecture @@ -44,5 +41,5 @@ src/vendors/wry-rust/ Rust workspace (cargo) wry-ffi/ UniFFI IDL → Swift / Kotlin bindings wry-jni/ JNI glue for Android platforms/ios/NSCWry/ Swift Package (no CocoaPods) -platforms/android/ Gradle project + prebuilt .aar +platforms/android/ NativeScript integration placeholder ``` diff --git a/packages/ns-wry/package.json b/packages/ns-wry/package.json index 31bc08a..b752a84 100644 --- a/packages/ns-wry/package.json +++ b/packages/ns-wry/package.json @@ -37,8 +37,6 @@ "test.ios": "cd platforms/ios/NSCWry && swift test --disable-sandbox", "lint.ios": "swiftlint lint --config ../../.swiftlint.yml platforms/ios/NSCWry/Sources", "periphery.ios": "cd platforms/ios/NSCWry && periphery scan --config $(git rev-parse --show-toplevel)/.periphery.yml --disable-update-check", - "test.android": "cd platforms/android/nsc-wry-android && ./gradlew :hosttest:test", - "build.android": "cd platforms/android/nsc-wry-android && ./gradlew :library:deployAar", "coverage": "vitest run --coverage", "build.xcframework": "bash tools/build-xcframework.sh" }, diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-arm64-simulator/NSCWry.framework.dSYM/Contents/Resources/DWARF/NSCWry b/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-arm64-simulator/NSCWry.framework.dSYM/Contents/Resources/DWARF/NSCWry deleted file mode 100644 index 68dffec..0000000 Binary files a/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-arm64-simulator/NSCWry.framework.dSYM/Contents/Resources/DWARF/NSCWry and /dev/null differ diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-arm64-simulator/NSCWry.framework.dSYM/Contents/Info.plist b/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWry.framework.dSYM/Contents/Info.plist similarity index 100% rename from packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-arm64-simulator/NSCWry.framework.dSYM/Contents/Info.plist rename to packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWry.framework.dSYM/Contents/Info.plist diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWry.framework.dSYM/Contents/Resources/DWARF/NSCWry b/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWry.framework.dSYM/Contents/Resources/DWARF/NSCWry new file mode 100644 index 0000000..6d05111 Binary files /dev/null and b/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWry.framework.dSYM/Contents/Resources/DWARF/NSCWry differ diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-arm64-simulator/NSCWry.framework.dSYM/Contents/Resources/Relocations/aarch64/NSCWry.yml b/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWry.framework.dSYM/Contents/Resources/Relocations/aarch64/NSCWry.yml similarity index 100% rename from packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-arm64-simulator/NSCWry.framework.dSYM/Contents/Resources/Relocations/aarch64/NSCWry.yml rename to packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWry.framework.dSYM/Contents/Resources/Relocations/aarch64/NSCWry.yml diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-x86_64-simulator/NSCWry.framework.dSYM/Contents/Resources/Relocations/x86_64/NSCWry.yml b/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWry.framework.dSYM/Contents/Resources/Relocations/x86_64/NSCWry.yml similarity index 100% rename from packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-x86_64-simulator/NSCWry.framework.dSYM/Contents/Resources/Relocations/x86_64/NSCWry.yml rename to packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-arm64_x86_64-simulator/NSCWry.framework.dSYM/Contents/Resources/Relocations/x86_64/NSCWry.yml diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-x86_64-simulator/NSCWry.framework.dSYM/Contents/Info.plist b/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-x86_64-simulator/NSCWry.framework.dSYM/Contents/Info.plist deleted file mode 100644 index cf4e15f..0000000 --- a/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-x86_64-simulator/NSCWry.framework.dSYM/Contents/Info.plist +++ /dev/null @@ -1,20 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleIdentifier - com.apple.xcode.dsym.org.nativescript.NSCWry - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - dSYM - CFBundleSignature - ???? - CFBundleShortVersionString - 0.1.0 - CFBundleVersion - 1 - - diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-x86_64-simulator/NSCWry.framework.dSYM/Contents/Resources/DWARF/NSCWry b/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-x86_64-simulator/NSCWry.framework.dSYM/Contents/Resources/DWARF/NSCWry deleted file mode 100644 index 79a7997..0000000 Binary files a/packages/ns-wry/platforms/ios/NSCWry.xcframework.dSYMs/ios-x86_64-simulator/NSCWry.framework.dSYM/Contents/Resources/DWARF/NSCWry and /dev/null differ diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework/Info.plist b/packages/ns-wry/platforms/ios/NSCWry.xcframework/Info.plist index f5cd9ce..49e5abe 100644 --- a/packages/ns-wry/platforms/ios/NSCWry.xcframework/Info.plist +++ b/packages/ns-wry/platforms/ios/NSCWry.xcframework/Info.plist @@ -16,23 +16,11 @@ LibraryIdentifier - ios-arm64-simulator + ios-arm64_x86_64-simulator LibraryPath NSCWry.framework SupportedArchitectures - arm64 - SupportedPlatform - ios - SupportedPlatformVariant - simulator - - - LibraryIdentifier - ios-x86_64-simulator - LibraryPath - NSCWry.framework - SupportedArchitectures - x86_64 + arm64x86_64 SupportedPlatform ios SupportedPlatformVariant diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64-simulator/NSCWry.framework/Headers/NSCWry.h b/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64_x86_64-simulator/NSCWry.framework/Headers/NSCWry.h similarity index 100% rename from packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64-simulator/NSCWry.framework/Headers/NSCWry.h rename to packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64_x86_64-simulator/NSCWry.framework/Headers/NSCWry.h diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64-simulator/NSCWry.framework/Headers/NSCWryClasses.h b/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64_x86_64-simulator/NSCWry.framework/Headers/NSCWryClasses.h similarity index 100% rename from packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64-simulator/NSCWry.framework/Headers/NSCWryClasses.h rename to packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64_x86_64-simulator/NSCWry.framework/Headers/NSCWryClasses.h diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64-simulator/NSCWry.framework/Info.plist b/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64_x86_64-simulator/NSCWry.framework/Info.plist similarity index 100% rename from packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64-simulator/NSCWry.framework/Info.plist rename to packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64_x86_64-simulator/NSCWry.framework/Info.plist diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64-simulator/NSCWry.framework/Modules/module.modulemap b/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64_x86_64-simulator/NSCWry.framework/Modules/module.modulemap similarity index 100% rename from packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64-simulator/NSCWry.framework/Modules/module.modulemap rename to packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64_x86_64-simulator/NSCWry.framework/Modules/module.modulemap diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64-simulator/NSCWry.framework/NSCWry b/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64_x86_64-simulator/NSCWry.framework/NSCWry similarity index 60% rename from packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64-simulator/NSCWry.framework/NSCWry rename to packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64_x86_64-simulator/NSCWry.framework/NSCWry index 49e1d8d..d1fdfaf 100755 Binary files a/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64-simulator/NSCWry.framework/NSCWry and b/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-arm64_x86_64-simulator/NSCWry.framework/NSCWry differ diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-x86_64-simulator/NSCWry.framework/Headers/NSCWry.h b/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-x86_64-simulator/NSCWry.framework/Headers/NSCWry.h deleted file mode 100644 index c1736b7..0000000 --- a/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-x86_64-simulator/NSCWry.framework/Headers/NSCWry.h +++ /dev/null @@ -1,10 +0,0 @@ -// NSCWry.h -// -// Umbrella header for the NSCWry framework. The implementation is Swift -// (@objc classes in NSCWry.swift); these headers mirror the @objc surface so -// the NativeScript iOS metadata generator can see it. -// -// NOTE: the class declarations live in NSCWryClasses.h, NOT here — the -// metadata generator only records classes declared in subheaders imported by -// the umbrella header; classes declared inline in the umbrella are skipped. -#import "NSCWryClasses.h" diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-x86_64-simulator/NSCWry.framework/Headers/NSCWryClasses.h b/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-x86_64-simulator/NSCWry.framework/Headers/NSCWryClasses.h deleted file mode 100644 index 735a03e..0000000 --- a/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-x86_64-simulator/NSCWry.framework/Headers/NSCWryClasses.h +++ /dev/null @@ -1,19 +0,0 @@ -// NSCWry.h -// -// Public header for the NSCWry framework. The implementation is Swift -// (@objc classes in NSCWry.swift); this header mirrors the @objc surface so -// the NativeScript iOS metadata generator can see it. -#import - -NS_ASSUME_NONNULL_BEGIN - -/// The wry engine runtime entry point. -@interface NSCWryRuntime : NSObject -- (instancetype)initWithStackSize:(NSUInteger)stackSizeInBytes; -- (instancetype)init; -+ (NSString *)wryVersion; -- (id _Nullable)callWithArgs:(NSArray *)args; -- (void)dispose; -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-x86_64-simulator/NSCWry.framework/Info.plist b/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-x86_64-simulator/NSCWry.framework/Info.plist deleted file mode 100644 index b9970ec..0000000 --- a/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-x86_64-simulator/NSCWry.framework/Info.plist +++ /dev/null @@ -1,18 +0,0 @@ - - - - - CFBundleExecutable - NSCWry - CFBundleIdentifier - org.nativescript.NSCWry - CFBundleName - NSCWry - CFBundlePackageType - FMWK - CFBundleShortVersionString - 0.1.0 - CFBundleVersion - 1 - - diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-x86_64-simulator/NSCWry.framework/Modules/module.modulemap b/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-x86_64-simulator/NSCWry.framework/Modules/module.modulemap deleted file mode 100644 index 84df099..0000000 --- a/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-x86_64-simulator/NSCWry.framework/Modules/module.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module NSCWry { - umbrella header "NSCWry.h" - - export * - module * { export * } -} diff --git a/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-x86_64-simulator/NSCWry.framework/NSCWry b/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-x86_64-simulator/NSCWry.framework/NSCWry deleted file mode 100755 index 4880a6e..0000000 Binary files a/packages/ns-wry/platforms/ios/NSCWry.xcframework/ios-x86_64-simulator/NSCWry.framework/NSCWry and /dev/null differ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 607d99a..285dc91 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,12 @@ importers: nx: specifier: 23.1.0 version: 23.1.0(@swc-node/register@1.11.1(@swc/core@1.15.8(@swc/helpers@0.5.18))(@swc/types@0.1.28)(supports-color@7.2.0)(typescript@6.0.3))(@swc/core@1.15.8(@swc/helpers@0.5.18)) + oxfmt: + specifier: ^0.62.0 + version: 0.62.0 + oxlint: + specifier: ^1.77.0 + version: 1.77.0 prettier: specifier: ^3.8.1 version: 3.9.6 @@ -66,16 +72,61 @@ importers: specifier: ^8.8.5 version: 8.8.5 + packages/ns-endive: + dependencies: + '@cross-code/ns-wasm-core': + specifier: workspace:* + version: link:../ns-wasm-core + tslib: + specifier: ^2.3.0 + version: 2.8.1 + packages/ns-wamr: + dependencies: + '@cross-code/ns-wasm-core': + specifier: workspace:* + version: link:../ns-wasm-core devDependencies: '@nativescript/core': specifier: ^8.9.0 version: 8.9.9 + packages/ns-wasm-chicory: + dependencies: + '@cross-code/ns-wasm-core': + specifier: workspace:* + version: link:../ns-wasm-core + tslib: + specifier: ^2.3.0 + version: 2.8.1 + + packages/ns-wasm-core: {} + + packages/ns-wasm-edge: + dependencies: + '@cross-code/ns-wasm-core': + specifier: workspace:* + version: link:../ns-wasm-core + tslib: + specifier: ^2.3.0 + version: 2.8.1 + packages/ns-wasm-fixture: {} + packages/ns-wasm-kit-runtime: + dependencies: + '@cross-code/ns-wasm-core': + specifier: workspace:* + version: link:../ns-wasm-core + tslib: + specifier: ^2.3.0 + version: 2.8.1 + packages/ns-wasm3: dependencies: + '@cross-code/ns-wasm-core': + specifier: workspace:* + version: link:../ns-wasm-core tslib: specifier: ^2.3.0 version: 2.8.1 @@ -1240,6 +1291,250 @@ packages: cpu: [x64] os: [win32] + '@oxfmt/binding-android-arm-eabi@0.62.0': + resolution: {integrity: sha512-pdsv0C4gPjJ8H1+sd8u0BDx+yLACTL+rgeMIOL1ln4ihSnhw8CWXtYWgvcSkyTfgGBIzFKab+d8rx9Xl4en/Kw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.62.0': + resolution: {integrity: sha512-WC3YQ7uS/KtDrjmqwBviwFKe9qeoi+eXx8aX1z/ffG23Md75myjrJaQqTuJvdOLPoa4EYTjDWH0dHXfwulCVog==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.62.0': + resolution: {integrity: sha512-GM8Yf3LjjaR1I8PD0SfeoIlwhsh9GvSF+cQ8sf624Yxnjsyumn95aFzYfKJVefblfDIiOAnZ7QVm2sa21Er/0Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.62.0': + resolution: {integrity: sha512-d5THp7F8bCxLqNogEXDORRsQD6dosf3EyFtnXfBer6v+8tGdcWIjoDX9WaXrrF/26zOmL8qHpPTKCEvpBDmZkQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.62.0': + resolution: {integrity: sha512-1DnrtXGZooOZ0fHgAXZUaDQzBVh1CM2MNW4oBXyQ2aWKvCHjyljvT9fgBkOM0fEOb96X5eqtcfJ0YUVt9jj66g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.62.0': + resolution: {integrity: sha512-4pQDHOYRH+Huqe0StIaWyvk2CVl/aTaqSrbZpA3/pLS2xH24ME7lBgYprhQF2fRkHBzhGGGKliwxFsDdHwx59g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.62.0': + resolution: {integrity: sha512-X0jAaZJFMCVKhB6YyWVTQ/wN2DLsBcZKSMqTS76bF6riT+XZdtg2FPEdjDvdVbunO9cG+tWiVaEs4Zs38lxYog==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.62.0': + resolution: {integrity: sha512-682Z8T5s8T5ATArYtsejKvbIfd8LEAXyyDkKkoZVq8HND7Vx8TYLlrDjDSeYfodMeVwHOgkj13lJYR8cj6vUSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-arm64-musl@0.62.0': + resolution: {integrity: sha512-lk25fAl7KWaLWVJcW0CHEXB7QlQZtx5eDkjpaGMK0hzXTjUe0Wmlu8IKuFHoviSOcEJedRTs4VE/506VqGxGew==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-ppc64-gnu@0.62.0': + resolution: {integrity: sha512-SFyNqHQLwySceWNLhiSldx7wPXRAzP0L0WcW9GegP3uWrpZGJiZlQO85NbHAFPEfxR9PhZ9qSnZryEh7+v+4Gw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-gnu@0.62.0': + resolution: {integrity: sha512-KYj55C1ywJfHo6+aKDuEmUtVEdJALsC5GwayDGsI6FGz2GxFqNr/mA8nxVsNbJzm7sE5MRqTQ9ziImSzhYXysA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.62.0': + resolution: {integrity: sha512-BhZDNo5GOU5nC378RhD0/XpvaEBHsH3HLgJp8YZX3A0InC7oivzA63HsRmiXFLtLSHAstEVrDf6fbC7Rs8Jh/A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.62.0': + resolution: {integrity: sha512-UyAFmyHkgSgUJ/wOM4p3U8AC2yAFvRH5PNBs7TnK0fObTT/XSWcdr/lAzPSWaekHaZFaMeFZyk9n93Joq3J93A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-gnu@0.62.0': + resolution: {integrity: sha512-1iYMP0leytWazFubD/WnINJuIrzRPuoL1aWEJdlGezEzDbTxcd29R4r8IUzP2oWeKst5V02uMJgR2NILlPlG6w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-musl@0.62.0': + resolution: {integrity: sha512-4rA/URtJSTVNVAQz6Q8wf7SaRvOXVy+TizriT9hs/Y1XhLR/R+92uWKRQG8yFWRAIEBbFHJ6WevQcl/G9SXEfw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-openharmony-arm64@0.62.0': + resolution: {integrity: sha512-mSZuFHU2ar1KLUjXpI2QBQcJ1VsOB3mOCgQXuXCpKs19dgh4u+OaovNfrWDfiJb+ihJ2+f7YFcaO9bS2dlTCXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.62.0': + resolution: {integrity: sha512-OfwuhkcjDlqC4EgDojtiV9mzpLqeB9KqTOWPOjLEYBVdDCVSxqW3qzp/xcIxsbtI0UgGCnKvAqYKyY25kf5JZw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.62.0': + resolution: {integrity: sha512-P9uDDNFRzghO3X8QAzhkjKhK7JvtABsVn8UYtFX7uor12IAnwNt8nNIctvfWj1JkQU/kE+fmLRPiw7XlrIHsZw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.62.0': + resolution: {integrity: sha512-dlI5SY7XYQCiCBafntWagCR6HcAJB/NpsLtdlPx8x08+Osz8Ok1HHz1GZuusegCe/VoJ6pAnF5a4pd5OZAq7qQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.77.0': + resolution: {integrity: sha512-E06sKWS6PiI6HRxS1wyQg22HvApt01hI7fV+T3wUk3OSbaaP4a3hYGY/MIQDmASqCiRjBdpRQYkgMkqH82cWmQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.77.0': + resolution: {integrity: sha512-NvsKz0KZxTp9cYWPLf+FXaSZwB3oO3peAjtukpOMBgse2vhQSoIIVqeO1yR0lEo/UcdZIDL18uq+kL0LzQ0ytA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.77.0': + resolution: {integrity: sha512-bgjTn6nW4bQCFBvSvuHCpDD+sONvmpo4lGI4PxzMt1quBA+xYxhczk6RiCn3GZ9gY8uhaBbwhj9MdKGfu6T9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.77.0': + resolution: {integrity: sha512-aotaIttH1R6j1Rwhx0M0htgeZyGtVQqYNTVEYMN/UcgHPquGA6kmk9OyuDc3a2GKUQBC+3C3GVQCcrRPMYqAFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.77.0': + resolution: {integrity: sha512-nNx/wta7ksRAdYvq+l4AWjXkLxEXHALhENxjj2cYbQAIR4ybaA5L+hCbE63HOmft5czQ6ks+hb8vmEAnn7YGPg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.77.0': + resolution: {integrity: sha512-tMLLjM7xXtzXisVCzkOTXNCy9bZVId2wteNwjohlFDR/jY6WagpEDA1c1wu4xRc20Hojaxj+V6DSR7gbKxijWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.77.0': + resolution: {integrity: sha512-MiAFDFaqR0tmHTAyo0YDcZ5hyLREdYw/RQhc2R3cbT+8O3tB+zqPM2th9TTQ+Uo3jn/embS+DO+HyX9ztCPkOQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.77.0': + resolution: {integrity: sha512-/xqQ3B16i1T4cyt/9Mn+4CpzhUXoBXp7kVpIwzOXNFLj5JmK1bIjsbSnX296Gg8A/o7oDtKWikFgBx0SLwztkw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.77.0': + resolution: {integrity: sha512-LSbwuRKiNCenPDcbARqAZ5RfBy7gmj7vOvfJRLeCDU3gFtSxWbhv/+VTlaUqzUhNj1gFLHB8h7ALnxa/Az6z6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.77.0': + resolution: {integrity: sha512-QWdcH31mXEUe5Nq1s0CfCpceaKjIo9uZtwDjAuL681g1axf+5x8xrg/eXWaw//4NCxYZ4V4e5Hu5tvdR+pTBlg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.77.0': + resolution: {integrity: sha512-GnOfYgJxbcElOiPZaDFDl406ONddwvOWk2jvAAAEjwAl4GofNoHF+/HHUIBYa6bFCArlcGPi0XjC4cU1pkgF/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.77.0': + resolution: {integrity: sha512-AyEMTUCf0xY+hHF+IxqXFQIX0yQOIR8ykpY0lJNOw9xYqOzUX8dyZfRvlG0RfXwuQn2eonf/8NrMmDSZJjdqsA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.77.0': + resolution: {integrity: sha512-sPLzEcNvxd/oyVQ5oZo92CiHkFkpBeRop13E/P3TPY+hZfXHKCOWKI70TE2RYwMKFJDc20EMjH16L7NZICtKTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.77.0': + resolution: {integrity: sha512-1Oh2ssH2L7lwyvkdSqaMUfsGfwU2Wfvew+obBUYjRVqhpBcUpwnsPSEr1IzVi9XqkuY10geiLsNKecqaZC34Dw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.77.0': + resolution: {integrity: sha512-0j/2wRgNGO+Qj/M1uu/p57h/hFTTWWcfie0ufkbabeus2s5+/QqkCflnMOwLLN5m2GsNeWp4xdl4cPa4n7QCOQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.77.0': + resolution: {integrity: sha512-BJ/j54qS0usEnyDkLYURMj2iiD9h5Cyy+ppzeMSXBGRXaGRNWnj1Mw14NqWMR5E/PzdgB30OOCCzLzbRoduafw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.77.0': + resolution: {integrity: sha512-Yh8w+g2Lpx7StrvtYkoz9JJvXjB9wxgFChFNb85nrXm/wj/XTwGWS1hve9+900HL7llrntYB3YP+y32E3tRqzA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.77.0': + resolution: {integrity: sha512-zja5b7+6a7UsRFgAQSrnax5vrzliEyNPLCjfXONu/vTWswaIVZGFajJZptaeRvPE4LghtFdAzVFlexTm7MVTGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.77.0': + resolution: {integrity: sha512-+teyvPDZ2RjUvo+SuCqS/UhaJl1QtdW5fWT5NJTV61V5MIuIS90Db9LixmtEGvXixyttiK62P96MSu3UlpviBw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@phenomnomnominal/tsquery@6.2.0': resolution: {integrity: sha512-Vo9nkhfZxDB/sBiqIY3pjDC4mOSyure+AFlEW5hcy/tRE82MqCXjRN4InnVNMldinRt0dLYqg4HAU2XPq5e1LA==} peerDependencies: @@ -3682,6 +3977,32 @@ packages: oxc-resolver@11.24.2: resolution: {integrity: sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==} + oxfmt@0.62.0: + resolution: {integrity: sha512-vxgGHTmnDU9j4CX7dDBLzxgmHxfda/yPcgJkGCMUSCwRmz+euo/V08xXLNgXTeqAB9Fhf3Pe2nO1RNKLCVgphQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + svelte: ^5.0.0 + vite-plus: '*' + peerDependenciesMeta: + svelte: + optional: true + vite-plus: + optional: true + + oxlint@1.77.0: + resolution: {integrity: sha512-qnGh8XJHaQ0dprrDXNQZgS0FgjI6v+V3+X8DwmaV++5Aamy6jGKfDdQ1TUvhUxtmKFAbEf4/WeO5QZX+5WSngg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=7.0.2001' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + p-finally@1.0.0: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} @@ -4464,6 +4785,10 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinypool@2.1.0: + resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@3.1.1: resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} @@ -6557,6 +6882,120 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.24.2': optional: true + '@oxfmt/binding-android-arm-eabi@0.62.0': + optional: true + + '@oxfmt/binding-android-arm64@0.62.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.62.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.62.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.62.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.62.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.62.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.62.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.62.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.62.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.62.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.62.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.62.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.62.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.62.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.62.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.62.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.62.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.62.0': + optional: true + + '@oxlint/binding-android-arm-eabi@1.77.0': + optional: true + + '@oxlint/binding-android-arm64@1.77.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.77.0': + optional: true + + '@oxlint/binding-darwin-x64@1.77.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.77.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.77.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.77.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.77.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.77.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.77.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.77.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.77.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.77.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.77.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.77.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.77.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.77.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.77.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.77.0': + optional: true + '@phenomnomnominal/tsquery@6.2.0(typescript@6.0.3)': dependencies: '@types/esquery': 1.5.4 @@ -9376,6 +9815,52 @@ snapshots: '@oxc-resolver/binding-win32-arm64-msvc': 11.24.2 '@oxc-resolver/binding-win32-x64-msvc': 11.24.2 + oxfmt@0.62.0: + dependencies: + tinypool: 2.1.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.62.0 + '@oxfmt/binding-android-arm64': 0.62.0 + '@oxfmt/binding-darwin-arm64': 0.62.0 + '@oxfmt/binding-darwin-x64': 0.62.0 + '@oxfmt/binding-freebsd-x64': 0.62.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.62.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.62.0 + '@oxfmt/binding-linux-arm64-gnu': 0.62.0 + '@oxfmt/binding-linux-arm64-musl': 0.62.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.62.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.62.0 + '@oxfmt/binding-linux-riscv64-musl': 0.62.0 + '@oxfmt/binding-linux-s390x-gnu': 0.62.0 + '@oxfmt/binding-linux-x64-gnu': 0.62.0 + '@oxfmt/binding-linux-x64-musl': 0.62.0 + '@oxfmt/binding-openharmony-arm64': 0.62.0 + '@oxfmt/binding-win32-arm64-msvc': 0.62.0 + '@oxfmt/binding-win32-ia32-msvc': 0.62.0 + '@oxfmt/binding-win32-x64-msvc': 0.62.0 + + oxlint@1.77.0: + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.77.0 + '@oxlint/binding-android-arm64': 1.77.0 + '@oxlint/binding-darwin-arm64': 1.77.0 + '@oxlint/binding-darwin-x64': 1.77.0 + '@oxlint/binding-freebsd-x64': 1.77.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.77.0 + '@oxlint/binding-linux-arm-musleabihf': 1.77.0 + '@oxlint/binding-linux-arm64-gnu': 1.77.0 + '@oxlint/binding-linux-arm64-musl': 1.77.0 + '@oxlint/binding-linux-ppc64-gnu': 1.77.0 + '@oxlint/binding-linux-riscv64-gnu': 1.77.0 + '@oxlint/binding-linux-riscv64-musl': 1.77.0 + '@oxlint/binding-linux-s390x-gnu': 1.77.0 + '@oxlint/binding-linux-x64-gnu': 1.77.0 + '@oxlint/binding-linux-x64-musl': 1.77.0 + '@oxlint/binding-openharmony-arm64': 1.77.0 + '@oxlint/binding-win32-arm64-msvc': 1.77.0 + '@oxlint/binding-win32-ia32-msvc': 1.77.0 + '@oxlint/binding-win32-x64-msvc': 1.77.0 + p-finally@1.0.0: {} p-limit@2.3.0: @@ -10204,6 +10689,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.5) picomatch: 4.0.5 + tinypool@2.1.0: {} + tinyrainbow@3.1.1: {} tldts-core@6.1.86: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f1fd67b..c8e3115 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -17,3 +17,7 @@ allowBuilds: '@swc/core': true nativescript: true nx: true + +# CI and developer workflows install dependencies explicitly with the lockfile. +# Avoid implicit pnpm installs when `pnpm run` / `pnpm exec` start a task. +verifyDepsBeforeRun: false diff --git a/tools/build-xcframework.sh b/tools/build-xcframework.sh index b81d105..711ccca 100755 --- a/tools/build-xcframework.sh +++ b/tools/build-xcframework.sh @@ -4,8 +4,9 @@ # Usage: tools/build-xcframework.sh # ENGINE = NSCWamr | NSCWasm3 | NSCWry # -# Builds .xcframework (device arm64 + simulator arm64 + simulator -# x86_64) with size optimizations, plus dSYM bundles for symbolication. +# Builds .xcframework (device arm64 + a universal arm64/x86_64 +# simulator slice) with size optimizations, plus dSYM bundles for +# symbolication. # The engine name selects the package (ns-) and product. # # The NativeScript CLI (9.x) picks up any platforms/ios/*.xcframework from a @@ -14,9 +15,14 @@ # # Notes: # - The iOS 26 SDK still supports x86_64 simulator compilation for Intel Macs -# and Rosetta-mode simulators on Apple Silicon. We ship an x86_64 simulator -# slice so the framework links on every macOS host. The xcframework has -# three slices: ios-arm64 (device), ios-arm64-simulator, ios-x86_64-simulator. +# and Rosetta-mode simulators on Apple Silicon. We ship x86_64 simulator +# code so the framework links on every macOS host. The two simulator +# architectures MUST be lipo'd into a single universal slice: an +# xcframework that lists ios-arm64-simulator and ios-x86_64-simulator as +# separate libraries is rejected by Xcode with "Both 'ios-arm64-simulator' +# and 'ios-x86_64-simulator' represent two equivalent library definitions" +# (same platform + same variant). The bundle therefore has two slices: +# ios-arm64 (device) and ios-arm64_x86_64-simulator. # - xcodebuild cannot be used from sandboxed terminals (nested sandbox-exec is # blocked), so each slice is built with `swift build --triple` + explicit # SDK flags, and the .xcframework bundle is assembled by hand (its layout is @@ -75,27 +81,40 @@ build_slice() { # $1=triple $2=sdk-name $3=output-name -c "Add :CFBundleVersion string 1" \ -c "Add :CFBundleShortVersionString string 0.1.0" \ "$fw/Info.plist" >/dev/null - - # dSYM from the UNSTRIPPED binary (release builds with -g carry DWARF). - dsymutil "$fw/$ENGINE" -o "$BUILD/$out_name/$ENGINE.framework.dSYM" >/dev/null 2>&1 || true } +SIM=ios-arm64_x86_64-simulator + # ── 1. Device slice (arm64) ───────────────────────────────────────────── build_slice arm64-apple-ios iphoneos ios-arm64 -# ── 2. Simulator slice (arm64) ────────────────────────────────────────── -build_slice arm64-apple-ios-simulator iphonesimulator ios-arm64-simulator +# ── 2. Simulator slices — built per-arch, merged into one universal slice ─ +# x86_64 is needed for Intel Macs and Rosetta-mode simulators. +build_slice arm64-apple-ios-simulator iphonesimulator sim-arm64 +build_slice x86_64-apple-ios-simulator iphonesimulator sim-x86_64 -# ── 3. Simulator slice (x86_64) — needed for Intel Macs and Rosetta sims ─ -build_slice x86_64-apple-ios-simulator iphonesimulator ios-x86_64-simulator +# ── 3. Merge the simulator architectures into ONE universal slice. Two +# separate simulator libraries are "equivalent library definitions" to +# Xcode (same platform + variant) and make the whole xcframework fail +# to load; a single fat binary is the layout xcodebuild itself emits. ─ +ditto "$BUILD/sim-arm64/$ENGINE.framework" "$BUILD/$SIM/$ENGINE.framework" +lipo -create "$BUILD/sim-arm64/$ENGINE.framework/$ENGINE" \ + "$BUILD/sim-x86_64/$ENGINE.framework/$ENGINE" \ + -output "$BUILD/$SIM/$ENGINE.framework/$ENGINE" + +# ── 4. dSYMs from the UNSTRIPPED binaries (release builds with -g carry +# DWARF); dsymutil handles the universal simulator binary. ────────── +for slice in ios-arm64 "$SIM"; do + dsymutil "$BUILD/$slice/$ENGINE.framework/$ENGINE" \ + -o "$BUILD/$slice/$ENGINE.framework.dSYM" >/dev/null 2>&1 || true +done -# ── 4. Strip the shipped binaries: local symbols + DWARF (keeps exported +# ── 5. Strip the shipped binaries: local symbols + DWARF (keeps exported # dynamic symbols + ObjC runtime metadata; dSYMs retain everything). ─ strip -S -x "$BUILD/ios-arm64/$ENGINE.framework/$ENGINE" -strip -S -x "$BUILD/ios-arm64-simulator/$ENGINE.framework/$ENGINE" -strip -S -x "$BUILD/ios-x86_64-simulator/$ENGINE.framework/$ENGINE" +strip -S -x "$BUILD/$SIM/$ENGINE.framework/$ENGINE" -# ── 5. Assemble the XCFramework bundle by hand ────────────────────────── +# ── 6. Assemble the XCFramework bundle by hand ────────────────────────── mkdir -p "$OUT" cat > "$OUT/Info.plist" < @@ -116,23 +135,11 @@ cat > "$OUT/Info.plist" < LibraryIdentifier - ios-arm64-simulator - LibraryPath - $ENGINE.framework - SupportedArchitectures - arm64 - SupportedPlatform - ios - SupportedPlatformVariant - simulator - - - LibraryIdentifier - ios-x86_64-simulator + $SIM LibraryPath $ENGINE.framework SupportedArchitectures - x86_64 + arm64x86_64 SupportedPlatform ios SupportedPlatformVariant @@ -147,11 +154,10 @@ cat > "$OUT/Info.plist" < PLIST ditto "$BUILD/ios-arm64/$ENGINE.framework" "$OUT/ios-arm64/$ENGINE.framework" -ditto "$BUILD/ios-arm64-simulator/$ENGINE.framework" "$OUT/ios-arm64-simulator/$ENGINE.framework" -ditto "$BUILD/ios-x86_64-simulator/$ENGINE.framework" "$OUT/ios-x86_64-simulator/$ENGINE.framework" +ditto "$BUILD/$SIM/$ENGINE.framework" "$OUT/$SIM/$ENGINE.framework" -# ── 6. Keep the dSYMs produced by dsymutil (all slices). ──────────────── -for slice in ios-arm64 ios-arm64-simulator ios-x86_64-simulator; do +# ── 7. Keep the dSYMs produced by dsymutil (all slices). ──────────────── +for slice in ios-arm64 "$SIM"; do ditto "$BUILD/$slice/$ENGINE.framework.dSYM" "$DSYM_OUT/$slice/$ENGINE.framework.dSYM" done diff --git a/tools/nx-oxfmt.js b/tools/nx-oxfmt.js new file mode 100644 index 0000000..c89042a --- /dev/null +++ b/tools/nx-oxfmt.js @@ -0,0 +1,59 @@ +// Nx inference plugin that adds oxfmt format targets to every TypeScript project. +// Registered in nx.json as "./tools/nx-oxfmt.js". +const { dirname, join } = require('path'); + +/** Build the format target for a single matched package.json, or undefined to skip. */ +function createNodesForFile(projectFile, opts, ctx) { + // dirname('package.json') === '.', which is how the workspace root is skipped. + const root = dirname(projectFile); + if (root === '.') return; + + try { + const pkg = require(join(ctx.workspaceRoot, projectFile)); + if (!pkg.name) return; + } catch { + return; + } + + if (opts?.targets?.format) return; + + return { + projects: { + [root]: { + targets: { + format: { + executor: 'nx:run-commands', + options: { + command: `npx oxfmt`, + cwd: root, + }, + cache: true, + inputs: [ + '{projectRoot}/**/*.ts', + '{projectRoot}/**/*.tsx', + '{projectRoot}/**/*.js', + '{projectRoot}/**/*.mjs', + ], + }, + }, + }, + }, + }; +} + +// Nx >= 21 calls createNodes with the *batch* of matched files and expects +// [configFile, result] tuples back. +/** @type {import('nx/src/project-graph/plugins').CreateNodesV2} */ +const createNodes = [ + '**/package.json', + (projectFiles, opts, ctx) => { + const results = []; + for (const projectFile of projectFiles) { + const result = createNodesForFile(projectFile, opts, ctx); + if (result) results.push([projectFile, result]); + } + return results; + }, +]; + +module.exports = { createNodes, createNodesV2: createNodes }; diff --git a/tools/nx-oxlint.js b/tools/nx-oxlint.js new file mode 100644 index 0000000..9663e46 --- /dev/null +++ b/tools/nx-oxlint.js @@ -0,0 +1,62 @@ +// Nx inference plugin that adds oxlint lint targets to every TypeScript project. +// Registered in nx.json as "./tools/nx-oxlint.js". +const { dirname, join } = require('path'); + +/** Build the lint target for a single matched package.json, or undefined to skip. */ +function createNodesForFile(projectFile, opts, ctx) { + // dirname('package.json') === '.', which is how the workspace root is skipped. + const root = dirname(projectFile); + if (root === '.') return; // skip workspace root + + // Only configure if the project has src/**/*.ts (a TS library or app). + try { + const pkg = require(join(ctx.workspaceRoot, projectFile)); + if (!pkg.name) return; + } catch { + return; + } + + // Already has a lint target defined in the project file — don't override. + // (nx:run-script targets for e.g. lint.ios / lint.android are OK.) + if (opts?.targets?.lint) return; + + return { + projects: { + [root]: { + targets: { + lint: { + executor: 'nx:run-commands', + options: { + command: `npx oxlint --config ${ctx.workspaceRoot}/.oxlintrc.json --deny-warnings`, + cwd: root, + }, + cache: true, + inputs: [ + '{projectRoot}/**/*.ts', + '{projectRoot}/**/*.tsx', + '{projectRoot}/tsconfig.json', + '{workspaceRoot}/.oxlintrc.json', + ], + }, + }, + }, + }, + }; +} + +// Nx >= 21 calls createNodes with the *batch* of matched files and expects +// [configFile, result] tuples back. +/** @type {import('nx/src/project-graph/plugins').CreateNodesV2} */ +const createNodes = [ + '**/package.json', + (projectFiles, opts, ctx) => { + const results = []; + for (const projectFile of projectFiles) { + const result = createNodesForFile(projectFile, opts, ctx); + if (result) results.push([projectFile, result]); + } + return results; + }, +]; + +module.exports = { createNodes, createNodesV2: createNodes }; diff --git a/tsconfig.base.json b/tsconfig.base.json index 322c123..0bba0f1 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -14,6 +14,7 @@ "noImplicitReturns": true, "noUnusedLocals": true, "skipLibCheck": true, + "noImplicitAny": true, "strict": true, "target": "es2022", "customConditions": ["@cross-code/source"], diff --git a/tsconfig.json b/tsconfig.json index 08c120b..f8cbfc7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,26 +1,41 @@ { - "extends": "./tsconfig.base.json", - "compileOnSave": false, - "files": [], - "references": [ - { - "path": "./packages/ns-wasm3" - }, - { - "path": "./packages/ns-wasm-fixture" - }, - { - "path": "./packages/ns-wamr" - }, - { - "path": "./packages/vitest-ns" - }, - { - "path": "./packages/vitest-ns-ui" - }, - { - "path": "./packages/ns-wry" - } - ], - "compilerOptions": {} + "extends": "./tsconfig.base.json", + "compileOnSave": false, + "files": [], + "references": [ + { + "path": "./packages/ns-wasm3" + }, + { + "path": "./packages/ns-wasm-fixture" + }, + { + "path": "./packages/ns-wamr" + }, + { + "path": "./packages/vitest-ns" + }, + { + "path": "./packages/vitest-ns-ui" + }, + { + "path": "./packages/ns-wry" + }, + { + "path": "./packages/ns-wasm-kit-runtime" + }, + { + "path": "./packages/ns-wasm-chicory" + }, + { + "path": "./packages/ns-wasm-core" + }, + { + "path": "./packages/ns-wasm-edge" + }, + { + "path": "./packages/ns-endive" + } + ], + "compilerOptions": {} }