| 1 | // Compile the committed canonical host with an explicitly supplied local Bun. |
| 2 | // --compile-executable-path forbids Bun's implicit runtime download. Target and |
| 3 | // architecture therefore follow that binary; cross targets need a matching |
| 4 | // verified build input, not a runtime fetched during host startup. |
| 5 | import { spawnSync } from 'node:child_process' |
| 6 | import { createHash } from 'node:crypto' |
| 7 | import { readFileSync, realpathSync } from 'node:fs' |
| 8 | import { dirname, isAbsolute, resolve } from 'node:path' |
| 9 | import { fileURLToPath } from 'node:url' |
| 10 | |
| 11 | const here = dirname(fileURLToPath(import.meta.url)) |
| 12 | const args = process.argv.slice(2) |
| 13 | const value = (flag) => { |
| 14 | const index = args.indexOf(flag) |
| 15 | if (index < 0 || !args[index + 1] || args[index + 1].startsWith('--')) throw new Error(`required: ${flag} PATH`) |
| 16 | return args[index + 1] |
| 17 | } |
| 18 | const bun = value('--bun') |
| 19 | if (!isAbsolute(bun)) throw new Error('--bun must name an absolute local Bun executable') |
| 20 | const binary = realpathSync(bun) |
| 21 | const output = resolve(value('--output')) |
| 22 | const bundle = resolve(here, 'dist/codewhale-extension-host.mjs') |
| 23 | const sourceSha = createHash('sha256').update(readFileSync(bundle)).digest('hex') |
| 24 | const cleanEnv = { ...process.env, NODE_OPTIONS: '', BUN_OPTIONS: '', BUN_BE_BUN: '0', BUN_JSC_useShadowRealm: '0' } |
| 25 | // Actual selected compiler process facts, including emulation. Never label a |
| 26 | // runtime/image using the architecture of the Node driver or Rust runner. |
| 27 | const facts = spawnSync(binary, ['--no-install', '--no-env-file', `--config=${process.platform === 'win32' ? 'NUL' : '/dev/null'}`, '-p', |
| 28 | 'JSON.stringify({platform:process.platform,arch:process.arch})'], |
| 29 | { cwd: here, env: cleanEnv, encoding: 'utf8', timeout: 5000, maxBuffer: 16 * 1024 }) |
| 30 | if (facts.error || facts.status !== 0) throw facts.error ?? new Error(`compiler runtime identity failed (${facts.status})`) |
| 31 | const runtimeInfo = JSON.parse(facts.stdout) |
| 32 | if (!['linux', 'darwin', 'win32'].includes(runtimeInfo.platform) || !['x64', 'arm64'].includes(runtimeInfo.arch)) throw new Error('unsupported compiler runtime identity') |
| 33 | const run = (argv) => { |
| 34 | const result = spawnSync(binary, argv, { cwd: here, env: cleanEnv, encoding: 'utf8', timeout: 120_000, maxBuffer: 4 * 1024 * 1024 }) |
| 35 | if (result.stdout) process.stderr.write(result.stdout) |
| 36 | if (result.stderr) process.stderr.write(result.stderr) |
| 37 | if (result.error || result.status !== 0) throw result.error ?? new Error(`Bun failed (${result.status})`) |
| 38 | } |
| 39 | run([ |
| 40 | '--no-install', '--no-env-file', `--config=${process.platform === 'win32' ? 'NUL' : '/dev/null'}`, 'build', '--compile', `--compile-executable-path=${binary}`, |
| 41 | // A Windows Native host runs in an LPAC, which cannot open the NUL device; |
| 42 | // --no-compile-autoload-bunfig below already keeps bunfig.toml unread there. |
| 43 | `--compile-exec-argv=--no-install --no-env-file${process.platform === 'win32' ? '' : ' --config=/dev/null'} --no-addons`, |
| 44 | '--no-compile-autoload-dotenv', '--no-compile-autoload-bunfig', |
| 45 | '--no-compile-autoload-tsconfig', '--no-compile-autoload-package-json', |
| 46 | '--define=CODEWHALE_COMPILED_HOST=true', |
| 47 | `--define=CODEWHALE_COMPILED_BUNDLE_SHA256=${JSON.stringify(sourceSha)}`, |
| 48 | resolve(here, 'src/compiled.mjs'), '--outfile', output, |
| 49 | ]) |
| 50 | const probe = spawnSync(output, ['--codewhale-host-info'], { env: cleanEnv, encoding: 'utf8', timeout: 5000, maxBuffer: 16 * 1024 }) |
| 51 | if (probe.error || probe.status !== 0) throw probe.error ?? new Error(`compiled host probe failed (${probe.status})`) |
| 52 | const info = JSON.parse(probe.stdout) |
| 53 | if (info.kind !== 'codewhale-extension-host' || info.runtime !== 'bun' || info.bundle_sha256 !== sourceSha || typeof info.version !== 'string' || info.platform !== runtimeInfo.platform || info.arch !== runtimeInfo.arch) { |
| 54 | throw new Error('compiled host identity does not match the canonical bundle') |
| 55 | } |
| 56 | console.log(JSON.stringify({ output, ...info, compiler: runtimeInfo })) |
| 57 |