bun-child-config.ts
73 lines · browse/src/bun-child-config.ts at 3f79d81
1/**
2 * The `--config` file for every Bun child browse spawns (#3085). The child
3 * runs in the user's project, where Bun would otherwise load that project's
4 * bunfig.toml, whose preload runs code. POSIX passes /dev/null. Windows has no
5 * /dev/null and `--config=NUL` panicked Bun 1.3.10 while parsing the flag, so
6 * Windows gets an empty bunfig created exclusively in a private directory and
7 * re-checked to be a zero-byte regular file before each spawn; anything else
8 * is replaced, so the file cannot become a preload vector.
9 */
10import * as fs from 'fs';
11import * as os from 'os';
12import * as path from 'path';
14let emptyConfig: string | null = null;
16function isEmptyRegularFile(file: string): boolean {
17 try {
18 const stat = fs.lstatSync(file);
19 return stat.isFile() && stat.size === 0;
20 } catch {
21 return false;
22 }
23}
25export function emptyBunConfig(platform: NodeJS.Platform = process.platform): string {
26 if (platform !== 'win32') return '/dev/null';
27 if (emptyConfig && isEmptyRegularFile(emptyConfig)) return emptyConfig;
28 const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-bunfig-'));
29 const file = path.join(dir, 'bunfig.toml');
30 fs.closeSync(fs.openSync(file, 'wx', 0o600));
31 if (!isEmptyRegularFile(file)) throw new Error(`empty Bun config ${file} is not a zero-byte file`);
32 process.once('exit', () => fs.rmSync(dir, { recursive: true, force: true }));
33 emptyConfig = file;
34 return file;
35}
37export interface BunExeLookup {
38 env?: NodeJS.ProcessEnv;
39 execPath?: string;
40 isBun?: boolean;
41 isFile?: (p: string) => boolean;
42}
44/**
45 * The bun.exe a Windows parent spawns its bun children with, or null when
46 * none is installed: the running bun.exe itself, then BUN_INSTALL\bin, each
47 * absolute PATH entry (plus, beside npm's bun.cmd shim, the bun.exe it
48 * forwards to), then USERPROFILE\.bun\bin. The terminal agent and the native
49 * cookie-import worker (cookie-import-native.ts) both resolve through it; it
50 * lives here because this file is part of the native import's source-bound
51 * qualification (NATIVE_CODE_INPUTS), and terminal-agent-control.ts is not.
52 */
53export function findWindowsBunExe(opts: BunExeLookup = {}): string | null {
54 const win = path.win32;
55 const env = opts.env ?? process.env;
56 const execPath = opts.execPath ?? process.execPath;
57 const isBun = opts.isBun ?? !!process.versions.bun;
58 const isFile = opts.isFile ?? ((p: string) => { try { return fs.statSync(p).isFile(); } catch { return false; } });
59 // A compiled browse.exe also reports versions.bun, so check the name too.
60 if (isBun && /^bun\.exe$/i.test(win.basename(execPath))) return execPath;
61 const dirs = (env.PATH || env.Path || '').split(';').filter(d => d && win.isAbsolute(d));
62 const candidates = [
63 ...(env.BUN_INSTALL ? [win.join(env.BUN_INSTALL, 'bin', 'bun.exe')] : []),
64 ...dirs.flatMap(d => [
65 win.join(d, 'bun.exe'),
66 // npm global install: PATH holds only the bun.cmd shim; the real
67 // binary it forwards to lives under node_modules.
68 ...(isFile(win.join(d, 'bun.cmd')) ? [win.join(d, 'node_modules', 'bun', 'bin', 'bun.exe')] : []),
69 ]),
70 ...(env.USERPROFILE ? [win.join(env.USERPROFILE, '.bun', 'bin', 'bun.exe')] : []),
71 ];
72 return candidates.find(isFile) ?? null;
73}