Files
gstack/browse/test/bun-child-config.test.ts
Garry Tanandningfeemic-dev 47cd4a9f14 v1.91.86.0 fix(browse): proxy from environment, Windows 8.3 temp paths, no NUL bunfig (Wave 9 Lane E) (#3135)
browse resolves its proxy from --proxy, BROWSE_PROXY_URL, then HTTPS_PROXY,
HTTP_PROXY and ALL_PROXY (BROWSE_PROXY_URL=direct opts out), translates
NO_PROXY to Chromium bypass rules with loopback always kept, and hashes the
proxy and NO_PROXY rules into the daemon config so a differing environment is
refused with the variable named. Unparseable standard variables warn and go
direct; browse stop always stops (#915, supersedes #916).

Safe directories and targets resolve through one realpath
(fs.realpathSync.native), fixing Windows 8.3 temp paths (#3104). Bun children
get an empty private bunfig instead of --config=NUL, which panicked Bun
1.3.10 on Windows (#3085).

Co-authored-by: ningfeemic-dev <239259264+ningfeemic-dev@users.noreply.github.com>
2026-10-11 15:56:22 +00:00

84 lines
4.0 KiB
TypeScript

/**
* #3085: `--config=NUL` panicked Bun 1.3.10 on Windows at flag parsing, and the
* terminal-agent respawn loop turned that into a crash window every few
* seconds. Every Bun child browse spawns now takes its `--config` from
* emptyBunConfig(): /dev/null on POSIX, and on Windows a zero-byte bunfig in a
* private directory that is re-checked before each spawn.
*/
import { describe, expect, test } from 'bun:test';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { emptyBunConfig } from '../src/bun-child-config';
import { bunChildFlags } from '../src/terminal-agent-control';
const SRC = path.resolve(import.meta.dir, '../src');
const SPAWN_SITES = ['terminal-agent-control.ts', 'cookie-import-native.ts', 'cookie-import-native-worker.ts', 'cli.ts'];
describe('#3085 empty Bun config for spawned children', () => {
test('POSIX keeps /dev/null', () => {
expect(emptyBunConfig('linux')).toBe('/dev/null');
expect(emptyBunConfig('darwin')).toBe('/dev/null');
});
test('Windows gets a zero-byte bunfig in a private directory, reused while it stays empty', () => {
const file = emptyBunConfig('win32');
expect(path.basename(file)).toBe('bunfig.toml');
expect(path.basename(path.dirname(file))).toStartWith('gstack-bunfig-');
expect(fs.lstatSync(file).isFile()).toBe(true);
expect(fs.statSync(file).size).toBe(0);
expect(emptyBunConfig('win32')).toBe(file);
});
test('a bunfig that gained content or was swapped for a link is replaced before the next spawn', () => {
const tampered = emptyBunConfig('win32');
fs.writeFileSync(tampered, '[test]\npreload = ["./evil.ts"]\n');
const fresh = emptyBunConfig('win32');
expect(fresh).not.toBe(tampered);
expect(fs.statSync(fresh).size).toBe(0);
if (process.platform === 'win32') return;
fs.rmSync(fresh);
fs.symlinkSync(tampered, fresh);
const third = emptyBunConfig('win32');
expect(third).not.toBe(fresh);
expect(fs.lstatSync(third).isFile()).toBe(true);
expect(fs.statSync(third).size).toBe(0);
});
test("a child started with the helper's config ignores the project's bunfig preload", () => {
const project = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-bunfig-project-'));
try {
fs.writeFileSync(path.join(project, 'preload.ts'), 'console.log("PRELOAD_RAN");\n');
fs.writeFileSync(path.join(project, 'bunfig.toml'), 'preload = ["./preload.ts"]\n');
fs.writeFileSync(path.join(project, 'probe.ts'), 'console.log("PROBE_RAN");\n');
const run = (flags: string[]) => Bun.spawnSync([process.execPath, 'run', ...flags, 'probe.ts'], { cwd: project, stdout: 'pipe', stderr: 'pipe', timeout: 30_000 }).stdout.toString();
expect(run(['--no-env-file'])).toContain('PRELOAD_RAN');
for (const platform of ['win32', process.platform] as NodeJS.Platform[]) {
const out = run(['--no-env-file', `--config=${emptyBunConfig(platform)}`]);
expect(out).toContain('PROBE_RAN');
expect(out).not.toContain('PRELOAD_RAN');
}
} finally {
fs.rmSync(project, { recursive: true, force: true });
}
});
test('bunChildFlags builds the config flag from the helper on every call', () => {
expect(bunChildFlags()).toEqual(['--no-env-file', `--config=${emptyBunConfig()}`]);
});
test('no spawn passes NUL, and every --config goes through the helper', () => {
for (const file of SPAWN_SITES) {
const source = fs.readFileSync(path.join(SRC, file), 'utf8');
expect(source).not.toMatch(/['"`]NUL['"`]|=NUL\b/);
for (const flag of source.match(/['"`]--config=[^'"`]*/g) ?? []) {
expect(`${file}: ${flag}`).toBe(`${file}: ${flag.slice(0, 1)}--config=\${emptyBunConfig()}`);
}
}
const spawned = SPAWN_SITES.map(file => fs.readFileSync(path.join(SRC, file), 'utf8'));
expect(spawned.filter(source => source.includes('${emptyBunConfig()}')).length).toBe(3);
expect(spawned.filter(source => source.includes('...bunChildFlags()')).length).toBe(2);
});
});