Repository navigation
Build packages/skill and invoke the installer directly via bun - #2143
Conversation
packages/skill was never built by the root/app build pipeline, so dist/install.js (the workspace bin target) never existed. On top of that, run_skill_installer shelled out to `npx --yes @nodespaceai/skill install`, resolving an unpublished npm package — guaranteed exit 127 on every launch. - Add scripts/build-skill.ts (bun run build:skill): runs packages/skill's own tsc build, then stages dist/, shims/, SKILL.md, and package.json into packages/desktop-app/src-tauri/resources/skill/, declared as a bundled Tauri `resources` entry in tauri.conf.json. Wired into root `build`, desktop-app's `dev:tauri`/`tauri:build`, and the pre-push test gate (nodespace-app's build.rs requires the resource to exist before any `cargo build/test -p nodespace-app`, same as the existing sidecar binaries requirement). - skill_setup.rs: resolve the installer via Tauri's BaseDirectory::Resource (falling back to the source-checkout path for cargo test/run), then invoke it directly with `bun <path> install` — never npx/npm, per this repo's Bun-only policy. Chose bundling over publishing to npm: this is a local-first desktop app and startup should not depend on registry availability, and there's no version-skew concern to manage between the app and a published package. - Track skill_install_failed in setup.json so a genuine failure logs at WARN and emits a `skill:install-failed` event to the frontend once, then drops to DEBUG-only retries on subsequent launches instead of repeating the warning every startup. app-shell.svelte surfaces the event via the existing statusBar.error() pattern (mirrors skill:cli-missing). - Add unit tests for the path resolution and a regression guard against npx/npm ever being reintroduced. Closes #2113
The v8 coverage provider scans the whole desktop-app project root for matching source files, not just ones touched by tests. The newly staged packages/desktop-app/src-tauri/resources/skill/dist/*.js (built by bun run build:skill) got swept in as 0%-covered, dragging the global line/statement coverage below the 90% threshold and failing the pre-push gate. Excluded src-tauri/ wholesale — it's the Rust crate root plus staged build resources, never frontend source.
…tests Adversarial review of the skill-installer fix found four real issues: - No locking around ~/.nodespace/setup.json's read-modify-write. Three call sites (startup, the onboarding configure_skill command, and the manual-retry install_skill command) could race and clobber each other's skill_installed/skill_install_failed state. Added a process-wide async Mutex serializing install_skill's full body. - finish_failed emitted skill:install-failed from all three call sites, but configure_skill/install_skill already return their result synchronously to an awaiting caller (Settings already shows result.error inline) — so a manual retry failure showed the same error twice, once inline and once as a toast. Moved the emit to the one call site that actually needs it (lib.rs's fire-and-forget startup call, which has no other way to reach the user), gated on a new failure_is_new field so it still only fires once per new failure. - resolve_installer_path's "not found" error embedded the build machine's absolute path and told the user to run a dev command — meaningless (and a minor path leak) if ever shown to a packaged end user. Made it generic; the technical detail still goes to a debug log. - run_skill_installer's "Failed to launch bun" error is what most non-developer installs will actually see (bun isn't bundled with the app). Detect ErrorKind::NotFound specifically and point at https://bun.sh instead of a raw OS error string. Also strengthened the regression tests: replaced the source-text self-scan for "npx"/"npm" (gameable by any non-literal command construction) with two behavioral tests against the real installer_command() the production code calls — one asserts the constructed Command's actual program is "bun" (via its Debug repr, which reflects the runtime value regardless of how it was built), the other actually runs it against an isolated, throwaway $HOME and asserts SKILL.md + the claude-code shim land where expected.
|
Adversarial review requested (high effort). It found 4 real issues + 2 nits; addressed the real ones in
Also replaced the weak source-text self-scan test (gameable by non-literal command construction) with two behavioral tests against the actual Two remaining nits acknowledged, not fixed (low value / disproportionate effort for this issue's scope):
All checks re-verified after the fix: |
|
Independent re-verification after the adversarial review's fixes were applied:
GitHub blocks self-approving my own PR, so recording verification here instead. Merging per this repo's merge policy (core requires adversarial re-review before merge, which is done — see the review comment above). |
Problem
packages/skillwas never built by any part of the build pipeline (dist/install.js, its declaredbintarget, never existed), and the desktop app invoked it vianpx --yes @nodespaceai/skill install— an unpublished npm package, in a Bun-only repo. Every launch logged exit 127 and never installedSKILL.md/agent shims.Fix
Build wiring (
scripts/build-skill.ts, newbuild:skillscript): runspackages/skill's owntscbuild, then stagesdist/,shims/,SKILL.md, andpackage.jsonintopackages/desktop-app/src-tauri/resources/skill/, declared as a bundled Tauriresourcesentry intauri.conf.json. Wired into:buildpackages/desktop-app'sdev:tauriandtauri:build(mirrors how sidecar binaries are staged before those same commands)scripts/test-gate.ts(the pre-push gate) —nodespace-app'sbuild.rs(tauri_build::build()) hard-fails immediately if any declaredresources/externalBinentry doesn't physically exist, socargo test -p nodespace-appneeds this staged first, same requirement the sidecar binaries already have.Invocation (
skill_setup.rs):resolve_installer_pathresolves the installer via Tauri'sBaseDirectory::Resource(falling back to the source-checkout path forcargo test/cargo runoutside the Tauri resource pipeline), thenrun_skill_installerinvokes it directly —Command::new("bun").arg(installer_path).arg("install")— nevernpx/npm.Decision: bundled installer, not a published npm package. This is a local-first desktop app; startup should not depend on npm registry availability, and there's no
@nodespaceai/skillversion-skew to manage against the app version if it's never resolved externally. Bun is already the sanctioned runtime for this repo, so invoking the bundleddist/install.jsdirectly viabun(notbunx, since it's a local file, not a registry package) fits the existing Bun-only policy rather than working around it.Failure surfacing:
setup.jsonnow tracksskill_install_failed. A genuine failure logs atWARNand emits askill:install-failedevent to the frontend (surfaced via the existingstatusBar.error()pattern inapp-shell.svelte, mirroringskill:cli-missing) — but only the first time. Subsequent launches with the same persistent failure keep retrying (the environment may get fixed, e.g.bungets installed) but log atDEBUGonly, so a standing failure never becomes per-launch log/UI spam. A later success clears the flag.Testing
bun run build:skill— confirmedpackages/skill/dist/install.jsand the stagedpackages/desktop-app/src-tauri/resources/skill/dist/install.jsboth get produced.$HOME(never touching the real~/.nodespaceor~/.claude):env HOME=<fake> bun dist/install.js install— correctly detected a fake.claudedir and installedSKILL.md+ theclaude-codeshim into<fake>/.claude/skills/nodespace/. Verified identically for both the source-checkoutdist/and the staged bundle copy.cargo check -p nodespace-app/cargo clippy -p nodespace-app --all-targets -- -D warnings -D clippy::unused-async/cargo fmt --all -- --check— all clean.cargo test -p nodespace-app --lib— 69 passed (67 pre-existing + 2 new:resolve_installer_pathresolves to the real builtdist/install.js, and a regression guard asserting noCommand::newinvocation in this file ever shells out tonpx/npm).tauri_build::build()copies the newresources/skill/**/*entry intotarget/debug/resources/skill/dist/install.jsatcargo buildtime — the exact pathBaseDirectory::Resourceresolves to in dev mode.bun run test:skill— 24/24 passed (untouched installer/shim-parity logic).bun run test(desktop-app frontend) — 4742/4743 passed; the one failure (architecture-benchmarks.test.ts, a perf-timing threshold) is pre-existing full-suite flakiness unrelated to this change — passes cleanly in isolation, and a second full run failed a different, also-unrelated test (core-plugins.test.ts), also passing in isolation.bunx eslint/svelte-checkon desktop-app — 0 errors, 0 warnings.bun run --cwd packages/skill quality:fix(tsc --noEmit) — clean.bun run test:scripts— 93/93 passed.Acceptance criteria
packages/skillis built as part of producing a runnable app;dist/install.jsexistsnpx/npm$HOME—SKILL.mdand agent shims land in the expected locationsCloses #2113