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

@ -1,65 +1,4 @@
// Every file under templates/, embedded as text so `storebound init` also works from the compiled desktop sidecar
// (a `bun build --compile` binary has no templates folder next to it). tests/apps.test.ts checks this list against the
// folder: add a line here when a template is added.
import t0 from '../../templates/app-privacy.md' with { type: 'text' };
import t1 from '../../templates/appstore.md' with { type: 'text' };
import t2 from '../../templates/credentials.example.json' with { type: 'text' };
import t3 from '../../templates/data-safety.csv' with { type: 'text' };
import t4 from '../../templates/declarations.md' with { type: 'text' };
import t5 from '../../templates/listing.md' with { type: 'text' };
import t6 from '../../templates/privacy-policy.html' with { type: 'text' };
import t7 from '../../templates/profiles/app/app-privacy.md' with { type: 'text' };
import t8 from '../../templates/profiles/app/appstore.md' with { type: 'text' };
import t9 from '../../templates/profiles/app/declarations.md' with { type: 'text' };
import t10 from '../../templates/profiles/app/listing.md' with { type: 'text' };
import t11 from '../../templates/profiles/app/privacy-policy.html' with { type: 'text' };
import t12 from '../../templates/profiles/app/shots-copy.json' with { type: 'text' };
import t13 from '../../templates/profiles/app/storebound.config.ts' with { type: 'text' };
import t14 from '../../templates/profiles/subscription-app/app-privacy.md' with { type: 'text' };
import t15 from '../../templates/profiles/subscription-app/appstore.md' with { type: 'text' };
import t16 from '../../templates/profiles/subscription-app/declarations.md' with { type: 'text' };
import t17 from '../../templates/profiles/subscription-app/listing.md' with { type: 'text' };
import t18 from '../../templates/profiles/subscription-app/storebound.config.ts' with { type: 'text' };
import t19 from '../../templates/shots-copy.json' with { type: 'text' };
import t20 from '../../templates/storebound.config.ts' with { type: 'text' };
import t21 from '../../templates/testers.csv' with { type: 'text' };
import tWebAppstore from '../../templates/web/appstore.svg' with { type: 'text' };
import tWebGoogleplay from '../../templates/web/googleplay.svg' with { type: 'text' };
import tWebLanding from '../../templates/web/landing.html' with { type: 'text' };
import tWfAppRelease from '../../templates/profiles/app/workflows/release.yml' with { type: 'text' };
import tWfAppBeta from '../../templates/profiles/app/workflows/beta.yml' with { type: 'text' };
import tWfRelease from '../../templates/workflows/release.yml' with { type: 'text' };
import tWfBeta from '../../templates/workflows/beta.yml' with { type: 'text' };
/** Template text by its path under templates/ (forward slashes). */
export const TEMPLATE_TEXT: Record<string, string> = {
'app-privacy.md': t0 as unknown as string,
'appstore.md': t1 as unknown as string,
'credentials.example.json': t2 as unknown as string,
'data-safety.csv': t3 as unknown as string,
'declarations.md': t4 as unknown as string,
'listing.md': t5 as unknown as string,
'privacy-policy.html': t6 as unknown as string,
'profiles/app/app-privacy.md': t7 as unknown as string,
'profiles/app/appstore.md': t8 as unknown as string,
'profiles/app/declarations.md': t9 as unknown as string,
'profiles/app/listing.md': t10 as unknown as string,
'profiles/app/privacy-policy.html': t11 as unknown as string,
'profiles/app/shots-copy.json': t12 as unknown as string,
'profiles/app/storebound.config.ts': t13 as unknown as string,
'profiles/subscription-app/app-privacy.md': t14 as unknown as string,
'profiles/subscription-app/appstore.md': t15 as unknown as string,
'profiles/subscription-app/declarations.md': t16 as unknown as string,
'profiles/subscription-app/listing.md': t17 as unknown as string,
'profiles/subscription-app/storebound.config.ts': t18 as unknown as string,
'shots-copy.json': t19 as unknown as string,
'storebound.config.ts': t20 as unknown as string,
'testers.csv': t21 as unknown as string,
'web/appstore.svg': tWebAppstore as unknown as string,
'web/googleplay.svg': tWebGoogleplay as unknown as string,
'web/landing.html': tWebLanding as unknown as string,
'profiles/app/workflows/release.yml': tWfAppRelease as unknown as string,
'profiles/app/workflows/beta.yml': tWfAppBeta as unknown as string,
'workflows/release.yml': tWfRelease as unknown as string,
'workflows/beta.yml': tWfBeta as unknown as string,
};
// Every file under templates/, embedded as text so `storebound init` also works from the npm bundle and the compiled
// desktop sidecar (neither has a templates folder next to its code). The table is generated into lib/embedded.ts by
// tools/gen-embedded.ts (bun run ci checks it is fresh).
export { TEMPLATE_TEXT } from './embedded';