diff --git a/src/bunfig/bunfig.rs b/src/bunfig/bunfig.rs index d6a3e49500c5..35fe4b47126b 100644 --- a/src/bunfig/bunfig.rs +++ b/src/bunfig/bunfig.rs @@ -404,7 +404,10 @@ impl<'a> Parser<'a> { if cmd == CommandTag::RunCommand || cmd == CommandTag::AutoCommand { if let Some(expr) = json.get(b"smol") { self.expect(&expr, ExprTag::EBoolean)?; - self.ctx.runtime_options.smol = expr.as_bool().expect("infallible: type checked"); + if !self.ctx.cli_overrides.smol { + self.ctx.runtime_options.smol = + expr.as_bool().expect("infallible: type checked"); + } } } @@ -420,8 +423,10 @@ impl<'a> Parser<'a> { if let Some(expr) = test.get(b"smol") { self.expect(&expr, ExprTag::EBoolean)?; - self.ctx.runtime_options.smol = - expr.as_bool().expect("infallible: type checked"); + if !self.ctx.cli_overrides.smol { + self.ctx.runtime_options.smol = + expr.as_bool().expect("infallible: type checked"); + } } if let Some(expr) = test.get(b"coverage") { @@ -754,7 +759,9 @@ impl<'a> Parser<'a> { self.expect_string(&prefer_expr)?; let key = prefer_expr.as_string(self.bump).unwrap_or(b""); if let Some(setting) = OFFLINE_PREFER.get(key) { - self.ctx.debug.offline_mode_setting = Some(*setting); + if !self.ctx.cli_overrides.install_prefer { + self.ctx.debug.offline_mode_setting = Some(*setting); + } } else { self.add_error( prefer_expr.loc, diff --git a/src/options_types/context.rs b/src/options_types/context.rs index 4190731b8ed8..a01a288c0650 100644 --- a/src/options_types/context.rs +++ b/src/options_types/context.rs @@ -44,6 +44,22 @@ pub struct ContextData { pub preloads: Vec>, pub has_loaded_global_config: bool, + pub cli_overrides: CliOverrides, +} + +/// Settings that were given on the command line. +/// +/// `Arguments::parse` loads bunfig.toml before it applies the flags for +/// `bun file.js` / `bun -e` / `bun test`, but `bun run ` (and the +/// `node` shim, `bun repl`) only load it afterwards, from `RunCommand`; the +/// bunfig parser skips the keys recorded here so the flag wins in that order +/// too. +#[derive(Clone, Copy, Default)] +pub struct CliOverrides { + /// `--smol` + pub smol: bool, + /// `--prefer-offline` or `--prefer-latest` + pub install_prefer: bool, } impl Default for ContextData { @@ -84,6 +100,7 @@ impl Default for ContextData { no_exit_on_error: false, preloads: Vec::new(), has_loaded_global_config: false, + cli_overrides: CliOverrides::default(), } } } diff --git a/src/runtime/cli/Arguments.rs b/src/runtime/cli/Arguments.rs index 486f92a04d5b..330fecb663ad 100644 --- a/src/runtime/cli/Arguments.rs +++ b/src/runtime/cli/Arguments.rs @@ -1171,13 +1171,19 @@ pub(crate) fn parse(cmd: CommandTag, ctx: Context<'_>) -> crate::Result) -> crate::Result { + try { + return readlinkSync("/proc/self/fd/" + fd).startsWith("/memfd:memfd-num-"); + } catch { + return false; + } + }); + console.log("mode:" + (memfd ? "smol" : "normal") + ":" + blob.size); +`; + +// Stands in for the probe source in argv so test names stay readable. +const PROBE = ""; + +type Case = [bunfig: string, argv: string[], expected: "smol" | "normal"]; + +async function runCase([bunfig, argv, expected]: Case, files: Record) { + if (bunfig) files["bunfig.toml"] = bunfig + "\n"; + using dir = tempDir("bunfig-smol", files); + await using proc = Bun.spawn({ + cmd: [bunExe(), ...argv.map(arg => (arg === PROBE ? probe : arg))], + cwd: String(dir), + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect(stdout.match(/^mode:(\w+):2097152$/m)?.[1], stderr).toBe(expected); + expect(exitCode, stderr).toBe(0); +} + +describe.skipIf(!isLinux).concurrent("bunfig.toml smol", () => { + const cases: Case[] = [ + ["", ["index.js"], "normal"], + ["smol = false", ["index.js"], "normal"], + ["[test]\nsmol = true", ["index.js"], "normal"], + // bunfig.toml is read while argv is being parsed for these. + ["smol = true", ["index.js"], "smol"], + ["smol = true", ["-e", PROBE], "smol"], + // `bun run` reads bunfig.toml after argv has been applied. + ["smol = true", ["run", "index.js"], "smol"], + // The flag wins over the file in either order. + ["smol = false", ["--smol", "index.js"], "smol"], + ["smol = false", ["run", "--smol", "index.js"], "smol"], + ["smol = false", ["--smol", "run", "index.js"], "smol"], + ]; + + test.each(cases)("%j + bun %j -> %s", (bunfig, argv, expected) => + runCase([bunfig, argv, expected], { "index.js": probe }), + ); +}); + +describe.skipIf(!isLinux).concurrent("bunfig.toml test.smol", () => { + const testFile = `import { test } from "bun:test";\ntest("probe", () => {${probe}});\n`; + + const cases: Case[] = [ + ["", ["test", "probe.test.js"], "normal"], + ["[test]\nsmol = true", ["test", "probe.test.js"], "smol"], + ["[test]\nsmol = false", ["test", "--smol", "probe.test.js"], "smol"], + ["[test]\nsmol = false", ["--smol", "test", "probe.test.js"], "smol"], + ]; + + test.each(cases)("%j + bun %j -> %s", (bunfig, argv, expected) => + runCase([bunfig, argv, expected], { "probe.test.js": testFile }), + ); +});