This change makes Bun's warning output much less scary by matching Node.js behavior.
Changes:
- Add --trace-warnings CLI flag support
- Format warnings as: (bun:PID) WarningName: message
- Show stack traces only when --trace-warnings is passed
- Show helpful hint about --trace-warnings flag (only once per process)
- Warnings go to stderr (not console.warn which showed full error objects)
Before:
process.emitWarning() would dump full error object with stack trace,
making every warning look like a critical error
After:
- Without --trace-warnings: Shows minimal format with helpful hint
- With --trace-warnings: Shows stack trace like Node.js
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
### What does this PR do?
Adds support for `publicHoistPattern` in `bunfig.toml` and
`public-hoist-pattern` from `.npmrc`. This setting allows you to select
transitive packages to hoist to the root node_modules making them
available for all workspace packages.
```toml
[install]
# can be a string
publicHoistPattern = "@types*"
# or an array
publicHoistPattern = [ "@types*", "*eslint*" ]
```
`publicHoistPattern` only affects the isolated linker.
---
Adds `hoistPattern`. `hoistPattern` is the same as `publicHoistPattern`,
but applies to the `node_modules/.bun/node_modules` directory instead of
the root node_modules. Also the default value of `hoistPattern` is `*`
(everything is hoisted to `node_modules/.bun/node_modules` by default).
---
Fixes a determinism issue constructing the
`node_modules/.bun/node_modules` directory.
---
closes#23481closes#6160closes#23548
### How did you verify your code works?
Added tests for
- [x] only include patterns
- [x] only exclude patterns
- [x] mix of include and exclude
- [x] errors for unexpected expression types
- [x] excluding direct dependency (should still include)
- [x] match all with `*`
- [x] string and array expression types
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
Adds a new `--only-failures` flag to `bun test` that only displays test
failures, similar to `--dots` but without printing dots for each test.
## Motivation
When running large test suites or in CI environments, users often only
care about test failures. The existing `--dots` reporter reduces
verbosity by showing dots, but still requires visual scanning to find
failures. The `--only-failures` flag provides a cleaner output by
completely suppressing passing tests.
## Changes
- Added `--only-failures` CLI flag in `Arguments.zig`
- Added `only_failures` boolean to the test reporters struct in
`cli.zig`
- Updated test output logic in `test_command.zig` to skip non-failures
when flag is set
- Updated `jest.zig` and `bun_test.zig` to handle the new flag
- Added comprehensive tests in `only-failures.test.ts`
## Usage
```bash
bun test --only-failures
```
Example output (only shows failures):
```
test/example.test.ts:
(fail) failing test
error: expect(received).toBe(expected)
Expected: 3
Received: 2
5 pass
1 skip
2 fail
Ran 8 tests across 1 file.
```
## Test Plan
- Verified `--only-failures` flag only shows failing tests
- Verified normal test output still works without the flag
- Verified `--dots` reporter still works correctly
- Added regression tests with snapshot comparisons
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: pfg <pfg@pfg.pw>
## Summary
This PR adds support for the `--pass-with-no-tests` CLI flag to the test
runner, addressing issue #20814.
With the latest v1.2.8 release, the test runner now fails when no tests
match a filter. While this is useful for agentic coding workflows, there
are legitimate cases where the previous behavior is preferred, such as
in monorepos where a standard test file pattern is used as a filter but
not all packages contain tests.
This flag makes the test runner behave like Jest and Vitest, exiting
with code 0 when no tests are found.
## Changes
- Added `--pass-with-no-tests` flag to CLI arguments in
`src/cli/Arguments.zig`
- Added `pass_with_no_tests` field to `TestOptions` struct in
`src/cli.zig`
- Updated test runner logic in `src/cli/test_command.zig` to respect the
flag
- Added comprehensive tests in
`test/cli/test/pass-with-no-tests.test.ts`
## Test Plan
All new tests pass:
- ✅ `--pass-with-no-tests` exits with 0 when no test files found
- ✅ `--pass-with-no-tests` exits with 0 when filters match no tests
- ✅ Without flag, still exits with 1 when no tests found (preserves
existing behavior)
- ✅ `--pass-with-no-tests` still fails when actual tests fail
Closes#20814🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: pfg <pfg@pfg.pw>
Fixes#23380 - this is a use-case for the `--only` flag that I missed
Adds back the `--only` flag. When running `bun test` on a full test
suite, without this flag it will run only that test in its file, but it
will run all other tests from other files. With this flag, it will not
run things from other files.
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: pfg <pfg@pfg.pw>
## Summary
Fixes#23206
When using `test.each` with object syntax and `$variable` interpolation,
string values were being quoted (e.g., `"apple"` instead of `apple`).
This didn't match the behavior of `%s` formatting or Jest's behavior.
## Changes
- Modified `formatLabel` in `src/bun.js/test/jest.zig` to check if the
value is a primitive string and use `toString()` instead of the
formatter with `quote_strings=true`
- Added regression test in `test/regression/issue/23206.test.ts`
## Example
**Before:**
```
test.each([
{ name: "apple" },
{ name: "banana" }
])("fruit #%# is $name", fruit => {
// Test names were:
// "fruit #0 is "apple""
// "fruit #1 is "banana""
});
```
**After:**
```
test.each([
{ name: "apple" },
{ name: "banana" }
])("fruit #%# is $name", fruit => {
// Test names are now:
// "fruit #0 is apple"
// "fruit #1 is banana"
});
```
## Test plan
- [x] Added regression test that verifies both `%s` and `$name` syntax
produce consistent output
- [x] Tested with `AGENT=0` - all tests pass
- [x] Verified other primitive types (numbers, booleans) still format
correctly
- [x] Verified complex objects still use proper formatting
This matches Jest's behavior after their fix:
https://github.com/jestjs/jest/issues/7689🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: pfg <pfg@pfg.pw>
Fixes#23275
### What does this PR do?
This PR fixes a bug where `bunfig.toml` files starting with a UTF-8 BOM
(byte order mark, `U+FEFF` or bytes `0xEF 0xBB 0xBF`) would fail to
parse with an "Unexpected" error.
The fix uses Bun's existing `File.toSource()` function with
`convert_bom: true` option when loading config files. This properly
detects and strips the BOM before parsing, matching the behavior of
other file readers in Bun (like the JavaScript lexer which treats
`0xFEFF` as whitespace).
**Changes:**
- Modified `src/cli/Arguments.zig` to use `bun.sys.File.toSource()` with
BOM conversion instead of manually reading the file
- Simplified the config loading code by removing intermediate file
handle and buffer logic
### How did you verify your code works?
Added comprehensive regression tests in
`test/regression/issue/23275.test.ts` that verify:
1. ✅ `bunfig.toml` with UTF-8 BOM parses correctly without errors
2. ✅ `bunfig.toml` without BOM still works (regression test)
3. ✅ `bunfig.toml` with BOM and actual config content parses the content
correctly
All three tests pass with the debug build:
```
3 pass
0 fail
11 expect() calls
Ran 3 tests across 1 file. [6.41s]
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
### What does this PR do?
Adds a max-concurrency flag to limit the amount of concurrent tests that
run at once. Defaults to 20. Jest and Vitest both default to 5.
### How did you verify your code works?
Tests
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Outputs the seed when randomizing. Adds --seed flag to reproduce a
random order. Seeds might not produce the same order across operating
systems / bun versions.
Fixes#11847
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
This PR adds a `--randomize` flag to `bun test` that shuffles test
execution order. This helps developers catch test interdependencies and
identify flaky tests that may depend on execution order.
## Changes
- ✨ Added `--randomize` CLI flag to test command
- 🔀 Implemented test shuffling using `bun.fastRandom()` as PRNG seed
- 🧪 Added comprehensive tests to verify randomization behavior
- 📝 Tests are shuffled at the scheduling phase, properly handling
describe blocks and hooks
## Usage
```bash
# Run tests in random order
bun test --randomize
# Works with other test flags
bun test --randomize --bail
bun test mytest.test.ts --randomize
```
## Implementation Details
The randomization happens in `Order.zig`'s `generateOrderDescribe`
function, which shuffles the `current.entries.items` array when the
randomize flag is set. This ensures:
- All tests still run (just in different order)
- Hooks (beforeAll, afterAll, beforeEach, afterEach) maintain proper
relationships
- Describe blocks and their children are shuffled independently
- Each run uses a different random seed for varied execution orders
## Test Coverage
Added tests in `test/cli/test/test-randomize.test.ts` that verify:
- Tests run in random order with the flag
- All tests execute (none are skipped)
- Without the flag, tests run in consistent order
- Randomization works with describe blocks
## Example Output
```bash
# Without --randomize (consistent order)
$ bun test mytest.js
Running test 1
Running test 2
Running test 3
Running test 4
Running test 5
# With --randomize (different order each run)
$ bun test mytest.js --randomize
Running test 3
Running test 5
Running test 1
Running test 4
Running test 2
$ bun test mytest.js --randomize
Running test 2
Running test 4
Running test 5
Running test 1
Running test 3
```
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: pfg <pfg@pfg.pw>
### What does this PR do?
Resume work on https://github.com/oven-sh/bun/pull/21898
### How did you verify your code works?
Manually tested on MacOS, Windows 11 and Ubuntu 25.04. CI changes are
needed for the tests
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
## Summary
- Clarifies help text for `--reporter` and `--reporter-outfile` flags
- Improves error messages when invalid reporter formats are specified
- Makes distinction between test reporters and coverage reporters
clearer
## Changes
1. Updated help text in `Arguments.zig` to better explain:
- What formats are currently available (only 'junit' for --reporter)
- Default behavior (console output for tests)
- Requirements (--reporter-outfile needed with --reporter=junit)
2. Improved error messages to list available options when invalid
formats are used
3. Updated CLI completions to match the new help text
## Test plan
- [x] Built and tested with `bun bd`
- [x] Verified help text displays correctly: `./build/debug/bun-debug
test --help`
- [x] Tested error message for invalid reporter:
`./build/debug/bun-debug test --reporter=json`
- [x] Tested error message for missing outfile: `./build/debug/bun-debug
test --reporter=junit`
- [x] Tested error message for invalid coverage reporter:
`./build/debug/bun-debug test --coverage-reporter=invalid`
- [x] Verified junit reporter still works: `./build/debug/bun-debug test
--reporter=junit --reporter-outfile=/tmp/junit.xml`
- [x] Verified lcov coverage reporter still works:
`./build/debug/bun-debug test --coverage --coverage-reporter=lcov`
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
# bun test
Fixes#8768, Fixes#14624, Fixes#20100, Fixes#19875, Fixes#14135,
Fixes#20980, Fixes#21830, Fixes#5738, Fixes#19758, Fixes#12782,
Fixes#5585, Fixes#9548, Might fix 5996
# New features:
## Concurrent tests
Concurrent tests allow running multiple async tests at the same time.
```ts
// concurrent.test.ts
test.concurrent("this takes a while 1", async () => {
await Bun.sleep(1000);
});
test.concurrent("this takes a while 2", async () => {
await Bun.sleep(1000);
});
test.concurrent("this takes a while 3", async () => {
await Bun.sleep(1000);
});
```
Without `.concurrent`, this test file takes 3 seconds to run because
each one has to wait for the one before it to finish before it can
start.
With `.concurrent`, this file takes 1 second because all three sleeps
can run at once.
```
$> bun-after test concurrent
concurrent.test.js:
✓ this takes a while 1 [1005.36ms]
✓ this takes a while 2 [1012.51ms]
✓ this takes a while 3 [1013.15ms]
3 pass
0 fail
Ran 3 tests across 1 file. [1081.00ms]
```
To run all tests as concurrent, pass the `--concurrent` flag when
running tests.
Limitations:
- concurrent tests cannot attribute `expect()` call counts to the test,
meaning `expect.assertions()` does not function
- concurrent tests cannot use `toMatchSnapshot`. `toMatchInlineSnapshot`
is still supported.
- `beforeAll`/`afterAll` will never be executed concurrently.
`beforeEach`/`afterEach` will.
## Chaining
Chaining multiple describe/test qualifiers is now allowed. Previously,
it would fail.
```ts
// chaining-test-qualifiers.test.ts
test.failing.each([1, 2, 3])("each %i", async i => {
throw new Error(i);
});
```
```
$> bun-after test chaining-test-qualifiers
a.test.js:
✓ each 1
✓ each 2
✓ each 3
```
# Breaking changes:
## Describe ordering
Previously, describe callbacks were called immediately. Now, they are
deferred until the outer callback has finished running. The previous
order matched Jest. The new order is similar to Vitest, but does not
match exactly.
```ts
// describe-ordering.test.ts
describe("outer", () => {
console.log("outer before");
describe("inner", () => {
console.log("inner");
});
console.log("outer after");
});
```
Before, this would print
```
$> bun-before test describe-ordering
outer before
inner
outer after
```
Now, this will print
```
$> bun-after test describe-ordering
outer before
outer after
inner
```
## Test ordering
Describes are no longer always called before tests. They are now in
order.
```ts
// test-ordering.test.ts
test("one", () => {});
describe("scope", () => {
test("two", () => {});
});
test("three", () => {});
```
Before, this would print
```
$> bun-before test test-ordering
✓ scope > two
✓ one
✓ three
```
Now, this will print
```
$> bun-after test test-ordering
✓ one
✓ scope > two
✓ three
```
## Preload hooks
Previously, beforeAll in a preload ran before the first file and
afterAll ran after the last file. Now, beforeAll will run at the start
of each file and afterAll will run at the end of each file. This
behaviour matches Jest and Vitest.
```ts
// preload.ts
beforeAll(() => console.log("preload: beforeAll"));
afterAll(() => console.log("preload: afterAll"));
```
```ts
// preload-ordering-1.test.ts
test("demonstration file 1", () => {});
```
```ts
// preload-ordering-2.test.ts
test("demonstration file 2", () => {});
```
```
$> bun-before test --preload=./preload preload-ordering
preload-ordering-1.test.ts:
preload: beforeAll
✓ demonstration file 1
preload-ordering-2.test.ts:
✓ demonstration file 2
preload: afterAll
```
```
$> bun-after test --preload=./preload preload-ordering
preload-ordering-1.test.ts:
preload: beforeAll
✓ demonstration file 1
preload: afterAll
preload-ordering-2.test.ts:
preload: beforeAll
✓ demonstration file 2
preload: afterAll
```
## Describe failures
Current behaviour is that when an error is thrown inside a describe
callback, none of the tests declared there will run. Now, describes
declared inside will also not run. The new behaviour matches the
behaviour of Jest and Vitest.
```ts
// describe-failures.test.ts
describe("erroring describe", () => {
test("this test does not run because its describe failed", () => {
expect(true).toBe(true);
});
describe("inner describe", () => {
console.log("does the inner describe callback get called?");
test("does the inner test run?", () => {
expect(true).toBe(true);
});
});
throw new Error("uh oh!");
});
```
Before, the inner describe callback would be called and the inner test
would run, although the outer test would not:
```
$> bun-before test describe-failures
describe-failures.test.ts:
does the inner describe callback get called?
# Unhandled error between tests
-------------------------------
11 | throw new Error("uh oh!");
^
error: uh oh!
-------------------------------
✓ erroring describe > inner describe > does the inner test run?
1 pass
0 fail
1 error
1 expect() calls
Ran 1 test across 1 file.
Exited with code [1]
```
Now, the inner describe callback is not called at all.
```
$> bun-after test describe-failures
describe-failures.test.ts:
# Unhandled error between tests
-------------------------------
11 | throw new Error("uh oh!");
^
error: uh oh!
-------------------------------
0 pass
0 fail
1 error
Ran 0 tests across 1 file.
Exited with code [1]
```
## Hook failures
Previously, a beforeAll failure would skip subsequent beforeAll()s, the
test, and the afterAll. Now, a beforeAll failure skips any subsequent
beforeAll()s and the test, but not the afterAll.
```js
beforeAll(() => {
throw new Error("before all: uh oh!");
});
test("my test", () => {
console.log("my test");
});
afterAll(() => console.log("after all"));
```
```
$> bun-before test hook-failures
Error: before all: uh oh!
$> bun-after test hook-failures
Error: before all: uh oh!
after all
```
Previously, an async beforeEach failure would still allow the test to
run. Now, an async beforeEach failure will prevent the test from running
```js
beforeEach(() => {
await 0;
throw "uh oh!";
});
it("the test", async () => {
console.log("does the test run?");
});
```
```
$> bun-before test async-beforeeach-failure
does the test run?
error: uh oh!
uh oh!
✗ the test
$> bun-after test async-beforeeach-failure
error: uh oh!
uh oh!
✗ the test
```
## Hook timeouts
Hooks will now time out, and can have their timeout configured in an
options parameter
```js
beforeAll(async () => {
await Bun.sleep(1000);
}, 500);
test("my test", () => {
console.log("ran my test");
});
```
```
$> bun-before test hook-timeouts
ran my test
Ran 1 test across 1 file. [1011.00ms]
$> bun-after test hook-timeouts
✗ my test [501.15ms]
^ a beforeEach/afterEach hook timed out for this test.
```
## Hook execution order
beforeAll will now execute before the tests in the scope, rather than
immediately when it is called.
```ts
describe("d1", () => {
beforeAll(() => {
console.log("<d1>");
});
test("test", () => {
console.log(" test");
});
afterAll(() => {
console.log("</d1>");
});
});
describe("d2", () => {
beforeAll(() => {
console.log("<d2>");
});
test("test", () => {
console.log(" test");
});
afterAll(() => {
console.log("</d2>");
});
});
```
```
$> bun-before test ./beforeall-ordering.test.ts
<d1>
<d2>
test
</d1>
test
</d2>
$> bun-after test ./beforeall-ordering.test.ts
<d1>
test
</d1>
<d2>
test
</d2>
```
## test inside test
test() inside test() now errors rather than silently failing. Support
for this may be added in the future.
```ts
test("outer", () => {
console.log("outer");
test("inner", () => {
console.log("inner");
});
});
```
```
$> bun-before test
outer
✓ outer [0.06ms]
1 pass
0 fail
Ran 1 test across 1 file. [8.00ms]
$> bun-after test
outer
1 | test("outer", () => {
2 | console.log("outer");
3 | test("inner", () => {
^
error: Cannot call test() inside a test. Call it inside describe() instead.
✗ outer [0.71ms]
0 pass
1 fail
```
## afterAll inside test
afterAll inside a test is no longer allowed
```ts
test("test 1", () => {
afterAll(() => console.log("afterAll"));
console.log("test 1");
});
test("test 2", () => {
console.log("test 2");
});
```
```
$> bun-before
test 1
✓ test 1 [0.05ms]
test 2
✓ test 2
afterAll
$> bun-after
error: Cannot call afterAll() inside a test. Call it inside describe() instead.
✗ test 1 [1.00ms]
test 2
✓ test 2 [0.20ms]
```
# Only inside only
Previously, an outer 'describe.only' would run all tests inside it even
if there was an inner 'test.only'. Now, only the innermost only tests
are executed.
```ts
describe.only("outer", () => {
test("one", () => console.log("should not run"));
test.only("two", () => console.log("should run"));
});
```
```
$> bun-before test
should not run
should run
$> bun-after test
should run
```
With no inner only, the outer only will still run all tests:
```ts
describe.only("outer", () => {
test("test 1", () => console.log("test 1 runs"));
test("test 2", () => console.log("test 2 runs"));
});
```
# Potential follow-up work
- [ ] for concurrent tests, display headers before console.log messages
saying which test it is for
- this will need async context or similar
- refActiveExecutionEntry should also be able to know the current test
even in test.concurrent
- [ ] `test("rerun me", () => { console.log("run one time!"); });`
`--rerun-each=3` <- this runs the first and third time but not the
second time. fix.
- [ ] should to cache the JSValue created from
DoneCallback.callAsFunction
- [ ] implement retry and rerun params for tests.
- [ ] Remove finalizer on ScopeFunctions.zig by storing the data in 3
jsvalues passed in bind rather than using a custom class. We should also
migrate off of the ClassGenerator for ScopeFunctions
- [ ] support concurrent limit, how many concurrent tests are allowed to
run at a time. ie `--concurrent-limit=25`
- [ ] flag to run tests in random order
- [ ] `test.failing` should have its own style in the same way
`test.todo` passing marks as 'todo' insetead of 'passing'. right now
it's `✓` which is confusing.
- [ ] remove all instances of bun.jsc.Jest.Jest.current
- [ ] test options should be in BunTestRoot
- [ ] we will need one global still, stored in the globalobject/vm/?.
but it should not be a Jest instance.
- [ ] consider allowing test() inside test(), as well as afterEach and
afterAll. could even allow describe() too. to do this we would switch
from indices to pointers and they would be in a linked list. they would
be allocated in memorypools for perf/locality. some special
consideration is needed for making sure repeated tests lose their
temporary items. this could also improve memory usage soomewhat.
- [ ] consider using a jsc Bound Function rather than CallbackWithArgs.
bound functions allow adding arguments and they are only one value for
GC instead of many. and this removes our unnecessary three copies.
- [ ] eliminate Strong.Safe. we should be using a C++ class instead.
- [ ] consider modifying the junit reporter to print the whole describe
tree at the end instead of trying to output as test results come in. and
move it into its own file.
- [ ] expect_call_count/expect_assertions is confusing. rename to
`expect_calls`, `assert_expect_calls`. or something.
- [ ] Should make line_no be an enum with a none option and a function
to get if line nombers are enabled
- [ ] looks like we don't need to use file_id anymore (remove
`bun.jsc.Jest.Jest.runner.?.getOrPutFile(file_path).file_id;`, store the
file path directly)
- [ ] 'dot' test reporter like vitest?
- [ ] `test.failing.if(false)` errors because it can't replace mode
'failing' with mode 'skip'. this should probably be allowed instead.
- [ ] trigger timeout termination exception for `while(true) {}`
- [ ] clean up unused callbacks. as soon as we advance to the next
execution group, we can fully clean out the previous one. sometimes
within an execution sequence we can do the same.
- clean by swapping held values with undefined
- [ ] structure cache for performance for donecallback/scopefunctions
- [ ] consider migrating CallbackWithArgs to be a bound function. the
length of the bound function can exclude the specified args.
- [ ] setting both result and maybe_skip is not ideal, maybe there
should be a function to do both at once?
- [ ] try using a linked list rather than arraylist for describe/test
children, see how it affects performance
- [ ] consider a memory pool for describescope/executionentry. test if
it improves performance.
- [ ] consider making RefDataValue methods return the reason for failure
rather than ?value. that way we can improve error messages. the reason
could be a string or it could be a defined error set
- [ ] instead of 'description orelse (unnamed)', let's have description
default to 'unnamed' and not free it if it === the global that defines
that
- [ ] Add a phase before ordering results that inherits properties to
the parents. (eg inherit only from the child and inherit has_callback
from the child. and has_callback can be on describe/test individually
rather than on base). then we won't have that happening in an init()
function (terrible!)
- [ ] this test was incidentally passing because resolves.pass() wasn't
waiting for promise
```
test("fetching with Request object - issue #1527", async () => {
const server = createServer((req, res) => {
res.end();
}).listen(0);
try {
await once(server, "listening");
const body = JSON.stringify({ foo: "bar" });
const request = new Request(`http://localhost:${server.address().port}`,
{
method: "POST",
body,
});
expect(fetch(request)).resolves.pass();
} finally {
server.closeAllConnections();
}
});
```
- [ ] the error "expect.assertions() is not supported in the describe
phase, in concurrent tests, between tests, or after test execution has
completed" is not very good. we should be able to identify which of
those it is and print the right error for the context
- [ ] consider: instead of storing weak pointers to BunTest, we can
instead give the instance an id and check that it is correct when
getting the current bun test instance from the ref
- [ ] auto_killer: add three layers of auto_killer:
- preload (includes file & test)
- file (includes test)
- test
- that way at the end of the test, we kill the test processes. at the
end of the file, we kill the file processes. at the end of all, we kill
anything remaining.
AsyncLocalStorage
- store active_id & refdatavalue. active_id is a replacement for the
above weak pointers thing. refdatavalue is for determining which test it
is. this probably fits in 2×u64
- use for auto_killer so timeouts can kill even in concurrent tests
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
- Removes unused function and class expression names when
`--minify-syntax` is enabled during bundling
- Adds `--keep-names` flag to preserve original names when minifying
- Matches esbuild's minification behavior
## Problem
When minifying with `--minify-syntax`, Bun was keeping function and
class expression names even when they were never referenced, resulting
in larger bundle sizes compared to esbuild.
**Before:**
```js
export var AB = function A() { };
// Bun output: var AB = function A() {};
// esbuild output: var AB = function() {};
```
## Solution
This PR adds logic to remove unused function and class expression names
during minification, matching esbuild's behavior. Names are only removed
when:
- `--minify-syntax` is enabled
- Bundling is enabled (not transform-only mode)
- The scope doesn't contain direct eval (which could reference the name
dynamically)
- The symbol's usage count is 0
Additionally, a `--keep-names` flag has been added to preserve original
names when desired (useful for debugging or runtime reflection).
## Testing
- Updated existing test in `bundler_minify.test.ts`
- All transpiler tests pass
- Manually verified output matches esbuild for various cases
## Examples
```bash
# Without --keep-names (names removed)
bun build --minify-syntax input.js
# var AB = function() {}
# With --keep-names (names preserved)
bun build --minify-syntax --keep-names input.js
# var AB = function A() {}
```
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
## Summary
This PR implements the `--workspaces` flag for the `bun run` command,
allowing scripts to be run in all workspace packages as defined in the
`"workspaces"` field in package.json.
Fixes the infinite loop issue reported in
https://github.com/threepointone/bun-workspace-bug-repro
## Changes
- Added `--workspaces` flag to run scripts in all workspace packages
- Added `--if-present` flag to gracefully skip packages without the
script
- Root package is excluded when using `--workspaces` to prevent infinite
recursion
- Added comprehensive tests for the new functionality
## Usage
```bash
# Run "test" script in all workspace packages
bun run --workspaces test
# Skip packages that don't have the script
bun run --workspaces --if-present build
# Combine with filters
bun run --filter="@scope/*" test
```
## Behavior
The `--workspaces` flag must come **before** the script name (matching
npm's behavior):
- ✅ `bun run --workspaces test`
- ❌ `bun run test --workspaces` (treated as passthrough to script)
## Test Plan
- [x] Added test cases in `test/cli/run/workspaces.test.ts`
- [x] Verified fix for infinite loop issue in
https://github.com/threepointone/bun-workspace-bug-repro
- [x] Tested with `--if-present` flag
- [x] All tests pass locally
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
Implements the `jsxSideEffects` option to control whether JSX elements
are marked as pure for dead code elimination, matching esbuild's
behavior from their TestJSXSideEffects test case.
## Features Added
- **tsconfig.json support**: `{"compilerOptions": {"jsxSideEffects":
true}}`
- **CLI flag support**: `--jsx-side-effects`
- **Dual runtime support**: Works with both classic
(`React.createElement`) and automatic (`jsx`/`jsxs`) JSX runtimes
- **Production/Development modes**: Works in both production and
development environments
- **Backward compatible**: Default value is `false` (maintains existing
behavior)
## Behavior
- **Default (`jsxSideEffects: false`)**: JSX elements marked with `/*
@__PURE__ */` comments (can be eliminated by bundlers)
- **When `jsxSideEffects: true`**: JSX elements NOT marked as pure
(always preserved)
## Example Usage
### tsconfig.json
```json
{
"compilerOptions": {
"jsxSideEffects": true
}
}
```
### CLI
```bash
bun build --jsx-side-effects
```
### Output Comparison
```javascript
// Input: console.log(<div>test</div>);
// Default (jsxSideEffects: false):
console.log(/* @__PURE__ */ React.createElement("div", null, "test"));
// With jsxSideEffects: true:
console.log(React.createElement("div", null, "test"));
```
## Implementation Details
- Added `side_effects: bool = false` field to `JSX.Pragma` struct
- Updated tsconfig.json parser to handle `jsxSideEffects` option
- Added CLI argument parsing for `--jsx-side-effects` flag
- Modified JSX element visiting logic to respect the `side_effects`
setting
- Updated API schema with proper encode/decode support
- Enhanced test framework to support the new JSX option
## Comprehensive Test Coverage (12 Tests)
### Core Functionality (4 tests)
- ✅ Classic JSX runtime with default behavior (includes `/* @__PURE__
*/`)
- ✅ Classic JSX runtime with `side_effects: true` (no `/* @__PURE__ */`)
- ✅ Automatic JSX runtime with default behavior (includes `/* @__PURE__
*/`)
- ✅ Automatic JSX runtime with `side_effects: true` (no `/* @__PURE__
*/`)
### Production Mode (4 tests)
- ✅ Classic JSX runtime in production with default behavior
- ✅ Classic JSX runtime in production with `side_effects: true`
- ✅ Automatic JSX runtime in production with default behavior
- ✅ Automatic JSX runtime in production with `side_effects: true`
### tsconfig.json Integration (4 tests)
- ✅ Default tsconfig.json behavior (automatic runtime, includes `/*
@__PURE__ */`)
- ✅ tsconfig.json with `jsxSideEffects: true` (automatic runtime, no `/*
@__PURE__ */`)
- ✅ tsconfig.json with `jsx: "react"` and `jsxSideEffects: true`
(classic runtime)
- ✅ tsconfig.json with `jsx: "react-jsx"` and `jsxSideEffects: true`
(automatic runtime)
### Snapshot Testing
All tests include inline snapshots demonstrating the exact output
differences, providing clear documentation of the expected behavior.
### Existing Compatibility
- ✅ All existing JSX tests continue to pass
- ✅ Cross-platform Zig compilation succeeds
## Closes
Fixes#22295🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
This PR adds a new `--compile-argv` option to `bun build --compile` that
allows developers to embed runtime arguments into standalone
executables. The specified arguments are stored in the executable
metadata during compilation and provide **dual functionality**:
1. **🔧 Actually processed by Bun runtime** (like passing them on command
line)
2. **📊 Available in `process.execArgv`** (for application inspection)
This means flags like `--user-agent`, `--smol`, `--max-memory` will
actually take effect AND be visible to your application!
## Motivation & Use Cases
### 1. **Global User Agent for Web Scraping**
Perfect for @thdxr's opencode use case - the user agent actually gets
applied:
```bash
# Compile with custom user agent that ACTUALLY works
bun build --compile --compile-argv="--user-agent='OpenCode/1.0'" ./scraper.ts --outfile=opencode
# The user agent is applied by Bun runtime AND visible in execArgv
./opencode # All HTTP requests use the custom user agent!
```
### 2. **Memory-Optimized Builds**
Create builds with actual runtime memory optimizations:
```bash
# Compile with memory optimization that ACTUALLY takes effect
bun build --compile --compile-argv="--smol --max-memory=512mb" ./app.ts --outfile=app-optimized
# Bun runtime actually runs in smol mode with memory limit
```
### 3. **Performance & Debug Builds**
Different builds with different runtime characteristics:
```bash
# Production: optimized for memory
bun build --compile --compile-argv="--smol --gc-frequency=high" ./app.ts --outfile=app-prod
# Debug: with inspector enabled
bun build --compile --compile-argv="--inspect=0.0.0.0:9229" ./app.ts --outfile=app-debug
```
### 4. **Security & Network Configuration**
Embed security settings that actually apply:
```bash
# TLS and network settings that work
bun build --compile --compile-argv="--tls-min-version=1.3 --dns-timeout=5000" ./secure-app.ts
```
## How It Works
### Dual Processing Architecture
The implementation provides both behaviors:
```bash
# Compiled with: --compile-argv="--smol --user-agent=Bot/1.0"
./my-app --config=prod.json
```
**What happens:**
1. **🔧 Runtime Processing**: Bun processes `--smol` and
`--user-agent=Bot/1.0` as if passed on command line
2. **📊 Application Access**: Your app can inspect these via
`process.execArgv`
```javascript
// In your compiled application:
// 1. The flags actually took effect:
// - Bun is running in smol mode (--smol processed)
// - All HTTP requests use Bot/1.0 user agent (--user-agent processed)
// 2. You can also inspect what flags were used:
console.log(process.execArgv); // ["--smol", "--user-agent=Bot/1.0"]
console.log(process.argv); // ["./my-app", "--config=prod.json"]
// 3. Your application logic can adapt:
if (process.execArgv.includes("--smol")) {
console.log("Running in memory-optimized mode");
}
```
### Implementation Details
1. **Build Time**: Arguments stored in executable metadata
2. **Runtime Startup**:
- Arguments prepended to actual argv processing (so Bun processes them)
- Arguments also populate `process.execArgv` (so app can inspect them)
3. **Result**: Flags work as if passed on command line + visible to
application
## Example Usage
```bash
# User agent that actually works
bun build --compile --compile-argv="--user-agent='MyBot/1.0'" ./scraper.ts --outfile=scraper
# Memory optimization that actually applies
bun build --compile --compile-argv="--smol --max-memory=256mb" ./microservice.ts --outfile=micro
# Debug build with working inspector
bun build --compile --compile-argv="--inspect=127.0.0.1:9229" ./app.ts --outfile=app-debug
# Multiple working flags
bun build --compile --compile-argv="--smol --user-agent=Bot/1.0 --tls-min-version=1.3" ./secure-scraper.ts
```
## Runtime Verification
```javascript
// Check what runtime flags are active
const hasSmol = process.execArgv.includes("--smol");
const userAgent = process.execArgv.find(arg => arg.startsWith("--user-agent="))?.split("=")[1];
const maxMemory = process.execArgv.find(arg => arg.startsWith("--max-memory="))?.split("=")[1];
console.log("Memory optimized:", hasSmol);
console.log("User agent:", userAgent);
console.log("Memory limit:", maxMemory);
// These flags also actually took effect in the runtime!
```
## Changes Made
### Core Implementation
- **Arguments.zig**: Added `--compile-argv <STR>` flag with validation
- **StandaloneModuleGraph.zig**: Serialization/deserialization for
`compile_argv`
- **build_command.zig**: Pass `compile_argv` to module graph
- **cli.zig**: **Prepend arguments to actual argv processing** (so Bun
processes them)
- **node_process.zig**: **Populate `process.execArgv`** from stored
arguments
- **bun.zig**: Made `appendOptionsEnv()` public for reuse
### Testing
- **expectBundled.ts**: Added `compileArgv` test support
- **compile-argv.test.ts**: Tests verifying dual behavior
## Behavior
### Complete Dual Functionality
```javascript
// With --compile-argv="--smol --user-agent=TestBot/1.0":
// ✅ Runtime flags actually processed by Bun:
// - Memory usage optimized (--smol effect)
// - HTTP requests use TestBot/1.0 user agent (--user-agent effect)
// ✅ Flags visible to application:
process.execArgv // ["--smol", "--user-agent=TestBot/1.0"]
process.argv // ["./app", ...script-args] (unchanged)
```
## Backward Compatibility
- ✅ Purely additive feature - no breaking changes
- ✅ Optional flag - existing behavior unchanged when not used
- ✅ No impact on non-compile builds
## Perfect for @thdxr's Use Case!
```bash
# Compile opencode with working user agent
bun build --compile --compile-argv="--user-agent='OpenCode/1.0'" ./opencode.ts --outfile=opencode
# Results in:
# 1. All HTTP requests actually use OpenCode/1.0 user agent ✨
# 2. process.execArgv contains ["--user-agent=OpenCode/1.0"] for inspection ✨
```
The user agent will actually work in all HTTP requests made by the
compiled executable, not just be visible as metadata!
🚀 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude <claude@anthropic.ai>
## Summary
- Adds `--user-agent` CLI flag to allow customizing the default
User-Agent header for HTTP requests
- Maintains backward compatibility with existing default behavior
- Includes comprehensive tests
## Test plan
- [x] Added unit tests for both custom and default user-agent behavior
- [x] Tested manually with external HTTP service (httpbin.org)
- [x] Verified existing tests still pass
@thdxr I built this for you! 🎉🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>