diff --git a/src/bunfig/arguments.rs b/src/bunfig/arguments.rs index 897a9a9fe0bb..662643ace673 100644 --- a/src/bunfig/arguments.rs +++ b/src/bunfig/arguments.rs @@ -105,7 +105,7 @@ fn load_global_bunfig(cmd: CommandTag, ctx: Context<'_>) -> Result<(), crate::Er Ok(()) } -pub fn load_config_path( +fn load_config_path( cmd: CommandTag, auto_loaded: bool, config_path: &ZStr, @@ -249,3 +249,18 @@ pub fn load_config_with_cmd_args( ) -> Result<(), crate::Error> { load_config(cmd, args.option(b"--config"), ctx) } + +/// `bun run`, `bun repl`, the `node` shim and compiled executables load `./bunfig.toml` here. +pub fn load_cwd_config_or_exit(ctx: Context<'_>) { + if ctx.debug.loaded_bunfig { + return; + } + if let Err(err) = load_config_path( + CommandTag::RunCommand, + true, + bun_core::zstr!("bunfig.toml"), + ctx, + ) { + report_bunfig_load_failure(ctx.log, err); + } +} diff --git a/src/bunfig/lib.rs b/src/bunfig/lib.rs index 4786aad2517d..a2c89e6f53e7 100644 --- a/src/bunfig/lib.rs +++ b/src/bunfig/lib.rs @@ -12,5 +12,5 @@ pub mod arguments; pub mod bunfig; pub mod error; -pub use arguments::{load_config, load_config_path, load_config_with_cmd_args}; +pub use arguments::{load_config, load_config_with_cmd_args, load_cwd_config_or_exit}; pub use error::{Error, Result}; diff --git a/src/runtime/cli/Arguments.rs b/src/runtime/cli/Arguments.rs index 925b975e07eb..a1018629145d 100644 --- a/src/runtime/cli/Arguments.rs +++ b/src/runtime/cli/Arguments.rs @@ -773,7 +773,7 @@ pub(crate) static Bun__Node__UseSystemCA: core::sync::atomic::AtomicBool = // their private helpers moved to `bun_bunfig::arguments` so `bun_install` can // call them without a tier-6 dependency. Re-export here so existing // `crate::cli::arguments::load_config*` callers are unaffected. -pub use bun_bunfig::arguments::{load_config_path, load_config_with_cmd_args}; +pub use bun_bunfig::arguments::{load_config_with_cmd_args, load_cwd_config_or_exit}; /// node aliases `-pe` to `--print --eval` as a whole token (node_options.cc): /// it can't be a short in either runtime, being ambiguous with `-p` carrying diff --git a/src/runtime/cli/repl_command.rs b/src/runtime/cli/repl_command.rs index 826be93d3e65..37d198b119da 100644 --- a/src/runtime/cli/repl_command.rs +++ b/src/runtime/cli/repl_command.rs @@ -45,15 +45,7 @@ impl ReplCommand { ctx: Command::Context<'_>, repl: &mut Repl<'r>, ) -> Result<(), crate::Error> { - // Load bunfig if not already loaded - if !ctx.debug.loaded_bunfig { - Arguments::load_config_path( - Command::Tag::RunCommand, - true, - bun_core::zstr!("bunfig.toml"), - ctx, - )?; - } + Arguments::load_cwd_config_or_exit(ctx); jsc::initialize(jsc::InitializeOptions { eval_mode: true, diff --git a/src/runtime/cli/run_command.rs b/src/runtime/cli/run_command.rs index aba13b40f541..865439a04109 100644 --- a/src/runtime/cli/run_command.rs +++ b/src/runtime/cli/run_command.rs @@ -29,7 +29,7 @@ use bun_which::which; use crate::cli; use crate::cli::arguments; -use crate::cli::command::{ContextData, Tag as CommandTag}; +use crate::cli::command::ContextData; use crate::cli::shell_completions::ShellCompletions; bun_core::declare_scope!(RUN_LOG, visible); @@ -936,14 +936,7 @@ Full documentation is available at https://bun.com/docs/cli/run entry_path: Box<[u8]>, loader: Option, ) -> crate::Result<()> { - if !ctx.debug.loaded_bunfig { - arguments::load_config_path( - CommandTag::RunCommand, - true, - bun_core::zstr!("bunfig.toml"), - ctx, - )?; - } + arguments::load_cwd_config_or_exit(ctx); // The shell does not need to initialize JSC (saves 1-3ms). if strings::has_suffix_comptime(&entry_path, b".sh") { @@ -1136,13 +1129,8 @@ Full documentation is available at https://bun.com/docs/cli/run // Load bunfig.toml unless disabled by compile flags. Config loading // with execArgv is handled earlier in `Command::start` via `init()`. - if !ctx.debug.loaded_bunfig && !graph.flags.contains(GraphFlags::DISABLE_AUTOLOAD_BUNFIG) { - arguments::load_config_path( - CommandTag::RunCommand, - true, - bun_core::zstr!("bunfig.toml"), - ctx, - )?; + if !graph.flags.contains(GraphFlags::DISABLE_AUTOLOAD_BUNFIG) { + arguments::load_cwd_config_or_exit(ctx); } // layering — `Options::graph` is the resolver's trait object @@ -2326,16 +2314,7 @@ impl RunCommand { } } - if !ctx.debug.loaded_bunfig { - // `Arguments::load_config_path` — loads global bunfig (if the - // command opts in via `read_global_config`) then `bunfig.toml`. - let _ = arguments::load_config_path( - CommandTag::RunCommand, - true, - bun_core::zstr!("bunfig.toml"), - ctx, - ); - } + arguments::load_cwd_config_or_exit(ctx); // ── try fast run (file exists & not a dir → boot VM) ──────────────── if try_fast_run && Self::maybe_open_with_bun_js(ctx, target_name) { diff --git a/test/bundler/bundler_compile_autoload.test.ts b/test/bundler/bundler_compile_autoload.test.ts index 6b4338c26985..73d3a5128d17 100644 --- a/test/bundler/bundler_compile_autoload.test.ts +++ b/test/bundler/bundler_compile_autoload.test.ts @@ -1,4 +1,4 @@ -import { describe } from "bun:test"; +import { describe, expect } from "bun:test"; import { itBundled } from "./expectBundled"; // Not describe.concurrent: the backend:"cli" cases each spawn a full @@ -152,6 +152,39 @@ console.log("PRELOAD"); }, }); + // A bunfig.toml that does not parse stops the executable with the same report as `bun run`. + itBundled("compile/AutoloadBunfigDoesNotParse", { + compile: true, + files: { + "/entry.ts": /* js */ ` + console.log("ENTRY"); + `, + }, + runtimeFiles: { + "/bunfig.toml": `[run]\nbun = "yes"\n`, + }, + run: { + exitCode: 1, + setCwd: true, + // With flags in BUN_OPTIONS the executable loads the config during argument parsing instead. + env: { BUN_OPTIONS: "" }, + validate({ stdout, stderr }) { + expect({ stdout, stderr }).toEqual({ + stdout: "", + stderr: [ + '2 | bun = "yes"', + " ^", + "error: Expected boolean", + " at bunfig.toml:2:7", + "", + "Invalid Bunfig: failed to load bunfig", + "", + ].join("\n"), + }); + }, + }, + }); + // Test CLI backend with autoloadDotenv: false itBundled("compile/AutoloadDotenvDisabledCLI", { compile: { diff --git a/test/cli/install/bun-run-bunfig.test.ts b/test/cli/install/bun-run-bunfig.test.ts index f8aecb74304c..ee28f66ed4d4 100644 --- a/test/cli/install/bun-run-bunfig.test.ts +++ b/test/cli/install/bun-run-bunfig.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { realpathSync } from "fs"; +import { realpathSync, symlinkSync } from "fs"; import { bunEnv, bunExe, isWindows, tempDir, toTOMLString } from "harness"; import { join as pathJoin } from "node:path"; @@ -248,3 +248,82 @@ describe.each(["bun run", "bun"])(`%s`, cmd => { expect(result.success).toBeTrue(); }); }); + +// These entry points read ./bunfig.toml after argument parsing. A file that does not parse +// stops them with the same report as `bun ` and `bun run --filter`. +describe.concurrent("bunfig.toml that does not parse", () => { + const configs: [problem: string, bunfig: string, stderr: string[]][] = [ + [ + "a TOML syntax error", + "[install]\nregistry =\n", + [ + "2 | registry =", + " ^", + "error: Missing value after '='; values must be on the same line", + " at bunfig.toml:2:11", + "", + "SyntaxError: failed to load bunfig", + "", + ], + ], + [ + "a value of the wrong type", + '[run]\nbun = "yes"\n', + [ + '2 | bun = "yes"', + " ^", + "error: Expected boolean", + " at bunfig.toml:2:7", + "", + "Invalid Bunfig: failed to load bunfig", + "", + ], + ], + ]; + const commands: [command: string, args: string[]][] = [ + ["bun run