QVAC-13429 feat[api]: add electron-forge plugin for native addon tree-shaking#2480
QVAC-13429 feat[api]: add electron-forge plugin for native addon tree-shaking#2480opaninakuffo wants to merge 1 commit into
Conversation
Adds @qvac/sdk/electron-forge: an Electron Forge plugin that bundles the QVAC worker, verifies its native addons, then configures Electron Packager to tree-shake unused @qvac/* addons and non-target prebuilds. Replaces the prior two-step `qvac bundle sdk` + `qvac verify` flow with a single `electron-forge package` invocation by calling bundleSdk and verifyBundle directly via dynamic import of @qvac/sdk/commands. Plugin behavior: - Hard-blocks macOS universal builds (prebuilds are arch-specific). - Forces asar: false (Bare worker can't load from asar). - Detects target hosts from --platform / --arch (CLI + packagerConfig). - Excludes unused @qvac/* addons via packagerConfig.ignore and cleans up empty addon dir shells in afterPrune. - Prunes prebuilds for non-target platform/arch, including nested node_modules (covers pnpm/non-hoisted layouts). Adds @electron-forge/plugin-base as devDep + optional peerDep, exports ./electron-forge from the SDK package, ignores *.tgz pack artifacts, and ships 15 unit tests covering diffAddons, createIgnore (function + array form), detectTargetHosts (argv parsing + multi-arch + override semantics), and the trimmed-stack QvacForgePluginError.
simon-iribarren
left a comment
There was a problem hiding this comment.
Findings
bundleSdkdoes not receive the detected target hosts.
The plugin detects target hosts from Forge config / CLI args and passes them into verifyBundle, but runBundleAndVerify calls bundleSdk before that without hosts:
const bundleOpts = { projectRoot: projectDir };
if (options.configPath) bundleOpts.configPath = options.configPath;
bundleResult = await bundleSdk(bundleOpts);bundleSdk then falls back to its own default host list, while verifyBundle checks the detected target hosts. That makes targeted Forge invocations inconsistent: for example, electron-forge package --platform=darwin --arch=x64 verifies darwin-x64, but the Bare bundle was still produced with the SDK default host set. For targets outside that default list, verification can check a host the bundle was never built for; even for normal desktop targets, this weakens the target-specific packaging behavior this plugin is meant to provide.
Please compute the resolved hosts once and pass the same list into both bundleSdk({ ..., hosts }) and verifyBundle({ ..., hosts }). It would also be worth adding a wiring test around runBundleAndVerify / configurePackager, since the current tests cover detectTargetHosts in isolation but not the command boundary where this mismatch occurs.
- The new public API needs durable consumer docs.
This adds the public @qvac/sdk/electron-forge export and the title/body tagging looks correct for feat[api], including a usage snippet in the PR body. But the diff does not include SDK README or docs-site coverage for install requirements, forge.config.cjs usage, the forced asar: false behavior, unsupported universal builds, or how target host detection works.
Please add consumer-facing docs in this PR, or link the follow-up docs PR in the description before merge.
CI Status
The visible PR checks are green: SDK Pod Checks, validate-pr, label gate, and resolve-config pass. Release/publish jobs are skipped as expected for a PR, and the run-tests job is skipped.
API Surface & Tagging
This PR adds public API surface via @qvac/sdk/electron-forge. The [api] title tag and usage snippet are present. The remaining gap is the missing durable consumer docs noted above.
Local Verification
I ran the focused unit test file:
bun run test/unit/electron-forge.test.tsResult: 15/15 tests passed, 32/32 assertions passed.
🎯 What problem does this PR solve?
qvac bundle sdkthenqvac verify) before everyelectron-forge package, with no way to keep the addon manifest, packaged tree, and prebuilds in sync.@qvac/*addon and every per-arch prebuild (~700 MB of dead weight on a darwin-arm64 build).arch: "universal"silently produced broken packages because prebuilds are arch-specific.asar: true(Forge default) breaks Bare worker loading.📝 How does it solve it?
@qvac/sdk/electron-forge— a build-time Electron Forge plugin (CJS) consumed fromforge.config.cjs.resolveForgeConfig: invokesbundleSdk+verifyBundleprogrammatically (no external CLI), composespackagerConfig.ignoreto strip unused@qvac/*addons + mobile prebuilds, forcesasar: false, hard-blocksarch: "universal"with a trimmed-stack error.afterPrune: prunes prebuilds for non-target${platform}-${arch}recursively (including nestednode_modules, so pnpm/non-hoisted layouts work), and removes empty addon dir shells left by Packager's ignore filter.--platform/--arch(CLI +packagerConfig), so cross-platform invocations (e.g. building win32 from a darwin host) verify against the right prebuild set instead of the host's.bundleSdk/verifyBundleresults and theafterPrunehook across Forge's double-firedresolveForgeConfig(Loading configuration+Preparing to package).🧪 How was it tested?
packages/sdk/test/unit/electron-forge.test.ts):diffAddons,createIgnore(function + array form,/out/parity, mobile prebuild filters),detectTargetHosts(argv parsing,=valueand space form, multi-arch expansion,packagerConfigoverride),QvacForgePluginErrortrimmed stack.electron-forge packagefrom darwin-arm64: ~520 MB / 134 prebuild dirs pruned, 8 unused addons excluded, app launches and runs llamacpp completion plus a customqvac-echo-pluginat runtime.electron-forge make(zip + dmg): dmg installs to/Applicationsand runs end-to-end. ConfirmedresolveForgeConfigcaching +afterPruneidempotency (no double-prune duringmake).electron-forge package --platform=darwin --arch=x64from arm64 host:verifyBundletargetsdarwin-x64, prune correctly dropsdarwin-arm64.electron-forge package --arch=universal: hard-blocks withQvacForgePluginError.bun run lint(eslint + tsc) clean.🔌 API Changes
Adds one new public export —
@qvac/sdk/electron-forge— and@electron-forge/plugin-baseas an optional peer dependency.