#!/usr/bin/env bun // Builds the npm package's CLI: one Node ESM bundle, dist/storebound.mjs (package.json "bin"), so `npx storebound` // runs on Node 20+ and `bunx storebound` keeps working. Zero runtime dependencies: playwright stays an optional peer // resolved from the project at run time (playwright-core only in the compiled sidecar), so it is left out. // bun tools/build-node.ts write dist/storebound.mjs (npm pack / publish run it: prepack) // bun tools/build-node.ts --out write elsewhere (tests) // The bundle is checked for this machine's home and checkout paths (like the sidecar): nothing private may ship. import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { dirname, join, relative, resolve } from 'node:path'; import { privateStrings, ROOT } from './build-sidecars'; /** Packages the CLI only imports at run time from the project (or never under Node). */ export const NODE_EXTERNALS = ['playwright', 'playwright-core', 'electron', 'chromium-bidi']; export const NODE_BUNDLE = join(ROOT, 'dist', 'storebound.mjs'); export function buildNode(out = NODE_BUNDLE): { path: string; bytes: number } { const dir = mkdtempSync(join(tmpdir(), 'sb-node-')); try { const tmp = join(dir, 'storebound.mjs'); const p = Bun.spawnSync(['bun', 'build', 'cli/index.ts', '--target=node', '--format=esm', ...NODE_EXTERNALS.flatMap(e => ['--external', e]), '--outfile', tmp], { cwd: ROOT, stdout: 'pipe', stderr: 'pipe' }); if (p.exitCode !== 0) throw new Error(`bun build failed\n${p.stderr.toString()}${p.stdout.toString()}`); // the entry's own `#!/usr/bin/env bun` and Bun's `// @bun` marker go; node runs this file const body = readFileSync(tmp, 'utf8').replace(/^#!.*\n/, '').replace(/^\/\/ @bun[^\n]*\n/, ''); const js = `#!/usr/bin/env node\n${body}`; const bunGlobal = /\bBun\.[a-zA-Z]/.exec(js); if (bunGlobal) throw new Error(`the Node bundle uses the Bun global (${bunGlobal[0]}...): go through cli/lib/rt.ts`); const leaked = privateStrings().filter(s => js.includes(s)); if (leaked.length) throw new Error(`the Node bundle contains private paths (${leaked.length}); refusing to write it`); mkdirSync(dirname(out), { recursive: true }); writeFileSync(out, js); chmodSync(out, 0o755); return { path: out, bytes: statSync(out).size }; } finally { rmSync(dir, { recursive: true, force: true }); } } if (import.meta.main) { const i = process.argv.indexOf('--out'); const t = Date.now(); const r = buildNode(i >= 0 ? resolve(process.argv[i + 1]!) : NODE_BUNDLE); console.log(`node bundle -> ${relative(process.cwd(), r.path) || r.path} (${(r.bytes / 1e6).toFixed(2)} MB, ${((Date.now() - t) / 1000).toFixed(1)}s)`); }