diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 582e40668..ca36a67c1 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -61,6 +61,36 @@ export function sanitizeNamespace(name: string): string { return s; } +/** + * Native dependencies the scaffold pulls in (transitively) that need their + * build scripts to run at install time. pnpm 10+ blocks dependency build + * scripts by default; without this allowlist `better-sqlite3` (used by the + * default standalone SQLite store, via knex) ships uncompiled and `serve` + * fails with "Could not locate the bindings file". + * + * Current pnpm reads this from `pnpm-workspace.yaml`, NOT the `pnpm` field in + * package.json (that field is now ignored and emits a deprecation warning). + * npm/yarn/bun build native modules by default and ignore this file. + */ +export const SCAFFOLD_BUILT_DEPENDENCIES = ['better-sqlite3', 'esbuild']; + +/** + * Render the `pnpm-workspace.yaml` that allowlists native build scripts. + * Kept minimal (no `packages:` key) so it acts purely as a settings file for + * the single-package scaffold rather than declaring a workspace. + */ +export function renderPnpmWorkspaceYaml(builtDeps: string[] = SCAFFOLD_BUILT_DEPENDENCIES): string { + return [ + '# Allowlist native dependency build scripts so `pnpm install` compiles', + '# them (pnpm 10+ blocks build scripts by default). Without this,', + '# better-sqlite3 ships uncompiled and `objectstack serve` fails with', + '# "Could not locate the bindings file".', + 'onlyBuiltDependencies:', + ...builtDeps.map((d) => ` - ${d}`), + '', + ].join('\n'); +} + export const TEMPLATES: Record; @@ -88,7 +118,10 @@ export const TEMPLATES: Record { }); }); +describe('native build allowlist (pnpm-workspace.yaml)', () => { + // pnpm 10+ blocks dependency build scripts by default. Without an allowlist, + // the scaffold installs but `serve` crashes with "Could not locate the + // bindings file" because `better-sqlite3` shipped uncompiled. Current pnpm + // reads the allowlist from pnpm-workspace.yaml, not the package.json `pnpm` + // field (which it now ignores). + it('includes the native deps the standalone store needs', () => { + expect(SCAFFOLD_BUILT_DEPENDENCIES).toContain('better-sqlite3'); + }); + + it('does NOT put the allowlist in package.json (current pnpm ignores it)', () => { + const t = TEMPLATES.app; + // Mirror init.ts's package.json construction. + const pkgJson: Record = { + name: 'my-app', + version: '0.1.0', + private: true, + type: 'module', + scripts: t.scripts, + dependencies: t.dependencies, + devDependencies: t.devDependencies, + }; + expect(pkgJson.pnpm).toBeUndefined(); + }); + + it('renders a pnpm-workspace.yaml that allowlists better-sqlite3', () => { + const yaml = renderPnpmWorkspaceYaml(); + expect(yaml).toMatch(/^onlyBuiltDependencies:/m); + expect(yaml).toMatch(/^ {2}- better-sqlite3$/m); + // No `packages:` key — this is a settings file, not a workspace declaration. + expect(yaml).not.toMatch(/^packages:/m); + }); +}); + describe('sanitizeNamespace', () => { const NS_RE = /^[a-z][a-z0-9_]{1,19}$/;