Skip to content

QVAC-18679 feat[api]: wire qvac verify bundle into Expo plugin#2000

Draft
opaninakuffo wants to merge 6 commits into
tetherto:mainfrom
opaninakuffo:feat/verify-bundle-expo-wiring
Draft

QVAC-18679 feat[api]: wire qvac verify bundle into Expo plugin#2000
opaninakuffo wants to merge 6 commits into
tetherto:mainfrom
opaninakuffo:feat/verify-bundle-expo-wiring

Conversation

@opaninakuffo
Copy link
Copy Markdown
Contributor

@opaninakuffo opaninakuffo commented May 12, 2026

🚧 Draft until: #1969 (qvac verify deps) and #1984 (qvac verify bundle) merges and @qvac/cli@0.4.0 ships to the registry.

🎯 What problem does this PR solve?

📝 How does it solve it?

  • Drop the npx fallbackresolveCliCommand throws CliNotFoundInNodeModulesError (50608) when node_modules/@qvac/cli/dist/index.js is missing. Consumers must declare @qvac/cli as a dep.
  • Reorder the build flow to runBundler → assert bundle exists → runVerifier → fs.copyFileSync. The SDK dist/worker.mobile.bundle.js only updates when verification passes; failure preserves the last known-good artifact and fails Expo prebuild fast.
  • runVerifier invokes qvac verify bundle --addons-source <bundle> --host <h>... for every MOBILE_HOSTS entry. Non-zero exit → BundleVerificationFailedError (50609).
  • Runtime sourcing via qvac.config.* — the plugin already discovers qvac.config.{json,js,mjs} for bundle sdk and now forwards the same path as --config <path> to verify bundle (PR 2 capability). With config, ABI checks are pinned to bareRuntimeVersion. Without config, the CLI auto-detects from node_modules (bare-runtimebare); ABI checks stay strict on auto-detect, and only fall through to unknown-runtime-version (warning) when neither is installed. Prebuild-presence checks always run.

Pear deferral: pear/pre.ts carries a head-note that qvac verify bundle isn't wired there yet — Pear consumers should run it manually (locally and/or in CI) before pear stage / pear run.

🔌 API Changes

  • New error codes (Build/Bundle range):
    • CLI_NOT_FOUND_IN_NODE_MODULES (50608) → CliNotFoundInNodeModulesError
    • BUNDLE_VERIFICATION_FAILED (50609) → BundleVerificationFailedError
  • Behavior change: withMobileBundle throws CliNotFoundInNodeModulesError instead of silently using npx. Consumers must add @qvac/cli (≥0.3.0) to their (dev)dependencies.
  • Config field consumed: optional bareRuntimeVersion: string in qvac.config.{json,js,mjs,ts} (read by the CLI via --config; no new SDK API).

🧪 How was it tested?

  • Unit (test/unit/with-mobile-bundle.test.ts, 9 tests):
    • MOBILE_HOSTS matches the canonical mobile host set.
    • buildVerifyBundleCommand: emits one --host per host; omits --config (and asserts no stray --bare-runtime-version — regression guard for the removed env-var path); includes --config "<path>" when provided; quotes config paths with spaces; quotes the bundle path; invokes the verify bundle subcommand.
    • resolveCliCommand: returns node "<absPath>" against a real mkdtemp fs fixture; throws CliNotFoundInNodeModulesError when the CLI is absent.
  • Lint + typecheck + full test:unit: green; no regressions.

Manual smoke — qvac-app-workbench-mobile

Locally packed cli and sdk packages, installed as file: deps, then npm run prebuild:android (4 MOBILE_HOSTS, 33 addons, bare-runtime@1.26.0 in node_modules):

# qvac.config.json Result Evidence
1 absent ✅ exit 0 "no qvac.config.* found" warning; CLI auto-detected bare-runtime@1.26.0; 33 addons verified; dist copied
2 bareRuntimeVersion: "1.26.0" ✅ exit 0 "Found qvac.config.json"; no warning; 33 addons verified; dist copied
3 bareRuntimeVersion: "0.0.1" ✅ exit 1 as intended 24 ABI mismatches; BundleVerificationFailedError (50609); dist NOT updated (last known-good preserved)

Scenario 3's stack confirmed runVerifier forwards --config:

node "<…>/@qvac/cli/dist/index.js" verify bundle \
  --addons-source "<…>/qvac/worker.bundle.js" \
  --host android-arm64 --host ios-arm64 \
  --host ios-arm64-simulator --host ios-x64-simulator \
  --config "<…>/qvac.config.json"

Manual smoke — Pear head-note workflow

Validated:

  • Packed sdk ships the new head-note in pear/pre.js.
  • Manual command from the head-note (qvac verify bundle --addons-source <bundle> --host darwin-arm64 --config qvac.config.json) works end-to-end: invalid runtime → 24 ABI errors / exit 1; valid runtime → 33 addons verified / exit 0.

Move src/verify-deps/ to src/verify/deps/ so the directory layout
mirrors the CLI surface (qvac verify <subcommand>) and future
verify subcommands (bundle, ...) land in a shared parent. No
behavior change: rename + import-path updates only.
Replace `key.indexOf(marker)` with `match.index` so each regex match
maps to its actual package root. Previously, a resolution key with
the same package name appearing twice (e.g.
`node_modules/foo/node_modules/bar/node_modules/foo/index.js`)
collapsed the nested `foo` back to the top-level path.
Also exports `buildNestedPathIndex` so it can be reused by the
upcoming `qvac verify bundle` command.
Adds regression tests covering single, nested, and repeated-name
resolution keys.
…cation

New `qvac verify bundle` subcommand under `qvac verify`. Validates
the actual artifacts (prebuilds + ABI compatibility) of a worker
bundle or installed node_modules tree before shipping.
Accepts a `worker.bundle.js` (bare-pack tree-shaken output) or a
`node_modules` directory via `--addons-source`; source kind is
auto-detected.
Per addon per `--host`:
- prebuild presence: `<packageRoot>/prebuilds/<host>/*.bare`
- ABI compatibility: addon's `engines.bare` must satisfy the resolved
  Bare runtime version. Resolution order: `--bare-runtime-version`
  flag -> `bare-runtime/package.json` -> `bare/package.json`. Mobile/Expo
  CI should pass `--bare-runtime-version` explicitly;
  `react-native-bare-kit` does not currently expose embedded runtime
  metadata.
Structured issue codes for CI consumption:
- error: `missing-prebuild`, `abi-mismatch`, `invalid-runtime-version`,
  `invalid-source`
- warning: `unknown-runtime-version`, `malformed-engines-bare`
Exit 1 on any error-level issue, 0 otherwise.
Tests: unit + Bats smoke for both source kinds, prebuild/ABI checks,
runtime auto-resolution, malformed-engines-bare warning path
(surfaced even when runtime is unknown), and regression coverage for
nested-only bundle resolutions and multi-instance retention.
Validated end-to-end against qvac-app-workbench-mobile: 33 addons in
a 10MB worker.bundle.js across 5 hosts; 45 addons in node_modules
with 2 legitimate prebuild gaps surfaced; bare-os@3.6.2 (top-level)
and bare-os@3.9.0 (nested under @qvac/tts-onnx) correctly
distinguished.
Adds `semver` as a runtime dependency.
Adds `--config <path>` and auto-detection of `qvac.config.{json,js,mjs,ts}`
so projects can pin the Bare runtime in a committed file (works in any
runtime, including the future Pear pre-hook where env vars don't).

Resolution: `--bare-runtime-version` > config `bareRuntimeVersion` >
`bare-runtime/package.json` > `bare/package.json`. Both flag and config
values share the same semver validation; malformed values emit
`invalid-runtime-version` carrying `source: 'flag' | 'config'`, with the
config file's actual path in the message. Explicit `--config` to a
missing/unreadable file emits `invalid-source`; auto-detect failures and
non-string config values fall through silently.

Tests: 7 unit + 1 bats covering auto-detect, explicit `--config`, flag
precedence, malformed/non-string config values, and config-path label in
error messages.
Expo plugin now invokes `qvac verify bundle` after generating the mobile
worker bundle. The build flow is `runBundler → assert bundle exists →
runVerifier → fs.copyFileSync` so `dist/worker.mobile.bundle.js` only
updates when verification passes.

`resolveCliCommand` requires a local `@qvac/cli` install — the previous
`npx --package=@qvac/cli qvac` fallback is removed (throws
`CliNotFoundInNodeModulesError`, code 50608). Non-zero verifier exit is
wrapped in `BundleVerificationFailedError` (50609).

The discovered `qvac.config.{json,js,mjs}` path is now forwarded to the
CLI as `--config <path>`, so `bareRuntimeVersion` pins ABI checks
deterministically. Without a config the CLI auto-detects Bare runtime
from `node_modules` (`bare-runtime` → `bare`); ABI checks stay strict on
auto-detect and only fall through to `unknown-runtime-version` (warning)
when neither is installed.

Pear wiring is intentionally deferred — `pear/pre.ts` carries a head-note
that consumers should run `qvac verify bundle` manually before
`pear stage` / `pear run`.

`MOBILE_HOSTS`, `buildVerifyBundleCommand`, and `resolveCliCommand` are
exported for unit testing; the plugin's default export is unchanged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant