Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/runtime/environment-variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ These environment variables are read by Bun and configure aspects of its behavio
| `FORCE_COLOR` | If `FORCE_COLOR=1`, then ANSI color output is force enabled, even if `NO_COLOR` is set. |
| `BUN_CONFIG_MAX_HTTP_REQUESTS` | Control the maximum number of concurrent HTTP requests sent by fetch and `bun install`. Defaults to `256`. If you are running into rate limits or connection issues, you can reduce this number. |
| `BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD` | If `BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD=true`, then `bun --watch` will not clear the console on reload |
| `BUN_CONFIG_ELIDE_LINES` | Maximum number of lines of script output shown when using `bun run --filter`. Defaults to `10`. The `--elide-lines <n>` argument takes precedence over this environment variable. Set to `0` to show all lines. |
| `DO_NOT_TRACK` | Disable uploading crash reports to `bun.report` on crash. On macOS & Windows, crash report uploads are enabled by default. Otherwise, telemetry is not sent yet as of May 21st, 2024, but we are planning to add telemetry in the coming weeks. If `DO_NOT_TRACK=1`, then auto-uploading crash reports and telemetry are both [disabled](https://do-not-track.dev/). |
| `BUN_OPTIONS` | Prepends command-line arguments to any Bun execution. For example, `BUN_OPTIONS="--hot"` makes `bun run dev` behave like `bun --hot run dev` |

Expand Down
1 change: 1 addition & 0 deletions src/bun_core/env_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ new!(pub BUN_CONFIG_DISABLE_ioctl_ficlonerange: boolean, "BUN_CONFIG_DISABLE_ioc
//
// It's unclear why this was done.
new!(pub BUN_CONFIG_DNS_TIME_TO_LIVE_SECONDS: unsigned, "BUN_CONFIG_DNS_TIME_TO_LIVE_SECONDS", { default: 30 });
new!(pub BUN_CONFIG_ELIDE_LINES: unsigned, "BUN_CONFIG_ELIDE_LINES", {});
// Idle timeout for HTTP client sockets (fetch / `bun install`), in seconds.
// The timer is armed when the socket opens and re-armed on every read/write;
// if it fires the request fails with `error.Timeout`. Covers the TLS
Expand Down
13 changes: 11 additions & 2 deletions src/bunfig/bunfig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -788,8 +788,17 @@ impl<'a> Parser<'a> {

if let Some(elide_lines) = run_expr.get(b"elide-lines") {
if let Some(n) = elide_lines.as_number() {
// Note: Rust `as` saturates on overflow/NaN where Zig @intFromFloat is UB
self.ctx.bundler_options.elide_lines = Some(n as usize);
// Reject negative, non-finite, fractional, and
// out-of-range values. A bare `n as usize` would
// silently truncate `0.7` to `0` (disabling elision —
// the opposite of the error-message intent) and
// saturate negatives to `0`. Mirror the CLI flag,
// which rejects these via integer parsing.
if n < 0.0 || !n.is_finite() || n.fract() != 0.0 || n > usize::MAX as f64 {
self.add_error(elide_lines.loc, b"Expected a non-negative integer")?;
} else {
self.ctx.bundler_options.elide_lines = Some(n as usize);
}
} else {
self.add_error(elide_lines.loc, b"Expected number")?;
}
Expand Down
60 changes: 29 additions & 31 deletions src/runtime/cli/Arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,31 @@ pub(crate) fn resolve_jsx_runtime(s: &[u8]) -> Result<api::JsxRuntime, bun_core:
}
}

/// Populate `ctx.bundler_options.elide_lines` from the `--elide-lines` CLI
/// flag, falling back to `BUN_CONFIG_ELIDE_LINES` when the flag is absent
/// or expanded to an empty string (e.g. `--elide-lines "$MAYBE_UNSET"`).
fn parse_elide_lines_option(args: &clap::Args<clap::Help>, ctx: Context<'_>) {
let cli_value = args.option(b"--elide-lines").filter(|v| !v.is_empty());

if let Some(elide_lines) = cli_value {
ctx.bundler_options.elide_lines = match strings::parse_int::<usize>(elide_lines, 10) {
Ok(v) => Some(v),
Err(_) => {
Output::pretty_errorln(format_args!(
"<r><red>error<r>: Invalid elide-lines: \"{}\"",
BStr::new(elide_lines)
));
Global::exit(1);
}
};
} else if let Some(value) = env_var::BUN_CONFIG_ELIDE_LINES.get() {
// `BUN_CONFIG_ELIDE_LINES` is parsed as `u64`; `elide_lines` is a
// `usize`. Clamp rather than truncate, so 32-bit targets don't lose
// the high bits for values above `usize::MAX`.
ctx.bundler_options.elide_lines = Some(usize::try_from(value).unwrap_or(usize::MAX));
}
}

pub(crate) type ParamType = clap::Param<clap::Help>;

// ─── param tables ────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -810,22 +835,6 @@ pub fn parse(cmd: CommandTag, ctx: Context<'_>) -> Result<api::TransformOptions,
ctx.parallel = args.flag(b"--parallel");
ctx.sequential = args.flag(b"--sequential");
ctx.no_exit_on_error = args.flag(b"--no-exit-on-error");

if let Some(elide_lines) = args.option(b"--elide-lines") {
if !elide_lines.is_empty() {
ctx.bundler_options.elide_lines = match strings::parse_int::<usize>(elide_lines, 10)
{
Ok(v) => Some(v),
Err(_) => {
Output::pretty_errorln(format_args!(
"<r><red>error<r>: Invalid elide-lines: \"{}\"",
BStr::new(elide_lines)
));
Global::exit(1);
}
};
}
}
}

if cmd == CommandTag::TestCommand {
Expand Down Expand Up @@ -1377,21 +1386,10 @@ pub fn parse(cmd: CommandTag, ctx: Context<'_>) -> Result<api::TransformOptions,
ctx.debug.silent = true;
}

if let Some(elide_lines) = args.option(b"--elide-lines") {
if !elide_lines.is_empty() {
ctx.bundler_options.elide_lines = match strings::parse_int::<usize>(elide_lines, 10)
{
Ok(v) => Some(v),
Err(_) => {
Output::pretty_errorln(format_args!(
"<r><red>error<r>: Invalid elide-lines: \"{}\"",
BStr::new(elide_lines)
));
Global::exit(1);
}
};
}
}
// Runs after `load_config_with_cmd_args` so the `--elide-lines` flag
// and `BUN_CONFIG_ELIDE_LINES` env var take precedence over any value
// set by `run.elide-lines` in bunfig.toml.
parse_elide_lines_option(&args, &mut *ctx);

if let Some(define) = &opts.define {
if !define.keys.is_empty() {
Expand Down
100 changes: 96 additions & 4 deletions test/cli/run/filter-workspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,10 +474,12 @@ describe("bun", () => {
elideLines,
target_pattern,
antipattern,
env = {},
}: {
elideLines: number;
elideLines?: number;
target_pattern: RegExp[];
antipattern?: RegExp[];
env?: Record<string, string | undefined>;
}) {
const dir = tempDirWithFiles("testworkspace", {
packages: {
Expand Down Expand Up @@ -509,8 +511,15 @@ describe("bun", () => {
// code path.
const { exitCode, stderr, stdout } = spawnSync({
cwd: dir,
cmd: [bunExe(), "run", "--filter", "./packages/dep0", "--elide-lines", String(elideLines), "script"],
env: { ...bunEnv, FORCE_COLOR: "1", NO_COLOR: "0" },
cmd: [
bunExe(),
"run",
"--filter",
"./packages/dep0",
...(elideLines !== undefined ? ["--elide-lines", String(elideLines)] : []),
"script",
],
env: { ...bunEnv, ...env, FORCE_COLOR: "1", NO_COLOR: "0" },
stdout: "pipe",
stderr: "pipe",
});
Expand All @@ -530,7 +539,7 @@ describe("bun", () => {
runInCwdSuccess({
cwd: dir,
pattern: "./packages/dep0",
env: { FORCE_COLOR: "1", NO_COLOR: "0" },
env: { ...env, FORCE_COLOR: "1", NO_COLOR: "0" },
target_pattern,
antipattern,
command: ["script"],
Expand Down Expand Up @@ -590,4 +599,87 @@ describe("bun", () => {
expect(stdoutval).toMatch(/(?:log_line[\s\S]*?){20}/);
expect(exitCode).toBe(0);
});

test("respects BUN_CONFIG_ELIDE_LINES environment variable", () => {
runElideLinesTest({
target_pattern: [/\[3 lines elided\]/, /(?:log_line[\s\S]*?){20}/],
antipattern: [/\[10 lines elided\]/],
env: { BUN_CONFIG_ELIDE_LINES: "17" },
});
});

test("command-line flag takes precedence over environment variable", () => {
runElideLinesTest({
elideLines: 12,
target_pattern: [/\[8 lines elided\]/, /(?:log_line[\s\S]*?){20}/],
antipattern: [/\[15 lines elided\]/],
env: { BUN_CONFIG_ELIDE_LINES: "5" },
});
});

test("empty --elide-lines value falls back to BUN_CONFIG_ELIDE_LINES", () => {
// Mimics `--elide-lines "$MAYBE_UNSET"` expanding to `--elide-lines ""`:
// the empty flag value must not shadow the env var, since neither was
// an explicit user choice.
const dir = tempDirWithFiles("testworkspace", {
packages: {
dep0: {
"index.js": Array(20).fill("console.log('log_line');").join("\n"),
"package.json": JSON.stringify({
name: "dep0",
scripts: { script: `${bunExe()} run index.js` },
}),
},
},
"package.json": JSON.stringify({ name: "ws", workspaces: ["packages/*"] }),
});

const { exitCode, stdout } = spawnSync({
cwd: dir,
cmd: [bunExe(), "run", "--filter", "./packages/dep0", "--elide-lines", "", "script"],
env: { ...bunEnv, BUN_CONFIG_ELIDE_LINES: "17", FORCE_COLOR: "1", NO_COLOR: "0" },
stdout: "pipe",
stderr: "pipe",
});

if (process.platform !== "win32") {
expect(stdout.toString()).toMatch(/\[3 lines elided\]/);
expect(stdout.toString()).not.toMatch(/\[10 lines elided\]/);
}
expect(exitCode).toBe(0);
});

test("run.elide-lines in bunfig.toml rejects fractional values", () => {
const dir = tempDirWithFiles("testworkspace", {
packages: {
dep0: {
"index.js": `console.log('hi');`,
"package.json": JSON.stringify({
name: "dep0",
scripts: { script: `${bunExe()} run index.js` },
}),
},
},
"package.json": JSON.stringify({ name: "ws", workspaces: ["packages/*"] }),
// `0.7` would otherwise silently `@intFromFloat` to `0`, which
// disables elision entirely — the opposite of what the error
// message suggests. `--config=./bunfig.toml` uses the `=` form
// because `-c` / `--config` is declared as `.one_optional` in
// clap and does not consume a following argv element; the split
// `-c ./bunfig.toml` form would leave the path as a positional
// and dispatch as `AutoCommand`.
"bunfig.toml": `[run]\nelide-lines = 0.7\n`,
});

const { exitCode, stderr } = spawnSync({
cwd: dir,
cmd: [bunExe(), "--config=./bunfig.toml", "run", "--filter", "./packages/dep0", "script"],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});

expect(stderr.toString()).toMatch(/Expected a non-negative integer/);
expect(exitCode).not.toBe(0);
});
});
Loading