CLI runs on Node 20+: npx storebound (bunx keeps working)

- cli/lib/rt.ts: the runtime layer on node: modules (spawn / spawnSync with Bun's defaults, exit codes and
  "Executable not found", which with PATHEXT, sleep, serve, Glob, hasher, gunzip, stdin, SQLite, packageRoot,
  config imports with a type-stripping fallback for Node 20); every Bun.* use in cli/ goes through it
- tools/gen-embedded.ts -> cli/lib/embedded.ts: templates/, cli/shots/*.html and Play's Data safety form embedded
  (replaces Bun text imports; the bundle and the compiled sidecar carry them), checked fresh by bun run ci
- package.json: bin dist/storebound.mjs (Node ESM bundle, tools/build-node.ts, prepack), engines node >=20
- bun run ci: node bundle + node smoke (real node: --version, help, check same as Bun, scan, release plan --help,
  init, MCP handshake + tool call); Forgejo CI sets up Node 20; tests/node-compat.test.ts guards cli/
- install writes npx MCP commands when run with Node; bin/storebound falls back to Node
- docs: npx storebound init (Node 20+) or bunx storebound init
This commit is contained in:
CRTkafa 2026-10-13 04:03:09 +08:00
parent 614c32bcba
commit 36a8bf9625
73 changed files with 1195 additions and 251 deletions

View file

@ -33,6 +33,14 @@ const add = (t: Task) => tasks.push(t);
const tsc = (name: string, project: string) => add({ name, cmd: ['bunx', 'tsc', '--noEmit', '--incremental', '-p', project,
'--tsBuildInfoFile', join(cache, `${name.replace(/\W+/g, '-')}.tsbuildinfo`)] });
const junitDir = mkdtempSync(join(tmpdir(), 'sb-ci-'));
const node = Bun.which('node');
/** The npm bundle (dist/storebound.mjs, Node 20+) and its smoke test with the real `node` (tools/node-smoke.ts). */
function addNode() {
add({ name: 'node bundle', cmd: ['bun', 'tools/build-node.ts'] });
if (node) add({ name: 'node smoke', cmd: ['bun', 'tools/node-smoke.ts', '--node', node], after: ['node bundle'] });
else console.log('ci: no `node` on PATH: the Node smoke test of the npm bundle is skipped');
}
function addTests(files: string[]) {
planShards(files).forEach((shard, i, all) => {
@ -53,7 +61,9 @@ if (quick) {
console.log(`ci:quick: ${changed === null ? 'no origin/main to diff against: every test' : `${changed.length} changed file(s), ${related.length} related test file(s)`}`);
if (far.length) console.log(`ci:quick: left out ${far.length} browser suite(s) reached only through shared modules (bun run ci runs them): ${far.join(' ')}`);
add({ name: 'adapters', cmd: ['bun', 'tools/gen-adapters.ts', '--check'] });
add({ name: 'embedded', cmd: ['bun', 'tools/gen-embedded.ts', '--check'] });
tsc('typecheck', '.');
if (touches(/^(cli\/|templates\/|tools\/(build-node|node-smoke|gen-embedded)\.ts$|package\.json$)/)) addNode();
if (touches(/^apps\/desktop\//)) tsc('desktop typecheck', 'apps/desktop');
if (related.length) addTests(related);
add({ name: 'scrub', cmd: ['bun', 'tools/scrub.ts'] });
@ -63,6 +73,9 @@ if (quick) {
if (!cargo) console.log('ci: cargo not found (or STOREBOUND_CI_RUST=0): desktop Rust tests and clippy skipped');
else if (!rust) console.log('ci: apps/desktop/src-tauri and the sidecar build unchanged against origin/main: desktop Rust tests and clippy skipped (CI=full runs them)');
add({ name: 'adapters', cmd: ['bun', 'tools/gen-adapters.ts', '--check'] });
// templates/, cli/shots/*.html and Play's form export, embedded in cli/lib/embedded.ts (every runtime and build)
add({ name: 'embedded', cmd: ['bun', 'tools/gen-embedded.ts', '--check'] });
addNode();
// the App Store Connect / Google Play operation tables, the wrapped-by scan and docs/api-coverage.md match specs/ and cli/
add({ name: 'api tables', cmd: ['bun', 'tools/gen-api.ts', '--check'] });
// one desktop version: package.json desktopVersion = apps/desktop/package.json = tauri.conf.json = Cargo.toml / .lock