|
| 1 | +/** |
| 2 | + * Stage the CodeRouter daemon into `resources/cli` so electron-builder can |
| 3 | + * ship it inside the packaged app. This makes CodeRouter Studio fully |
| 4 | + * self-contained: it spawns `<resources>/cli/dist/cli.js daemon` under |
| 5 | + * Electron's bundled Node (which includes node:sqlite), so the app works on |
| 6 | + * a machine with no global Node or `coderouter` install. |
| 7 | + * |
| 8 | + * The bundled cli.js (built by tsup) keeps a handful of dependencies external |
| 9 | + * and imports them statically, so they must exist in an adjacent |
| 10 | + * node_modules: |
| 11 | + * - ink + react: the REPL renderer. Never rendered by the daemon, but ESM |
| 12 | + * resolves the static imports at load, so they must be present. |
| 13 | + * - ws: WebSocket server for the terminal PTY relay. |
| 14 | + * - @vscode/ripgrep: per-platform `rg` binary for fast context scanning. |
| 15 | + * - node-pty: native terminal backend (NAPI prebuilds). |
| 16 | + * |
| 17 | + * Pure-JS deps (ink/react/ws/ripgrep) are materialised with a real `npm |
| 18 | + * install` so their full transitive closure is correct and flat. node-pty is |
| 19 | + * copied from the workspace instead — it already carries working NAPI |
| 20 | + * prebuilds, and reinstalling it risks a native recompile. |
| 21 | + */ |
| 22 | +import { execFileSync } from 'node:child_process'; |
| 23 | +import { cpSync, existsSync, mkdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs'; |
| 24 | +import { dirname, join } from 'node:path'; |
| 25 | +import { fileURLToPath } from 'node:url'; |
| 26 | + |
| 27 | +const appDir = join(dirname(fileURLToPath(import.meta.url)), '..'); |
| 28 | +const repoRoot = join(appDir, '..', '..'); |
| 29 | +const cliDir = join(repoRoot, 'packages', 'cli'); |
| 30 | +const out = join(appDir, 'resources', 'cli'); |
| 31 | + |
| 32 | +const cliJs = join(cliDir, 'dist', 'cli.js'); |
| 33 | +if (!existsSync(cliJs)) { |
| 34 | + console.error('[stage-daemon] packages/cli/dist/cli.js not found — build the CLI first (pnpm --filter coderouter-cli build).'); |
| 35 | + process.exit(1); |
| 36 | +} |
| 37 | + |
| 38 | +// Pin to the versions declared by the CLI so the bundle matches what we test. |
| 39 | +const cliPkg = JSON.parse( |
| 40 | + (await import('node:fs')).readFileSync(join(cliDir, 'package.json'), 'utf8'), |
| 41 | +); |
| 42 | +const want = (name) => cliPkg.dependencies?.[name] ?? cliPkg.devDependencies?.[name] ?? 'latest'; |
| 43 | + |
| 44 | +rmSync(out, { recursive: true, force: true }); |
| 45 | +mkdirSync(join(out, 'dist'), { recursive: true }); |
| 46 | +cpSync(cliJs, join(out, 'dist', 'cli.js')); |
| 47 | + |
| 48 | +// 1. package.json with the pure-JS externals; `npm install` resolves the |
| 49 | +// full (flat) transitive closure, including ripgrep's per-platform binary |
| 50 | +// package (an optionalDependency). |
| 51 | +writeFileSync( |
| 52 | + join(out, 'package.json'), |
| 53 | + `${JSON.stringify( |
| 54 | + { |
| 55 | + name: 'coderouter-daemon', |
| 56 | + private: true, |
| 57 | + type: 'module', |
| 58 | + dependencies: { |
| 59 | + '@vscode/ripgrep': want('@vscode/ripgrep'), |
| 60 | + ink: want('ink'), |
| 61 | + react: want('react'), |
| 62 | + ws: want('ws'), |
| 63 | + }, |
| 64 | + }, |
| 65 | + null, |
| 66 | + 2, |
| 67 | + )}\n`, |
| 68 | +); |
| 69 | + |
| 70 | +console.log('[stage-daemon] installing daemon runtime deps (ink, react, ws, ripgrep)…'); |
| 71 | +execFileSync('npm', ['install', '--omit=dev', '--no-audit', '--no-fund', '--no-package-lock'], { |
| 72 | + cwd: out, |
| 73 | + stdio: 'inherit', |
| 74 | +}); |
| 75 | + |
| 76 | +// 2. node-pty: copy the already-built package (with NAPI prebuilds) from the |
| 77 | +// workspace to avoid a native recompile during install. |
| 78 | +const ptySrc = [join(cliDir, 'node_modules', 'node-pty'), join(repoRoot, 'node_modules', 'node-pty')].find(existsSync); |
| 79 | +if (ptySrc) { |
| 80 | + cpSync(realpathSync(ptySrc), join(out, 'node_modules', 'node-pty'), { recursive: true, dereference: true }); |
| 81 | + console.log('[stage-daemon] staged node-pty (terminal backend)'); |
| 82 | +} else { |
| 83 | + console.warn('[stage-daemon] node-pty not found in workspace — terminal will be unavailable'); |
| 84 | +} |
| 85 | + |
| 86 | +console.log(`[stage-daemon] daemon staged at ${out}`); |
0 commit comments