Compare commits

...

130 Commits

Author SHA1 Message Date
Claude Bot
24395f24ed Merge remote-tracking branch 'origin/main' into pr-25604 2025-12-24 06:48:53 +00:00
Aiden Cline
822d75a380 fix(@types/bun): add missing autoloadTsconfig and autoloadPackageJson types (#25501)
### What does this PR do?

Adds missing types, fixes typo

### How did you verify your code works?

Add missing types from: 
https://github.com/oven-sh/bun/pull/25340/changes

---------

Co-authored-by: Alistair Smith <hi@alistair.sh>
2025-12-24 06:47:07 +00:00
SUZUKI Sosuke
bffccf3d5f Upgrade WebKit 2025/12/07 (#25429)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
2025-12-23 22:24:18 -08:00
robobun
0300150324 docs: fix incorrect [env] section documentation in bunfig.toml (#25634)
## Summary
- Fixed documentation that incorrectly claimed you could use `[env]` as
a TOML section to set environment variables directly
- The `env` option in bunfig.toml only controls whether automatic `.env`
file loading is disabled (via `env = false`)
- Updated to show the correct approaches: using preload scripts or
`.env` files with `--env-file`

## Test plan
- Documentation-only change, no code changes

🤖 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>
2025-12-23 15:31:12 -08:00
robobun
34a1e2adad fix: use LLVM unstable repo for Debian Trixie (13) (#25657)
## Summary

- Fix LLVM installation on Debian Trixie (13) by using the unstable
repository from apt.llvm.org

The `llvm.sh` script doesn't automatically detect that Debian Trixie
needs to use the unstable repository. This is because trixie's `VERSION`
is `13 (trixie)` rather than `testing`, and apt.llvm.org doesn't have a
dedicated trixie repository.

Without this fix, the LLVM installation falls back to Debian's main
repository packages, which don't include `libclang-rt-19-dev` (the
compiler-rt sanitizer libraries) by default. This causes builds with
ASan (AddressSanitizer) to fail with:

```
ld.lld: error: cannot open /usr/lib/llvm-19/lib/clang/19/lib/x86_64-pc-linux-gnu/libclang_rt.asan.a: No such file or directory
```

This was breaking the [Daily Docker
Build](https://github.com/oven-sh/bun-development-docker-image/actions/runs/20437290601)
in the bun-development-docker-image repo.

## Test plan

- [ ] Wait for the PR CI to pass
- [ ] After merging, the next Daily Docker Build should succeed

🤖 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>
2025-12-22 23:10:15 -08:00
robobun
8484e1b827 perf: shrink ConcurrentTask from 24 bytes to 16 bytes (#25636) 2025-12-22 12:07:24 -08:00
robobun
3898ed5e3f perf: pack boolean flags and reorder fields to reduce struct padding (#25627) 2025-12-21 17:12:42 -08:00
Jarred Sumner
c08ffadf56 perf(linux): add memfd optimizations and typed flags (#25597)
## Summary

- Add `MemfdFlags` enum to replace raw integer flags for `memfd_create`,
providing semantic clarity for different use cases (`executable`,
`non_executable`, `cross_process`)
- Add support for `MFD_EXEC` and `MFD_NOEXEC_SEAL` flags (Linux 6.3+)
with automatic fallback to older kernel flags when `EINVAL` is returned
- Use memfd + `/proc/self/fd/{fd}` path for loading embedded `.node`
files in standalone builds, avoiding disk writes entirely on Linux

## Test plan

- [ ] Verify standalone builds with embedded `.node` files work on Linux
- [ ] Verify fallback works on older kernels (pre-6.3)
- [ ] Verify subprocess stdio memfd still works correctly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-19 23:18:21 -08:00
Dylan Conway
fa983247b2 fix(create): crash when running postinstall task with --no-install (#25616)
## Summary
- Fix segmentation fault in `bun create` when using `--no-install` with
a template that has a `bun-create.postinstall` task starting with "bun "
- The bug was caused by unconditionally slicing `argv[2..]` which
created an empty array when `npm_client` was null
- Added check for `npm_client != null` before slicing

## Reproduction
```bash
# Create template with bun-create.postinstall
mkdir -p ~/.bun-create/test-template
echo '{"name":"test","bun-create":{"postinstall":"bun install"}}' > ~/.bun-create/test-template/package.json

# This would crash before the fix
bun create test-template /tmp/my-app --no-install
```

## Test plan
- [x] Verified the reproduction case crashes before the fix
- [x] Verified the reproduction case works after the fix

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-19 23:17:51 -08:00
Dylan Conway
99b0a16c33 fix: prevent out-of-bounds access in NO_PROXY parsing (#25617)
## Summary
- Fix out-of-bounds access when parsing `NO_PROXY` environment variable
with empty entries
- Empty entries (e.g., `"localhost, , example.com"`) would cause a panic
when checking if the host starts with a dot
- Skip empty entries after trimming whitespace

fixes BUN-110G
fixes BUN-128V

## Test plan
- [x] Verify `NO_PROXY="localhost, , example.com"` no longer crashes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-19 23:17:29 -08:00
Dylan Conway
085e25d5d1 fix: protect StringOrBuffer from GC in async operations (#25594)
## Summary

- Fix use-after-free crash in async zstd compression, scrypt, and
JSTranspiler operations
- When `StringOrBuffer.fromJSMaybeAsync` is called with `is_async=true`,
the buffer's JSValue is now protected from garbage collection
- Previously, the buffer could be GC'd while a worker thread was still
accessing it, causing segfaults in zstd's `HIST_count_simple` and
similar functions

Fixes BUN-167Z

## Changes

- `fromJSMaybeAsync`: Call `protect()` on buffer when `is_async=true`
- `fromJSWithEncodingMaybeAsync`: Same protection for the early return
path
- `Scrypt`: Fix cleanup to use `deinitAndUnprotect()` for async path,
add missing `deinit()` in sync path
- `JSTranspiler`: Use new protection mechanism instead of manual
`protect()`/`unprotect()` calls
- Simplify `createOnJSThread` signatures to not return errors (OOM is
handled internally)
- Update all callers to use renamed/simplified APIs

## Test plan

- [x] Code review of all callsites to verify correct protect/unprotect
pairing
- [ ] Run existing zstd tests
- [ ] Run existing scrypt tests
- [ ] Run existing transpiler tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-19 17:30:26 -08:00
Jarred Sumner
ce5c336ea5 Revert "fix: memory leaks in IPC message handling (#25602)"
This reverts commit 05b12e0ed0.

The tests did not fail with system version of Bun.
2025-12-19 17:28:54 -08:00
robobun
05b12e0ed0 fix: memory leaks in IPC message handling (#25602)
## Summary

- Add periodic memory reclamation for IPC buffers after processing
messages
- Fix missing `deref()` on `bun.String` created from `cmd` property in
`handleIPCMessage`
- Add `reclaimMemory()` function to shrink incoming buffer and send
queue when they exceed 2MB capacity
- Track message count to trigger memory reclamation every 256 messages

The incoming `ByteList` buffer and send queue `ArrayList` would grow but
never shrink, causing memory accumulation during sustained IPC
messaging.

## Test plan

- [x] Added regression tests in
`test/js/bun/spawn/spawn-ipc-memory.test.ts`
- [x] Existing IPC tests pass (`spawn.ipc.test.ts`)
- [x] Existing cluster tests pass

🤖 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>
2025-12-19 17:27:09 -08:00
Angus Comrie
d9459f8540 Fix postgres empty check when handling arrays (#25607)
### What does this PR do?
Closes #25505. This adjusts the byte length check in `DataCell:
fromBytes` to 12 bytes instead of 16, as zero-dimensional arrays will
have a shorter preamble.

### How did you verify your code works?
Test suite passes, and I've added a new test that fails in the main
branch but passes with this change. The issue only seems to crop up when
a connection is _reused_, which is curious.
2025-12-19 14:49:12 -08:00
Jarred Sumner
e79b512a9d Propagate debugger CLI config in single-file executables (#25600) 2025-12-19 09:49:02 -08:00
Claude Bot
dee17270ae Restore Next.js 16.1.0 in test files
Restore Next.js 16.1.0 and update snapshots. The dev-server test
flakiness will be investigated separately.

- next: ^16.1.0
- next-auth: 5.0.0-beta.30
- react: ^19.2.3
- react-dom: ^19.2.3

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-19 16:03:03 +00:00
Claude Bot
34fcffd1eb Revert Next.js to 15.x in test files for Bun compatibility
Next.js 16 has compatibility issues with Bun's test infrastructure.
Revert test files to Next.js 15.5.9 while keeping React 19.2.3.

Changes:
- test/integration/next-pages: next ^15.5.9, react ^19.2.3
- test/js/third_party/next-auth/fixture: next ^15.5.9, next-auth 5.0.0-beta.25
- bench/install: next ^15.5.9, next-auth 5.0.0-beta.25, react ^19.2.3

The init templates (src/init/*) still use the latest versions for
user-facing templates.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-19 15:51:35 +00:00
Claude Bot
87d2e28f6c Update next-build.test.ts snapshots for Next.js 16 / React 19
Updated lockfile snapshots to reflect new dependency versions:
- next: ^16.1.0
- react: ^19.2.3
- react-dom: ^19.2.3

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-19 14:55:46 +00:00
Claude Bot
0d2f84c625 Revert root and bench React to 18.x to fix test infrastructure
The root package.json React is used by the test infrastructure via
file: references. Upgrading it to React 19 breaks the test harness
since test/package.json still has React 18 type definitions and
react-dom 18.3.1.

Keep React 19 updates only in:
- src/init/* templates (user-facing)
- test/integration/next-pages (Next.js integration test)
- test/js/third_party/next-auth/fixture (next-auth test)
- test/bake/fixtures/react-spa-simple (bake test)
- bench/install (install benchmark with Next.js)
- bench/react-hello-world (React benchmark)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-19 11:56:39 +00:00
Claude Bot
267ebf501c Update next-auth to 5.0.0-beta.30 for Next.js 16 support
next-auth 5.0.0-beta.25 has peer dependency "next": "^14.0.0-0 || ^15.0.0"
next-auth 5.0.0-beta.30 has peer dependency "next": "^14.0.0-0 || ^15.0.0 || ^16.0.0"

This fixes the peer dependency conflict with Next.js 16.1.0.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-19 11:00:55 +00:00
Claude Bot
d75322ed05 Update lockfiles for react, react-dom, and next upgrades
Run bun install to regenerate lockfiles after updating:
- react: ^19.2.3
- react-dom: ^19.2.3
- next: ^16.1.0

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-19 09:50:21 +00:00
Claude Bot
b084d8e9bf Update react, react-dom, and next to latest versions
Update all package.json files to use the latest versions:
- react: ^19.2.3
- react-dom: ^19.2.3
- @types/react: ^19.2.3
- @types/react-dom: ^19.2.3
- next: ^16.1.0 (where applicable)
- eslint-config-next: ^16.1.0 (where applicable)

Updated files:
- src/init/react-app/package.json
- src/init/react-tailwind/package.json
- src/init/react-shadcn/package.json
- test/integration/next-pages/package.json
- test/js/third_party/next-auth/fixture/package.json
- test/bake/fixtures/react-spa-simple/package.json
- bench/install/package.json
- bench/package.json
- bench/react-hello-world/package.json
- package.json (root)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-19 09:46:58 +00:00
robobun
9902039b1f fix: memory leaks in error-handling code for Brotli, Zstd, and Zlib compression state machines (#25592)
## Summary

Fix several memory leaks in the compression libraries:

- **NativeBrotli/NativeZstd reset()** - Each call to `reset()` allocated
a new encoder/decoder without freeing the previous one
- **NativeBrotli/NativeZstd init() error paths** - If `setParams()`
failed after `stream.init()` succeeded, the instance was leaked
- **NativeZstd init()** - If `setPledgedSrcSize()` failed after context
creation, the context was leaked
- **ZlibCompressorArrayList** - After `deflateInit2_()` succeeded, if
`ensureTotalCapacityPrecise()` failed with OOM, zlib internal state was
never freed
- **NativeBrotli close()** - Now sets state to null to prevent potential
double-free (defensive)
- **LibdeflateState** - Added `deinit()` for API consistency

## Test plan

- [x] Added regression test that calls `reset()` 100k times and measures
memory growth
- [x] Test shows memory growth dropped from ~600MB to ~10MB for Brotli
- [x] Verified no double-frees by tracing code paths
- [x] Existing zlib tests pass (except pre-existing timeout in debug
build)

Before fix (system bun 1.3.3):
```
Memory growth after 100000 reset() calls: 624.38 MB  (BrotliCompress)
Memory growth after 100000 reset() calls: 540.63 MB  (BrotliDecompress)
```

After fix:
```
Memory growth after 100000 reset() calls: 11.84 MB   (BrotliCompress)
Memory growth after 100000 reset() calls: 0.16 MB    (BrotliDecompress)
```

🤖 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>
2025-12-18 21:42:14 -08:00
Dylan Conway
f3fd7506ef fix(windows): handle UV_UNKNOWN and UV_EAI_* error codes in libuv errno mapping (#25596)
## Summary
- Add missing `UV_UNKNOWN` and `UV_EAI_*` error code mappings to the
`errno()` function in `ReturnCode`
- Fixes panic "integer does not fit in destination type" on Windows when
libuv returns unmapped error codes
- Speculative fix for BUN-131E

## Root Cause
The `errno()` function was missing mappings for `UV_UNKNOWN` (-4094) and
all `UV_EAI_*` address info errors (-3000 to -3014). When libuv returned
these codes, the switch fell through to `else => null`, and the caller
at `sys_uv.zig:317` assumed success and tried to cast the negative
return code to `usize`, causing a panic.

This was triggered in `readFileWithOptions` -> `preadv` when:
- Memory-mapped file operations encounter exceptions (file
modified/truncated by another process, network drive issues)
- Windows returns error codes that libuv cannot map to standard errno
values

## Crash Report
```
Bun v1.3.5 (1e86ceb) on windows x86_64baseline []
panic: integer does not fit in destination type
sys_uv.zig:294: preadv
node_fs.zig:5039: readFileWithOptions
```

## Test plan
- [ ] This fix prevents a panic, converting it to a proper error.
Testing would require triggering `UV_UNKNOWN` from libuv, which is
difficult to do reliably (requires memory-mapped file exceptions or
unusual Windows errors).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 21:41:41 -08:00
robobun
c21c51a0ff test(security-scanner): add TTY prompt tests using Bun.Terminal (#25587)
Co-authored-by: Alistair Smith <hi@alistair.sh>
2025-12-19 05:21:44 +00:00
robobun
0bbf6c74b5 test: add describe blocks for grouping in bun-types.test.ts (#25598)
Co-authored-by: Alistair Smith <hi@alistair.sh>
2025-12-19 04:53:18 +00:00
Dylan Conway
57cbbc09e4 fix: correct off-by-one bounds checks in bundler and package installer (#25582)
## Summary

- Fix two off-by-one bounds check errors that used `>` instead of `>=`
- Both bugs could cause undefined behavior (array out-of-bounds access)
when an index equals the array length

## The Bugs

### 1. `src/install/postinstall_optimizer.zig:62`

```zig
// Before (buggy):
if (resolution > metas.len) continue;
const meta: *const Meta = &metas[resolution];  // Out-of-bounds when resolution == metas.len

// After (fixed):
if (resolution >= metas.len) continue;
```

### 2. `src/bundler/linker_context/doStep5.zig:10`

```zig
// Before (buggy):
if (id > c.graph.meta.len) return;
const resolved_exports = &c.graph.meta.items(.resolved_exports)[id];  // Out-of-bounds when id == c.graph.meta.len

// After (fixed):
if (id >= c.graph.meta.len) return;
```

## Why These Are Bugs

Valid array indices are `0` to `len - 1`. When `index == len`:
- `index > len` evaluates to `false` → check passes
- `array[index]` accesses `array[len]` → out-of-bounds / undefined
behavior

## Codebase Patterns

The rest of the codebase correctly uses `>=` for these checks:
- `lockfile.zig:484`: `if (old_resolution >= old.packages.len)
continue;`
- `lockfile.zig:522`: `if (old_resolution >= old.packages.len)
continue;`
- `LinkerContext.zig:389`: `if (source_index >= import_records_list.len)
continue;`
- `LinkerContext.zig:1667`: `if (source_index >= c.graph.ast.len) {`

## Test plan

- [x] Verified fix aligns with existing codebase patterns
- [ ] CI passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-18 18:04:28 -08:00
Jarred Sumner
7f589ffb4b Disable coderabbit enrichment 2025-12-18 18:03:23 -08:00
Francis F
cea59d7fc0 docs(sqlite): fix .run() return value documentation (#25060)
Co-authored-by: Alistair Smith <hi@alistair.sh>
2025-12-18 20:44:35 +00:00
Jarred Sumner
4ea1454e4a Delete unused workflow 2025-12-18 12:04:28 -08:00
Dylan Conway
8941a363c3 fix: dupe ca string in .npmrc to prevent use-after-free (#25563)
## Summary

- Fix use-after-free bug when parsing `ca` option from `.npmrc`
- The `ca` string was being stored directly from the parser's arena
without duplication
- Since the parser arena is freed at the end of `loadNpmrc`, this
created a dangling pointer

## The Bug

In `src/ini.zig`, the `ca` string wasn't being duplicated like all other
string properties:

```zig
// Lines 983-986 explicitly warn about this:
// Need to be very, very careful here with strings.
// They are allocated in the Parser's arena, which of course gets
// deinitialized at the end of the scope.
// We need to dupe all strings

// Line 981: Parser arena is freed here
defer parser.deinit();

// Line 1016-1020: THE BUG - string not duped!
if (out.asProperty("ca")) |query| {
    if (query.expr.asUtf8StringLiteral()) |str| {
        install.ca = .{
            .str = str,  // ← Dangling pointer after parser.deinit()!
        };
```

All other string properties in the same function correctly duplicate:
- `registry` (line 996): `try allocator.dupe(u8, str)`
- `cache` (line 1002): `try allocator.dupe(u8, str)`
- `cafile` (line 1037): `asStringCloned(allocator)`
- `ca` array items (line 1026): `asStringCloned(allocator)`

## User Impact

When a user has `ca=<certificate>` in their `.npmrc` file:
1. The certificate string is parsed and stored
2. The parser arena is freed
3. `install.ca.str` becomes a dangling pointer
4. Later TLS/SSL operations access freed memory
5. Could cause crashes, undefined behavior, or security issues

## Test plan

- Code inspection confirms this matches the pattern used for all other
string properties
- The fix adds `try allocator.dupe(u8, str)` to match `cache`,
`registry`, etc.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 19:56:25 -08:00
Dylan Conway
722ac3aa5a fix: check correct variable in subprocess stdin cleanup (#25562)
## Summary

- Fix typo in `onProcessExit` where `existing_stdin_value.isCell()` was
checked instead of `existing_value.isCell()`
- Since `existing_stdin_value` is always `.zero` at that point, the
condition was always false, making the inner block dead code

## The Bug

In `src/bun.js/api/bun/subprocess.zig:593`:

```zig
var existing_stdin_value = jsc.JSValue.zero;  // Line 590 - always .zero
if (this_jsvalue != .zero) {
    if (jsc.Codegen.JSSubprocess.stdinGetCached(this_jsvalue)) |existing_value| {
        if (existing_stdin_value.isCell()) {  // BUG! Should be existing_value
            // This block was DEAD CODE - never executed
```

Compare with the correct pattern used elsewhere:
```zig
// shell/subproc.zig:251-252 (CORRECT)
if (jsc.Codegen.JSSubprocess.stdinGetCached(subprocess.this_jsvalue)) |existing_value| {
    jsc.WebCore.FileSink.JSSink.setDestroyCallback(existing_value, 0);  // Uses existing_value
}
```

## Impact

The dead code prevented:
- Recovery of stdin from cached JS value when `weak_file_sink_stdin_ptr`
is null
- Proper cleanup via `onAttachedProcessExit` on the FileSink  
- `setDestroyCallback` cleanup in `onProcessExit`

Note: The user-visible impact was mitigated by redundant cleanup paths
in `Writable.zig` that also call `setDestroyCallback`.

## Test plan

- Code inspection confirms this is a straightforward typo fix
- Existing subprocess tests continue to pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 18:34:58 -08:00
Dylan Conway
a333d02f84 fix: correct inverted buffer allocation logic in Postgres array parsing (#25564)
## Summary

- Fix inverted buffer allocation logic when parsing strings in Postgres
arrays
- Strings larger than 16KB were incorrectly using the stack buffer
instead of dynamically allocating
- This caused spurious `InvalidByteSequence` errors for valid data

## The Bug

In `src/sql/postgres/DataCell.zig`, the condition for when to use
dynamic allocation was inverted:

```zig
// BEFORE (buggy):
const needs_dynamic_buffer = str_bytes.len < stack_buffer.len;  // TRUE when SMALL

// AFTER (fixed):
const needs_dynamic_buffer = str_bytes.len > stack_buffer.len;  // TRUE when LARGE
```

## What happened with large strings (>16KB):

1. `needs_dynamic_buffer` = false (e.g., 20000 < 16384 is false)
2. Uses `stack_buffer[0..]` which is only 16KB
3. `unescapePostgresString` hits bounds check and returns
`BufferTooSmall`
4. Error converted to `InvalidByteSequence`
5. User gets error even though data is valid

## User Impact

Users with Postgres arrays containing JSON or string elements larger
than 16KB would get spurious InvalidByteSequence errors even though
their data was perfectly valid.

## Test plan

- Code inspection confirms the logic was inverted
- The fix aligns with the intended behavior: use stack buffer for small
strings, dynamic allocation for large strings

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 18:34:17 -08:00
Dylan Conway
c1acb0b9a4 fix(shell): prevent double-close of fd when using &> redirect with builtins (#25568)
## Summary

- Fix double-close of file descriptor when using `&>` redirect with
shell builtin commands
- Add `dupeRef()` helper for cleaner reference counting semantics
- Add tests for `&>` and `&>>` redirects with builtins

## Test plan

- [x] Added tests in `test/js/bun/shell/file-io.test.ts` that reproduce
the bug
- [x] All file-io tests pass

## The Bug

When using `&>` to redirect both stdout and stderr to the same file with
a shell builtin command (e.g., `pwd &> file.txt`), the code was creating
two separate `IOWriter` instances that shared the same file descriptor.
When both `IOWriter`s were destroyed, they both tried to close the same
fd, causing an `EBADF` (bad file descriptor) error.

```javascript
import { $ } from "bun";
await $`pwd &> output.txt`; // Would crash with EBADF
```

## The Fix

1. Share a single `IOWriter` between stdout and stderr when both are
redirected to the same file, with proper reference counting
2. Rename `refSelf` to `dupeRef` for clarity across `IOReader`,
`IOWriter`, `CowFd`, and add it to `Blob` for consistency
3. Fix the `Body.Value` blob case to also properly reference count when
the same blob is assigned to multiple outputs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Latest model <noreply@anthropic.com>
2025-12-17 18:33:53 -08:00
Jarred Sumner
ffd2240c31 Bump 2025-12-17 11:42:54 -08:00
190n
fa5a5bbe55 fix: v8::Value::IsInt32()/IsUint32() edge cases (#25548)
### What does this PR do?

- fixes both functions returning false for double-encoded values (even
if the numeric value is a valid int32/uint32)
- fixes IsUint32() returning false for values that don't fit in int32
- fixes the test from #22462 not testing anything (the native functions
were being passed a callback to run garbage collection as the first
argument, so it was only ever testing what the type check APIs returned
for that function)
- extends the test to cover the first edge case above

### How did you verify your code works?

The new tests fail without these fixes.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-12-17 00:52:16 -08:00
Dylan Conway
1e86cebd74 Add bun_version to link metadata (#25545)
## Summary
- Add `bun_version` field to `link-metadata.json`
- Pass `VERSION` CMake variable to the metadata script as `BUN_VERSION`
env var

This ensures the build version is captured in the link metadata JSON
file, which is useful for tracking which version produced a given build
artifact.

## Test plan
- Build with `bun bd` and verify `link-metadata.json` includes
`bun_version`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 19:53:05 -08:00
robobun
bc47f87450 fix(ini): support env var expansion in quoted .npmrc values (#25518)
## Summary

Fixes environment variable expansion in quoted `.npmrc` values and adds
support for the `?` optional modifier.

### Changes

**Simplified quoted value handling:**
- Removed unnecessary `isProperlyQuoted` check that added complexity
without benefit
- When JSON.parse succeeds for quoted strings, expand env vars in the
result
- When JSON.parse fails for single-quoted strings like `'${VAR}'`, still
expand env vars

**Added `?` modifier support (matching npm behavior):**
- `${VAR}` - if VAR is undefined, leaves as `${VAR}` (no expansion)
- `${VAR?}` - if VAR is undefined, expands to empty string

This applies consistently to both quoted and unquoted values.

### Examples

```ini
# Env var found - all expand to the value
token = ${NPM_TOKEN}
token = "${NPM_TOKEN}"
token = '${NPM_TOKEN}'

# Env var NOT found - left as-is
token = ${NPM_TOKEN}         # → ${NPM_TOKEN}
token = "${NPM_TOKEN}"       # → ${NPM_TOKEN}
token = '${NPM_TOKEN}'       # → ${NPM_TOKEN}

# Optional modifier (?) - expands to empty if not found
token = ${NPM_TOKEN?}        # → (empty)
token = "${NPM_TOKEN?}"      # → (empty)
auth = "Bearer ${TOKEN?}"    # → Bearer 
```

### Test Plan

- Added 8 new tests for the `?` modifier covering quoted and unquoted
values
- Verified all expected values match `npm config get` behavior
- All 30 ini tests pass

🤖 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: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
2025-12-16 19:49:23 -08:00
Dylan Conway
698b004ea4 Add step in CI to upload link metadata (#25448)
### What does this PR do?

### How did you verify your code works?

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 14:30:10 -08:00
robobun
b135c207ed fix(yaml): remove YAML 1.1 legacy boolean values for YAML 1.2 compliance (#25537)
## Summary

- Remove YAML 1.1 legacy boolean values (`yes/no/on/off/y/Y`) that are
not part of the YAML 1.2 Core Schema
- Keep YAML 1.2 Core Schema compliant values: `true/True/TRUE`,
`false/False/FALSE`, `null/Null/NULL`, `0x` hex, `0o` octal
- Add comprehensive roundtrip tests for YAML 1.2 compliance

**Removed (now parsed as strings):**
- `yes`, `Yes`, `YES` (were `true`)
- `no`, `No`, `NO` (were `false`)
- `on`, `On`, `ON` (were `true`)
- `off`, `Off`, `OFF` (were `false`)
- `y`, `Y` (were `true`)

This fixes a common pain point where GitHub Actions workflow files with
`on:` keys would have the key parsed as boolean `true` instead of the
string `"on"`.

## YAML 1.2 Core Schema Specification

From [YAML 1.2.2 Section 10.3.2 Tag
Resolution](https://yaml.org/spec/1.2.2/#1032-tag-resolution):

| Regular expression | Resolved to tag |
|-------------------|-----------------|
| `null \| Null \| NULL \| ~` | tag:yaml.org,2002:null |
| `/* Empty */` | tag:yaml.org,2002:null |
| `true \| True \| TRUE \| false \| False \| FALSE` |
tag:yaml.org,2002:bool |
| `[-+]? [0-9]+` | tag:yaml.org,2002:int (Base 10) |
| `0o [0-7]+` | tag:yaml.org,2002:int (Base 8) |
| `0x [0-9a-fA-F]+` | tag:yaml.org,2002:int (Base 16) |
| `[-+]? ( \. [0-9]+ \| [0-9]+ ( \. [0-9]* )? ) ( [eE] [-+]? [0-9]+ )?`
| tag:yaml.org,2002:float |
| `[-+]? ( \.inf \| \.Inf \| \.INF )` | tag:yaml.org,2002:float
(Infinity) |
| `\.nan \| \.NaN \| \.NAN` | tag:yaml.org,2002:float (Not a number) |

Note: `yes`, `no`, `on`, `off`, `y`, `n` are **not** in the YAML 1.2
Core Schema boolean list. These were removed from YAML 1.1 as noted in
[YAML 1.2 Section 1.2](https://yaml.org/spec/1.2.2/#12-yaml-history):

> The YAML 1.2 specification was published in 2009. Its primary focus
was making YAML a strict superset of JSON. **It also removed many of the
problematic implicit typing recommendations.**

## Test plan

- [x] Updated existing YAML tests to reflect YAML 1.2 Core Schema
behavior
- [x] Added roundtrip tests (stringify → parse) for YAML 1.2 compliance
- [x] Verified tests fail with system Bun (YAML 1.1 behavior) and pass
with debug build (YAML 1.2)
- [x] Run `bun bd test test/js/bun/yaml/yaml.test.ts`

🤖 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>
2025-12-16 14:29:39 -08:00
Ciro Spaciari
a1dd26d7db fix(usockets) fix last_write_failed flag (#25496)
https://github.com/oven-sh/bun/pull/25361 needs to be merged before this
PR

## Summary
- Move `last_write_failed` flag from loop-level to per-socket flag for
correctness

## Changes

- Move `last_write_failed` from `loop->data` to `socket->flags`
- More semantically correct since write status is per-socket, not
per-loop

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 14:26:42 -08:00
Christian Rishøj
7c06320d0f fix(ws): fix zlib version mismatch on Windows (segfault) (#25538)
## Summary

Fixes #24593 - WebSocket segfault on Windows when publishing large
messages with `perMessageDeflate: true`.
Also fixes #21028 (duplicate issue).
Also closes #25457 (alternative PR).

**Root cause:** 

On Windows, the C++ code was compiled against system zlib headers
(1.3.1) but linked against Bun's vendored Cloudflare zlib (1.2.8).

This version mismatch caused `deflateInit2()` to return
`Z_VERSION_ERROR` (-6), leaving the deflate stream in an invalid state.
All subsequent `deflate()` calls returned `Z_STREAM_ERROR` (-2),
producing zero output, which then caused an integer underflow when
subtracting the 4-byte trailer → segfault in memcpy.

**Fix:** 

Add `${VENDOR_PATH}/zlib` to the C++ include paths in
`cmake/targets/BuildBun.cmake`. This ensures the vendored zlib headers
are found before system headers, maintaining header/library version
consistency.

This is a simpler alternative to #25457 which worked around the issue by
using libdeflate exclusively.

## Test plan

- [x] Added regression test `test/regression/issue/24593.test.ts` with 4
test cases:
  - Large ~109KB JSON message publish (core reproduction)
  - Multiple rapid publishes (buffer corruption)
  - Broadcast to multiple subscribers
  - Messages at CORK_BUFFER_SIZE boundary (16KB)
- [x] Tests pass on Windows (was crashing before fix)
- [x] Tests pass on macOS

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 14:23:29 -08:00
robobun
dd04c57258 feat: implement V8 Value type checking APIs (#22462)
## Summary

This PR implements four V8 C++ API methods for type checking that are
commonly used by native Node.js modules:
- `v8::Value::IsMap()` - checks if value is a Map
- `v8::Value::IsArray()` - checks if value is an Array  
- `v8::Value::IsInt32()` - checks if value is a 32-bit integer
- `v8::Value::IsBigInt()` - checks if value is a BigInt

## Implementation Details

The implementation maps V8's type checking APIs to JavaScriptCore's
equivalent functionality:
- `IsMap()` uses JSC's `inherits<JSC::JSMap>()` check
- `IsArray()` uses JSC's `isArray()` function with the global object
- `IsInt32()` uses JSC's `isInt32()` method  
- `IsBigInt()` uses JSC's `isBigInt()` method

## Changes

- Added method declarations to `V8Value.h`
- Implemented the methods in `V8Value.cpp` 
- Added symbol exports to `napi.zig` (both Unix and Windows mangled
names)
- Added symbols to `symbols.txt` and `symbols.dyn`
- Added comprehensive tests in `v8-module/main.cpp` and `v8.test.ts`

## Testing

The implementation has been verified to:
- Compile successfully without errors
- Export the correct symbols in the binary
- Follow established patterns in the V8 compatibility layer

Tests cover various value types including empty and populated
Maps/Arrays, different numeric ranges, BigInts, and other JavaScript
types.

🤖 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: Jarred Sumner <jarred@jarredsumner.com>
2025-12-15 19:50:11 -08:00
robobun
344b2c1dfe fix: Response.clone() no longer locks body when body was accessed before clone (#25484)
## Summary
- Fix bug where `Response.clone()` would lock the original response's
body when `response.body` was accessed before cloning
- Apply the same fix to `Request.clone()`

## Root Cause
When `response.body` was accessed before calling `response.clone()`, the
original response's body would become locked after cloning. This
happened because:

1. When the cloned response was wrapped with `toJS()`,
`checkBodyStreamRef()` was called which moved the stream from
`Locked.readable` to `js.gc.stream` and cleared `Locked.readable`
2. The subsequent code tried to get the stream from `Locked.readable`,
which was now empty, so the body cache update was skipped
3. The JavaScript-level body property cache still held the old locked
stream

## Fix
Updated the cache update logic to:
1. For the cloned response: use `js.gc.stream.get()` instead of
`Locked.readable.get()` since `toJS()` already moved the stream
2. For the original response: use `Locked.readable.get()` which still
holds the teed stream since `checkBodyStreamRef` hasn't been called yet

## Reproduction
```javascript
const readableStream = new ReadableStream({
  start(controller) {
    controller.enqueue(new TextEncoder().encode("Hello, world!"));
    controller.close();
  },
});

const response = new Response(readableStream);
console.log(response.body?.locked); // Accessing body before clone
const cloned = response.clone();
console.log(response.body?.locked); // Expected: false, Actual: true 
console.log(cloned.body?.locked);   // Expected: false, Actual: false 
```

## Test plan
- [x] Added regression tests for `Response.clone()` in
`test/js/web/fetch/response.test.ts`
- [x] Added regression test for `Request.clone()` in
`test/js/web/request/request.test.ts`
- [x] Verified tests fail with system bun (before fix) and pass with
debug build (after fix)

🤖 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: Jarred Sumner <jarred@jarredsumner.com>
2025-12-15 18:46:02 -08:00
Ciro Spaciari
aef0b5b4a6 fix(usockets): safely handle socket reallocation during context adoption (#25361)
## Summary
- Fix use-after-free vulnerability during socket adoption by properly
tracking reallocated sockets
- Add safety checks to prevent linking closed sockets to context lists
- Properly track socket state with new `is_closed`, `adopted`, and
`is_tls` flags

## What does this PR do?

This PR improves event loop stability by addressing potential
use-after-free issues that can occur when sockets are reallocated during
adoption (e.g., when upgrading a TCP socket to TLS).

### Key Changes

**Socket State Tracking
([internal.h](packages/bun-usockets/src/internal/internal.h))**
- Added `is_closed` flag to explicitly track when a socket has been
closed
- Added `adopted` flag to mark sockets that were reallocated during
context adoption
- Added `is_tls` flag to track TLS socket state for proper low-priority
queue handling

**Safe Socket Adoption
([context.c](packages/bun-usockets/src/context.c))**
- When `us_poll_resize()` returns a new pointer (reallocation occurred),
the old socket is now:
  - Marked as closed (`is_closed = 1`)
  - Added to the closed socket cleanup list
  - Marked as adopted (`adopted = 1`)
  - Has its `prev` pointer set to the new socket for event redirection
- Added guards to
`us_internal_socket_context_link_socket/listen_socket/connecting_socket`
to prevent linking already-closed sockets

**Event Loop Handling ([loop.c](packages/bun-usockets/src/loop.c))**
- After callbacks that can trigger socket adoption (`on_open`,
`on_writable`, `on_data`), the event loop now checks if the socket was
reallocated and redirects to the new socket
- Low-priority socket handling now properly checks `is_closed` state and
uses `is_tls` flag for correct SSL handling

**Poll Resize Safety
([epoll_kqueue.c](packages/bun-usockets/src/eventing/epoll_kqueue.c))**
- Changed `us_poll_resize()` to always allocate new memory with
`us_calloc()` instead of `us_realloc()` to ensure the old pointer
remains valid for cleanup
- Now takes `old_ext_size` parameter to correctly calculate memory sizes
- Re-enabled `us_internal_loop_update_pending_ready_polls()` call in
`us_poll_change()` to ensure pending events are properly redirected

### How did you verify your code works?
Run existing CI and existing socket upgrade tests under asan build
2025-12-15 18:43:51 -08:00
robobun
740fb23315 fix(windows): improve bunx metadata validation (#25012)
## Summary

- Improved validation for bunx metadata files on Windows
- Added graceful error handling for malformed metadata instead of
crashing
- Added regression test for the fix

## Test plan

- [x] Run `bun bd test test/cli/install/bunx.test.ts -t "should not
crash on corrupted"`
- [x] Manual testing with corrupted `.bunx` files
- [x] Verified normal operation still works

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2025-12-15 18:37:09 -08:00
robobun
2dd997c4b5 fix(node): support duplicate dlopen calls with DLHandleMap (#24404)
## Summary

Fixes an issue where loading the same native module
(NODE_MODULE_CONTEXT_AWARE) multiple times would fail with:
```
symbol 'napi_register_module_v1' not found in native module
```

Fixes https://github.com/oven-sh/bun/issues/23136
Fixes https://github.com/oven-sh/bun/issues/21432

## Root Cause

When a native module is loaded for the first time:
1. `dlopen()` loads the shared library
2. Static constructors run and call `node_module_register()`
3. The module registers successfully

On subsequent loads of the same module:
1. `dlopen()` returns the same handle (library already loaded)
2. Static constructors **do not run again**
3. No registration occurs, leading to the "symbol not found" error

## Solution

Implemented a thread-safe `DLHandleMap` to cache and replay module
registrations:

1. **Thread-local storage** captures the `node_module*` during static
constructor execution
2. **After successful first load**, save the registration to the global
map
3. **On subsequent loads**, look up the cached registration and replay
it

This approach matches Node.js's `global_handle_map` implementation.

## Changes

- Created `src/bun.js/bindings/DLHandleMap.h` - thread-safe singleton
cache
- Added thread-local storage in `src/bun.js/bindings/v8/node.cpp`
- Modified `src/bun.js/bindings/BunProcess.cpp` to save/lookup cached
modules
- Also includes the exports fix (using `toObject()` to match Node.js
behavior)

## Test Plan

Added `test/js/node/process/dlopen-duplicate-load.test.ts` with tests
that:
- Build a native addon using node-gyp
- Load it twice with `process.dlopen`
- Verify both loads succeed
- Test with different exports objects

All tests pass.

## Related Issue

Fixes the second bug discovered in the segfault investigation.

---------

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>
2025-12-15 18:35:26 -08:00
robobun
4061e1cb4f fix: handle EINVAL from copy_file_range on eCryptfs (#25534)
## Summary
- Add `EINVAL` and `OPNOTSUPP` to the list of errors that trigger
fallback from `copy_file_range` to `sendfile`/read-write loop
- Fixes `Bun.write` and `fs.copyFile` failing on eCryptfs filesystems

## Test plan
- [x] Existing `copyFile` tests pass (`bun bd test
test/js/node/fs/fs.test.ts -t "copyFile"`)
- [x] Existing `copy_file_range` fallback tests pass (`bun bd test
test/js/bun/io/bun-write.test.js -t "should work when copyFileRange is
not available"`)

Fixes #13968

🤖 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: Jarred Sumner <jarred@jarredsumner.com>
2025-12-15 17:47:08 -08:00
robobun
6386eef8aa fix(bunx): handle empty string arguments on Windows (#25025)
## Summary

Fixes #13316
Fixes #18275

Running `bunx cowsay ""` (or any package with an empty string argument)
on Windows caused a panic. Additionally, `bunx concurrently "command
with spaces"` was splitting quoted arguments incorrectly.

**Repro #13316:**
```bash
bunx cowsay ""
# panic(main thread): reached unreachable code
```

**Repro #18275:**
```bash
bunx concurrently "bun --version" "bun --version"
# Only runs once, arguments split incorrectly
# Expected: ["bun --version", "bun --version"]
# Actual: ["bun", "--version", "bun", "--version"]
```

## Root Cause

The bunx fast path on Windows bypasses libuv and calls `CreateProcessW`
directly to save 5-12ms. The command line building logic had two issues:

1. **Empty strings**: Not quoted at all, resulting in invalid command
line
2. **Arguments with spaces**: Not quoted, causing them to be split into
multiple arguments

## Solution

Implement Windows command-line argument quoting using libuv's proven
algorithm:
- Port of libuv's `quote_cmd_arg` function (process backwards + reverse)
- Empty strings become `""`
- Strings with spaces/tabs/quotes are wrapped in quotes
- Backslashes before quotes are properly escaped per Windows rules

**Why not use libuv directly?**
- Normal `Bun.spawn()` uses `uv_spawn()` which handles quoting
internally
- bunx fast path bypasses libuv to save 5-12ms (calls `CreateProcessW`
directly)
- libuv's `quote_cmd_arg` is a static function (not exported)
- Solution: port the algorithm to Zig

## Test Plan

- [x] Added regression test for empty strings (#13316)
- [x] Added regression test for arguments with spaces (#18275)
- [x] Verified system bun (v1.3.3) fails both tests
- [x] Verified fix passes both tests
- [x] Implementation based on battle-tested libuv algorithm

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <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>
2025-12-15 17:29:04 -08:00
robobun
3394fd3bdd fix(node:url): return empty string for invalid domains in domainToASCII/domainToUnicode (#25196)
## Summary
- Fixes `url.domainToASCII` and `url.domainToUnicode` to return empty
string instead of throwing `TypeError` when given invalid domains
- Per Node.js docs: "if `domain` is an invalid domain, the empty string
is returned"

## Test plan
- [x] Run `bun bd test test/regression/issue/24191.test.ts` - all 2
tests pass
- [x] Verify tests fail with system Bun (`USE_SYSTEM_BUN=1`) to confirm
fix validity
- [x] Manual verification: `url.domainToASCII('xn--iñvalid.com')`
returns `""`

## Example

Before (bug):
```
$ bun -e "import url from 'node:url'; console.log(url.domainToASCII('xn--iñvalid.com'))"
TypeError: domainToASCII failed
```

After (fixed):
```
$ bun -e "import url from 'node:url'; console.log(url.domainToASCII('xn--iñvalid.com'))"
(empty string output)
```

Closes #24191

🤖 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>
2025-12-15 17:26:32 -08:00
robobun
5a8cdc08f0 docs(bundler): add comprehensive Bun.build compile API documentation (#25536) 2025-12-15 16:20:50 -08:00
Jarred Sumner
dcc3386611 [publish images] 2025-12-15 15:34:04 -08:00
robobun
8dc79641c8 fix(http): support proxy passwords longer than 4096 characters (#25530)
## Summary
- Fixes silent 401 Unauthorized errors when using proxies with long
passwords (e.g., JWT tokens > 4096 chars)
- Bun was silently dropping proxy passwords exceeding 4095 characters,
falling through to code that only encoded the username

## Changes
- Added `PercentEncoding.decodeWithFallback` which uses a 4KB stack
buffer for the common case and falls back to heap allocation only for
larger inputs
- Updated proxy auth encoding in `AsyncHTTP.zig` to use the new fallback
method

## Test plan
- [x] Added test case that verifies passwords > 4096 chars are handled
correctly
- [x] Test fails with system bun (v1.3.3), passes with this fix
- [x] All 29 proxy tests pass

🤖 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>
2025-12-15 13:21:41 -08:00
robobun
d865ef41e2 feat: add Bun.Terminal API for pseudo-terminal (PTY) support (#25415)
## Summary

This PR adds a new `Bun.Terminal` API for creating and managing
pseudo-terminals (PTYs), enabling interactive terminal applications in
Bun.

### Features

- **Standalone Terminal**: Create PTYs directly with `new
Bun.Terminal(options)`
- **Spawn Integration**: Spawn processes with PTY attached via
`Bun.spawn({ terminal: options })`
- **Full PTY Control**: Write data, resize, set raw mode, and handle
callbacks

## Examples

### Basic Terminal with Spawn (Recommended)

```typescript
const proc = Bun.spawn(["bash"], {
  terminal: {
    cols: 80,
    rows: 24,
    data(terminal, data) {
      // Handle output from the terminal
      process.stdout.write(data);
    },
    exit(terminal, code, signal) {
      console.log(`Process exited with code ${code}`);
    },
  },
});

// Write commands to the terminal
proc.terminal.write("echo Hello from PTY!\n");
proc.terminal.write("exit\n");

await proc.exited;
proc.terminal.close();
```

### Interactive Shell

```typescript
// Create an interactive shell that mirrors to stdout
const proc = Bun.spawn(["bash", "-i"], {
  terminal: {
    cols: process.stdout.columns || 80,
    rows: process.stdout.rows || 24,
    data(term, data) {
      process.stdout.write(data);
    },
  },
});

// Forward stdin to the terminal
process.stdin.setRawMode(true);
for await (const chunk of process.stdin) {
  proc.terminal.write(chunk);
}
```

### Running Interactive Programs (vim, htop, etc.)

```typescript
const proc = Bun.spawn(["vim", "file.txt"], {
  terminal: {
    cols: process.stdout.columns,
    rows: process.stdout.rows,
    data(term, data) {
      process.stdout.write(data);
    },
  },
});

// Handle terminal resize
process.stdout.on("resize", () => {
  proc.terminal.resize(process.stdout.columns, process.stdout.rows);
});

// Forward input
process.stdin.setRawMode(true);
for await (const chunk of process.stdin) {
  proc.terminal.write(chunk);
}
```

### Capturing Colored Output

```typescript
const chunks: Uint8Array[] = [];

const proc = Bun.spawn(["ls", "--color=always"], {
  terminal: {
    data(term, data) {
      chunks.push(data);
    },
  },
});

await proc.exited;
proc.terminal.close();

// Output includes ANSI color codes
const output = Buffer.concat(chunks).toString();
console.log(output);
```

### Standalone Terminal (Advanced)

```typescript
const terminal = new Bun.Terminal({
  cols: 80,
  rows: 24,
  data(term, data) {
    console.log("Received:", data.toString());
  },
});

// Use terminal.stdin as the fd for child process stdio
const proc = Bun.spawn(["bash"], {
  stdin: terminal.stdin,
  stdout: terminal.stdin,
  stderr: terminal.stdin,
});

terminal.write("echo hello\n");

// Clean up
terminal.close();
```

### Testing TTY Detection

```typescript
const proc = Bun.spawn([
  "bun", "-e", 
  "console.log('isTTY:', process.stdout.isTTY)"
], {
  terminal: {},
});

// Output: isTTY: true
```

## API

### `Bun.spawn()` with `terminal` option

```typescript
const proc = Bun.spawn(cmd, {
  terminal: {
    cols?: number,        // Default: 80
    rows?: number,        // Default: 24  
    name?: string,        // Default: "xterm-256color"
    data?: (terminal: Terminal, data: Uint8Array) => void,
    exit?: (terminal: Terminal, code: number, signal: string | null) => void,
    drain?: (terminal: Terminal) => void,
  }
});

// Access the terminal
proc.terminal.write(data);
proc.terminal.resize(cols, rows);
proc.terminal.setRawMode(enabled);
proc.terminal.close();

// Note: proc.stdin, proc.stdout, proc.stderr return null when terminal is used
```

### `new Bun.Terminal(options)`

```typescript
const terminal = new Bun.Terminal({
  cols?: number,
  rows?: number,
  name?: string,
  data?: (terminal, data) => void,
  exit?: (terminal, code, signal) => void,
  drain?: (terminal) => void,
});

terminal.stdin;   // Slave fd (for child process)
terminal.stdout;  // Master fd (for reading)
terminal.closed;  // boolean
terminal.write(data);
terminal.resize(cols, rows);
terminal.setRawMode(enabled);
terminal.ref();
terminal.unref();
terminal.close();
await terminal[Symbol.asyncDispose]();
```

## Implementation Details

- Uses `openpty()` to create pseudo-terminal pairs
- Properly manages file descriptor lifecycle with reference counting
- Integrates with Bun's event loop via `BufferedReader` and
`StreamingWriter`
- Supports `await using` syntax for automatic cleanup
- POSIX only (Linux, macOS) - not available on Windows

## Test Results

- 80 tests passing
- Covers: construction, writing, reading, resize, raw mode, callbacks,
spawn integration, error handling, GC safety

## Changes

- `src/bun.js/api/bun/Terminal.zig` - Terminal implementation
- `src/bun.js/api/bun/Terminal.classes.ts` - Class definition for
codegen
- `src/bun.js/api/bun/subprocess.zig` - Added terminal field and getter
- `src/bun.js/api/bun/js_bun_spawn_bindings.zig` - Terminal option
parsing
- `src/bun.js/api/BunObject.classes.ts` - Terminal getter on Subprocess
- `packages/bun-types/bun.d.ts` - TypeScript types
- `docs/runtime/child-process.mdx` - Documentation
- `test/js/bun/terminal/terminal.test.ts` - Comprehensive tests

🤖 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: Jarred Sumner <jarred@jarredsumner.com>
2025-12-15 12:51:13 -08:00
robobun
e66b4639bd fix: use LLVM unstable repo for Debian trixie/forky [publish images] (#25470)
## Summary
- Fix Docker image build failure on Debian trixie by using LLVM's
`unstable` repository instead of the non-existent `trixie` repository
- The LLVM apt repository (`apt.llvm.org`) doesn't have packages for
Debian trixie (13) or forky - attempts to access
`llvm-toolchain-trixie-19` return 404
- Pass `-n=unstable` flag to `llvm.sh` when running on these Debian
versions

## Test plan
- [ ] Verify the Docker image build succeeds at
https://github.com/oven-sh/bun-development-docker-image/actions

Fixes the build failure from:
https://github.com/oven-sh/bun-development-docker-image/actions/runs/20105199193

Error was:
```
E: The repository 'http://apt.llvm.org/trixie llvm-toolchain-trixie-19 Release' does not have a Release file.
```

🤖 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>
2025-12-15 12:45:45 -08:00
robobun
8698d25c52 fix: ensure TLS handshake callback fires before HTTP request handler (#25525)
## Summary

Fixes a flaky test (`test-http-url.parse-https.request.js`) where
`request.socket._secureEstablished` was intermittently `false` when the
HTTP request handler was called on HTTPS servers.

## Root Cause

The `isAuthorized` flag was stored in
`HttpContextData::flags.isAuthorized`, which is **shared across all
sockets** in the same context. This meant multiple concurrent TLS
connections could overwrite each other's authorization state, and the
value could be stale when read.

## Fix

Moved the `isAuthorized` flag from the context-level `HttpContextData`
to the per-socket `AsyncSocketData` base class. This ensures each socket
has its own authorization state that is set correctly during its TLS
handshake callback.

## Changes

- **`AsyncSocketData.h`**: Added per-socket `bool isAuthorized` field
- **`HttpContext.h`**: Updated handshake callback to set per-socket flag
instead of context-level flag
- **`JSNodeHTTPServerSocket.cpp`**: Updated `isAuthorized()` to read
from per-socket `AsyncSocketData` (via `HttpResponseData` which inherits
from it)

## Testing

Ran the flaky test 50+ times with 100% pass rate.

Also verified gRPC and HTTP2 tests still pass.

🤖 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>
2025-12-15 12:44:26 -08:00
github-actions[bot]
81a5c79928 deps: update c-ares to v1.34.6 (#25509)
## What does this PR do?

Updates c-ares to version v1.34.6

Compare:
d3a507e920...3ac47ee46e

Auto-updated by [this
workflow](https://github.com/oven-sh/bun/actions/workflows/update-cares.yml)

Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2025-12-15 11:36:18 -08:00
Alistair Smith
fa996ad1a8 fix: Support @types/node@25.0.2 (#25532)
### What does this PR do?

CI failed again because of a change in @types/node

### How did you verify your code works?

bun-types.test.ts passes
2025-12-15 11:29:04 -08:00
robobun
ed1d6e595c skip HandleScope GC test on ASAN builds (#25523)
## Summary
- Skip `test_handle_scope_gc` test on ASAN builds due to false positives
from dynamic library boundary crossing (Bun built with ASAN+UBSAN,
native addon without sanitizers)

## Test plan
- CI should pass on ASAN builds with this test skipped
- Non-ASAN builds continue to run the test normally

🤖 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>
2025-12-15 11:11:11 -08:00
Jarred Sumner
7c98b0f440 Deflake test/js/node/test/parallel/test-fs-readdir-stack-overflow.js 2025-12-14 22:49:51 -08:00
robobun
f0d18d73c9 Disable CodeRabbit issue enrichment (#25520) 2025-12-14 14:56:58 -08:00
Ciro Spaciari
a5712b92b8 Fix 100% CPU usage with idle WebSocket connections on macOS (kqueue) (#25475)
### What does this PR do?

Fixes a bug where idle WebSocket connections would cause 100% CPU usage
on macOS and other BSD systems using kqueue.

**Root cause:** The kqueue event filter comparison was using bitwise AND
(`&`) instead of equality (`==`) when checking the filter type. Combined
with missing `EV_ONESHOT` flags on writable events, this caused the
event loop to continuously spin even when no actual I/O was pending.

**Changes:**
1. **Fixed filter comparison** in `epoll_kqueue.c`: Changed `filter &
EVFILT_READ` to `filter == EVFILT_READ` (same for `EVFILT_WRITE`). The
filter field is a value, not a bitmask.

2. **Added `EV_ONESHOT` flag** to writable events: kqueue writable
events now use one-shot mode to prevent continuous triggering.

3. **Re-arm writable events when needed**: After a one-shot writable
event fires, the code now properly updates the poll state and re-arms
the writable event if another write is still pending.

### How did you verify your code works?

Added a test that:
1. Creates a TLS WebSocket server and client
2. Sends messages then lets the connection sit idle
3. Measures CPU usage over 3 seconds
4. Fails if CPU usage exceeds 2% (expected is ~0.XX% when idle)
2025-12-12 11:10:22 -08:00
robobun
7dcd49f832 fix(install): only apply default trusted dependencies to npm packages (#25163)
## Summary
- The default trusted dependencies list should only apply to packages
installed from npm
- Non-npm sources (file:, link:, git:, github:) now require explicit
trustedDependencies
- This prevents malicious packages from spoofing trusted names through
local paths or git repos

## Test plan
- [x] Added test: file: dependency named "esbuild" does NOT auto-run
postinstall scripts
- [x] Added test: file: dependency runs scripts when explicitly added to
trustedDependencies
- [x] Verified tests fail with system bun (old behavior) and pass with
new build
- [x] Build compiles successfully

🤖 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: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
2025-12-11 17:44:41 -08:00
robobun
c59a6997cd feat(bundler): add statically-analyzable dead-code elimination via feature flags (#25462)
## Summary
- Adds `import { feature } from "bun:bundle"` for compile-time feature
flag checking
- `feature("FLAG_NAME")` calls are replaced with `true`/`false` at
bundle time
- Enables dead-code elimination through `--feature=FLAG_NAME` CLI
argument
- Works in `bun build`, `bun run`, and `bun test`
- Available in both CLI and `Bun.build()` JavaScript API

## Usage

```ts
import { feature } from "bun:bundle";

if (feature("SUPER_SECRET")) {
  console.log("Secret feature enabled!");
} else {
  console.log("Normal mode");
}
```

### CLI
```bash
# Enable feature during build
bun build --feature=SUPER_SECRET index.ts

# Enable at runtime
bun run --feature=SUPER_SECRET index.ts

# Enable in tests
bun test --feature=SUPER_SECRET
```

### JavaScript API
```ts
await Bun.build({
  entrypoints: ['./index.ts'],
  outdir: './out',
  features: ['SUPER_SECRET', 'ANOTHER_FLAG'],
});
```

## Implementation
- Added `bundler_feature_flags` (as `*const bun.StringSet`) to
`RuntimeFeatures` and `BundleOptions`
- Added `bundler_feature_flag_ref` to Parser struct to track the
`feature` import
- Handle `bun:bundle` import at parse time (similar to macros) - capture
ref, return empty statement
- Handle `feature()` calls in `e_call` visitor - replace with boolean
based on flags
- Wire feature flags through CLI arguments and `Bun.build()` API to
bundler options
- Added `features` option to `JSBundler.zig` for JavaScript API support
- Added TypeScript types in `bun.d.ts`
- Added documentation to `docs/bundler/index.mdx`

## Test plan
- [x] Basic feature flag enabled/disabled tests (both CLI and API
backends)
- [x] Multiple feature flags test
- [x] Dead code elimination verification tests
- [x] Error handling for invalid arguments
- [x] Runtime tests with `bun run --feature=FLAG`
- [x] Test runner tests with `bun test --feature=FLAG`
- [x] Aliased import tests (`import { feature as checkFeature }`)
- [x] Ternary operator DCE tests
- [x] Tests use `itBundled` with both `backend: "cli"` and `backend:
"api"`

🤖 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: Alistair Smith <hi@alistair.sh>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2025-12-11 17:44:14 -08:00
Alistair Smith
1d50af7fe8 @types/bun: Update to @types/node@25, fallback to PropertyKey in test expect matchers when keyof unknown is used (#25460)
more accurately, developers cannot pass a value when expect values
resolve to never. this is easy to fall into when using the
`toContainKey*` matchers. falling back to PropertyKey when this happens
is a sensible/reasonable default

### What does this PR do?

fixes #25456, cc @MonsterDeveloper
fixes #25461

### How did you verify your code works?

bun types integration test
2025-12-10 18:15:55 -08:00
Jarred Sumner
98cee5a57e Improve Bun.stringWidth accuracy and robustness (#25447)
This PR significantly improves `Bun.stringWidth` to handle a wider
variety of Unicode characters and escape sequences correctly.

## Zero-width character handling

Added support for many previously unhandled zero-width characters:
- Soft hyphen (U+00AD)
- Word joiner and invisible operators (U+2060-U+2064)
- Lone surrogates (U+D800-U+DFFF)
- Arabic formatting characters (U+0600-U+0605, U+06DD, U+070F, U+08E2)
- Indic script combining marks (Devanagari through Malayalam)
- Thai and Lao combining marks
- Combining Diacritical Marks Extended and Supplement
- Tag characters (U+E0000-U+E007F)

## ANSI escape sequence handling

### CSI sequences
- Now properly handles ALL CSI final bytes (0x40-0x7E), not just `m`
- This means cursor movement (A/B/C/D), erase (J/K), scroll (S/T), and
other CSI commands are now correctly excluded from width calculation

### OSC sequences
- Added support for OSC sequences (ESC ] ... BEL/ST)
- OSC 8 hyperlinks are now properly handled
- Supports both BEL (0x07) and ST (ESC \) terminators

### ESC ESC fix
- Fixed state machine bug where `ESC ESC` would incorrectly reset state
- Now correctly handles consecutive ESC characters

## Emoji handling

Added proper grapheme-aware emoji width calculation:
- Flag emoji (regional indicator pairs) → width 2
- Skin tone modifiers → width 2
- ZWJ sequences (family, professions, etc.) → width 2
- Keycap sequences → width 2
- Variation selectors (VS15 for text, VS16 for emoji presentation)
- Uses ICU's `UCHAR_EMOJI` property for accurate emoji detection

## Test coverage

Added comprehensive test suite with **94 tests** covering:
- All zero-width character categories
- All CSI final bytes
- OSC sequences with various terminators
- Emoji edge cases (flags, skin tones, ZWJ, keycaps, variation
selectors)
- East Asian width (CJK, fullwidth, halfwidth katakana)
- Indic and Thai script combining marks
- Fuzzer-like stress tests for robustness

## Breaking changes

This is a behavior change - `stringWidth` will return different values
for some inputs. However, the new values are more accurate
representations of terminal display width:

| Input | Old | New | Why |
|-------|-----|-----|-----|
| Flag emoji 🇺🇸 | 1 | 2 | Flags display as 2 cells |
| Skin tone 👋🏽 | 4 | 2 | Emoji + modifier = 1 grapheme |
| ZWJ family 👨‍👩‍👧 | 8 | 2 | ZWJ sequence = 1 grapheme |
| Word joiner U+2060 | 1 | 0 | Invisible character |
| OSC 8 hyperlinks | counted URL | just visible text | URLs are
invisible |
| Cursor movement ESC[5A | counted | 0 | Control sequence |

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
2025-12-10 16:17:57 -08:00
Elfayeur - Remi
ac0099ebc6 docs: fix code highlight (#25411)
### What does this PR do?

Fix code highlight line, see problem:
<img width="684" height="663" alt="Screenshot 2025-12-08 at 11 40 39 AM"
src="https://github.com/user-attachments/assets/9894a7b7-ddd6-4bad-b7de-3e0e55ecd8cd"
/>


### How did you verify your code works?

Co-authored-by: Michael H <git@riskymh.dev>
2025-12-10 16:06:45 +11:00
Ryan Machado
64146d47f9 Update os-signals.mdx (#25372)
### What does this PR do?

Remove a loose section from os-signals documentation

### How did you verify your code works?

Just a small documentation change.

Co-authored-by: Michael H <git@riskymh.dev>
2025-12-10 16:06:39 +11:00
robobun
a2d8b75962 fix(yaml): quote strings ending with colons (#25443)
## Summary
- Fixes strings ending with colons (e.g., `"tin:"`) not being quoted in
YAML.stringify output
- This caused YAML.parse to fail with "Unexpected token" when parsing
the output back

## Test plan
- Added regression tests in `test/regression/issue/25439.test.ts`
- Verified round-trip works for various strings ending with colons
- Ran existing YAML tests to ensure no regressions

Fixes #25439

🤖 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>
2025-12-09 18:20:26 -08:00
Kyle
a15fe76bf2 add brotli and zstd to CompressionStream and DecompressionStream types (#25374)
### What does this PR do?

- removes the `Unimplemented in Bun` comment on `CompressionStream` and
`DecompressionStream`
- updates the types for `CompressionStream` and `DecompressionStream` to
add a new internal `CompressionFormat` type to the constructor, which
adds `brotli` and `zstd` to the union
- adds tests for brotli and zstd usage
- adds lib.dom.d.ts exclusions for brotli and zstd as these don't exist
in the DOM version of CompressionFormat

fixes #25367

### How did you verify your code works?

typechecks and tests
2025-12-09 17:56:55 -08:00
robobun
8dc084af5f fix(fetch): ignore proxy object without url property (#25414)
## Summary
- When a URL object is passed as the proxy option, or when a proxy
object lacks a "url" property, ignore it instead of throwing an error
- This fixes a regression introduced in 1.3.4 where libraries like taze
that pass URL objects as proxy values would fail

## Test plan
- Added test: "proxy as URL object should be ignored (no url property)"
- passes a URL object directly as proxy
- Updated test: "proxy object without url is ignored (regression
#25413)" - proxy object with headers but no url
- Updated test: "proxy object with null url is ignored (regression
#25413)" - proxy object where url is null
- All 29 proxy tests pass

Fixes #25413

🤖 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: Jarred Sumner <jarred@jarredsumner.com>
2025-12-09 12:31:45 -08:00
Alistair Smith
2028e21d60 fmt bun.d.ts 2025-12-08 18:00:09 -08:00
Ciro Spaciari
f25ea59683 feat(s3): add Content-Disposition support for S3 uploads (#25363)
### What does this PR do?
- Add `contentDisposition` option to S3 file uploads to control the
`Content-Disposition` HTTP header
- Support passing `contentDisposition` through all S3 upload paths
(simple uploads, multipart uploads, and streaming uploads)
- Add TypeScript types for the new option
Fixes https://github.com/oven-sh/bun/issues/25362
### How did you verify your code works?
Test
2025-12-08 15:30:20 -08:00
Jarred Sumner
55c6afb498 Deflake test/js/bun/net/socket.test.ts 2025-12-08 11:16:26 -08:00
Jarred Sumner
0aca002161 Deflake test/js/bun/util/sleep.test.ts 2025-12-08 11:14:52 -08:00
robobun
4980736786 docs(server): fix satisfies Serve type in export default example (#25410) 2025-12-08 09:25:00 -08:00
robobun
3af0d23d53 docs: expand single-file executable file embedding documentation (#25408)
## Summary

- Expanded documentation for embedding files in single-file executables
with `with { type: "file" }`
- Added clear explanation of how the import attribute works and path
transformation at build time
- Added examples for reading embedded files with both `Bun.file()` and
Node.js `fs` APIs
- Added practical examples: JSON configs, HTTP static assets, templates,
binary files (WASM, fonts)
- Improved `Bun.embeddedFiles` section with a dynamic asset server
example

## Test plan

- [x] Verified all code examples compile and run correctly with `bun
build --compile`
- [x] Tested `Bun.file()` reads embedded files correctly
- [x] Tested `node:fs` APIs (`readFileSync`, `promises.readFile`,
`stat`) work with embedded files
- [x] Tested `Bun.embeddedFiles` returns correct blob array
- [x] Tested `--asset-naming` flag removes content hashes

🤖 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>
2025-12-07 18:43:00 -08:00
Hamidreza Hanafi
9c96937329 fix(transpiler): preserve simplified property values in object spread expressions (#25401)
Fixes #25398

### What does this PR do?

Fixes a bug where object expressions with spread properties and nullish
coalescing to empty objects (e.g., `k?.x ?? {}`) would produce invalid
JavaScript output like `k?.x ?? ` (missing `{}`).

### Root Cause

In `src/ast/SideEffects.zig`, the `simplifyUnusedExpr` function handles
unused object expressions with spread properties. When simplifying
property values:

1. The code creates a mutable copy `prop` from the original `prop_`
2. When a property value is simplified (e.g., `k?.x ?? {}` → `k?.x`), it
updates `prop.value`
3. **Bug:** The code then wrote back `prop_` (the original) instead of
`prop` (the modified copy)

Because `simplifyUnusedExpr` mutates the AST in place when handling
nullish coalescing (setting `bin.right` to empty), the original `prop_`
now contained an expression with `bin.right` as an empty/missing
expression, resulting in invalid output.

### How did you verify your code works?
- Added regression test in `test/regression/issue/25398.test.ts`
- Verified the original reproduction case passes
- Verified existing CommonJS tests continue to pass
- Verified test fails with system bun and passes with the fix
2025-12-07 16:33:37 -08:00
robobun
d4eaaf8363 docs: document autoload options for standalone executables (#25385)
## Summary

- Document new default behavior in v1.3.4: `tsconfig.json` and
`package.json` loading is now disabled by default for standalone
executables
- Add documentation for `--compile-autoload-tsconfig` and
`--compile-autoload-package-json` CLI flags
- Document all four JavaScript API options: `autoloadTsconfig`,
`autoloadPackageJson`, `autoloadDotenv`, `autoloadBunfig`
- Note that `.env` and `bunfig.toml` may also be disabled by default in
a future version

## Test plan

- [ ] Review rendered documentation for accuracy and formatting

🤖 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>
2025-12-07 15:44:06 -08:00
Jarred Sumner
e1aa437694 Bump 2025-12-07 15:42:23 -08:00
robobun
73c3f0004f fix(vm): delete internal Loader property from node:vm global object (#25397) 2025-12-07 13:29:32 -08:00
robobun
b80cb629c6 fix(docs): correct mock documentation examples (#25384)
## Summary

- Fix `mock.mock.args` to `mock.mock.calls` in mock-functions.mdx (the
`.args` property doesn't exist)
- Fix mock.restore example to use module methods instead of spy
functions (calling spy functions after restore returns `undefined`)
- Add missing `vi` import in Vitest compatibility example

## Test plan

- [x] Verified each code block works by running tests against the debug
build

🤖 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>
2025-12-06 22:17:51 -08:00
Jarred Sumner
8773f7ab65 Delete TODO.md 2025-12-06 19:58:21 -08:00
Dylan Conway
5eb2145b31 fix(compile): use 8-byte header for embedded section to ensure bytecode alignment (#25377)
## Summary
- Change the size header in embedded Mach-O and PE sections from `u32`
(4 bytes) to `u64` (8 bytes)
- Ensures the data payload starts at an 8-byte aligned offset, which is
required for the bytecode cache

## Test plan
- [x] Test standalone compilation on macOS
- [ ] Test standalone compilation on Windows

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-06 16:37:09 -08:00
Jarred Sumner
cde167cacd Revert "Add Tanstack Start to bun init (#24648)"
Adding a 260 KB bun header image is not a good use of binary size

This reverts commit 830fd9b0ae.
2025-12-05 18:32:51 -08:00
robobun
6ce419d3f8 fix(napi): napi_typeof returns napi_object for String objects (#25365)
## Summary

- Fix `napi_typeof` to return `napi_object` for boxed String objects
(`new String("hello")`) instead of incorrectly returning `napi_string`
- Add regression test for boxed primitive objects (String, Number,
Boolean)

The issue was that `StringObjectType` and `DerivedStringObjectType` JSC
cell types were falling through to return `napi_string`, but these
represent object wrappers around strings, not primitive strings.

## Test plan

- [x] `bun bd test test/napi/napi.test.ts -t "napi_typeof"` passes
- [x] Test fails with `USE_SYSTEM_BUN=1` (confirming the bug exists in
released version)

Fixes #25351

🤖 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>
2025-12-05 18:27:06 -08:00
Alistair Smith
05508a627d Reapply "use event.message when no event.error in HMR during event" (#25360)
This reverts commit b4c8379447.

### What does this PR do?

### How did you verify your code works?

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-05 17:38:56 -08:00
robobun
23383b32b0 feat(compile): add --compile-autoload-tsconfig and --compile-autoload-package-json flags (#25340)
## Summary

By default, standalone executables no longer load `tsconfig.json` and
`package.json` at runtime. This improves startup performance and
prevents unexpected behavior from config files in the runtime
environment.

- Added `--compile-autoload-tsconfig` / `--no-compile-autoload-tsconfig`
CLI flags (default: false)
- Added `--compile-autoload-package-json` /
`--no-compile-autoload-package-json` CLI flags (default: false)
- Added `autoloadTsconfig` and `autoloadPackageJson` options to the
`Bun.build()` compile config
- Flags are stored in `StandaloneModuleGraph.Flags` and applied at
runtime boot

This follows the same pattern as the existing
`--compile-autoload-dotenv` and `--compile-autoload-bunfig` flags.

## Test plan

- [x] Added tests in `test/bundler/bundler_compile_autoload.test.ts`
- [x] Verified standalone executables work correctly with runtime config
files that differ from compile-time configs
- [x] Verified the new CLI flags are properly parsed and applied
- [x] Verified the JS API options work correctly

🤖 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: Jarred Sumner <jarred@jarredsumner.com>
2025-12-05 14:43:53 -08:00
eroderust
0d5a7c36ed chore: remove duplicate words in comment (#25347) 2025-12-05 11:19:47 -08:00
Alistair Smith
b4c8379447 Revert "use event.message when no event.error in HMR during event"
This reverts commit 438aaf9e95.
2025-12-05 11:16:52 -08:00
Alistair Smith
438aaf9e95 use event.message when no event.error in HMR during event 2025-12-05 11:14:45 -08:00
robobun
4d60b6f69d docs: clarify SQLite embed example requires existing database (#25329)
Co-authored-by: Alistair Smith <hi@alistair.sh>
2025-12-03 22:02:39 -08:00
pfg
e9e93244cb remove CMakeCache before building (#24860)
So it doesn't cache flags that are passed to the build
2025-12-01 22:02:46 -08:00
pfg
800a937cc2 Add fake timers for bun:test (#23764)
Fixes ENG-21288

TODO: Test with `@testing-library/react` `waitFor`

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-01 21:59:11 -08:00
Lydia Hallie
830fd9b0ae Add Tanstack Start to bun init (#24648)
Co-authored-by: Alistair Smith <hi@alistair.sh>
2025-12-01 21:05:47 -08:00
Meghan Denny
fe0aba79f4 test: add regression tests for building docker containers (#25210) 2025-12-01 20:20:06 -08:00
sdm2345
a4aaec5b2f Fix http.Agent connection pool not reusing connections (#24351)
This fixes a critical bug where `http.Agent` with `keepAlive: true` was
not reusing connections, causing a 66% performance degradation compared
to Node.js. Every request was establishing a new TCP/TLS connection
instead of reusing existing ones.

**Root Cause:**

Three independent bugs were causing the connection pool to fail:

1. **TypeScript layer** (`src/js/node/_http_client.ts:271`)
   - Reading wrong property: `keepalive` instead of `keepAlive`
   - User's `keepAlive: true` setting was being ignored

2. **Request header handling** (`src/http.zig:591`)
   - Only handled `Connection: close`, ignored `Connection: keep-alive`
   - Missing explicit flag update for keep-alive header

3. **Response header handling** (`src/http.zig:2240`)
   - Used compile-time function `eqlComptime` at runtime (always failed)
   - Inverted logic: disabled pool when NOT "keep-alive"
   - Ignored case-sensitivity (should use `eqlIgnoreCase` per RFC 7230)

**Performance Impact:**

- **Before**: All requests ~940ms, stddev 33ms (0% improvement) 
- **After**: First request ~930ms, subsequent ~320ms (65.9% improvement)

- Performance now matches Node.js (65.9% vs 66.5% improvement)
- QPS increased from 4.2 to 12.2 req/s (190% improvement)

**Files Changed:**
- `src/js/node/_http_client.ts` - Fix property name (1 line)
- `src/http.zig` - Fix request/response header handling (5 lines)

Fixes #12053

### What does this PR do?

This PR fixes the HTTP connection pool by correcting three bugs:

1. **Fixes TypeScript property name**: Changes `this[kAgent]?.keepalive`
to `this[kAgent]?.keepAlive` to properly read the user's keepAlive
setting from http.Agent

2. **Adds keep-alive request header handling**: Explicitly sets
`disable_keepalive = false` when receiving `Connection: keep-alive`
header

3. **Fixes response header parsing**: 
- Replaces compile-time `strings.eqlComptime()` with runtime
`std.ascii.eqlIgnoreCase()`
- Corrects inverted logic to properly enable connection pool on
`Connection: keep-alive`
   - Makes header comparison case-insensitive per RFC 7230

All three bugs must be fixed together - any single bug would cause the
connection pool to fail.

### How did you verify your code works?

**Test 1: Minimal reproduction with 10 sequential HTTPS requests**
```typescript
const agent = new https.Agent({ keepAlive: true });
// Make 10 requests to https://api.example.com
```

Results:
- First request: 930ms (cold start with TCP/TLS handshake)
- Subsequent requests: ~320ms average (connection reused)
- **Improvement: 65.9%** (matches Node.js 66.5%)
- Verified across 3 repeated test runs for stability

**Test 2: Response header validation**
- Confirmed server returns `Connection: Keep-Alive`
- Verified Bun correctly parses and applies the header

**Test 3: Performance comparison**

| Runtime | First Request | Subsequent Avg | Improvement | QPS |
|---------|--------------|----------------|-------------|-----|
| Node.js v20.18.0 | 938ms | 314ms | **66.5%** | 12.2 |
| Bun v1.3.1 (broken) | 935ms | 942ms | -0.7%  | 4.2 |
| Bun v1.3.2 (fixed) | 930ms | 317ms | **65.9%**  | 12.2 |

Bun now performs identically to Node.js, confirming the connection pool
works correctly.
2025-12-01 17:31:15 -08:00
Meghan Denny
24bc8aa416 ci: remove ubuntu 24 (#25288)
redundant with 25
2025-12-01 17:01:14 -08:00
robobun
2ab6efeea3 fix(ffi): restore CString constructor functionality (#25257)
## Summary
- Fix regression where `new Bun.FFI.CString(ptr)` throws "function is
not a constructor"
- Pass the same function as both call and constructor callbacks for
CString

## Root Cause
PR #24910 replaced `jsc.createCallback` with `jsc.JSFunction.create` for
all FFI functions. However, `JSFunction.create` doesn't allow
constructor calls by default (it uses `callHostFunctionAsConstructor`
which throws). The old `createCallback` used `JSFFIFunction` which
allowed the same function to be called with `new`.

## Fix
Pass the same function as both the `implementation` and `constructor`
option to `JSFunction.create` for CString specifically. This allows `new
CString(ptr)` to work while keeping the refactoring from #24910.

Additionally, the `bun:ffi` module now replaces `Bun.FFI.CString` with
the proper JS CString class after loading, so users get the full class
with `.ptr`, `.byteOffset`, etc. properties.

## Test plan
- [x] Added regression test `test/regression/issue/25231.test.ts`
- [x] Test fails with `USE_SYSTEM_BUN=1` (v1.3.3), passes with fix
- [x] Verified reproduction case from issue works

Fixes #25231

🤖 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>
2025-12-01 15:47:27 -08:00
Amdadul Haq
6745bdaa85 Add protocol property to serve.d.ts (#25267) 2025-12-01 13:43:14 -08:00
Lydia Hallie
dce7a02f4d Docs: Minor fixes and improvements (#25284)
This PR addresses several issues opened for the docs:

- Add callout for SQLite caching behavior between prepare() and query()
- Fix SQLite types and fix deprecated exec to run
- Fix Secrets API example
- Update SolidStart guide
- Add bun upgrade guide
- Prefer `process.versions.bun` over `typeof Bun` for detection
- Document complete `bunx` flags
- Improve Nitro preset documentation for Nuxt

Fixes #23165, #24424, #24294, #25175, #18433, #16804, #22967, #22527,
#10560, #14744
2025-12-01 13:32:08 -08:00
Michael H
9c420c9eff fix production build for vscode extention (#25274) 2025-12-01 12:59:27 -08:00
github-actions[bot]
9c2ca4b8fd deps: update sqlite to 3.51.100 (#25243)
## What does this PR do?

Updates SQLite to version 3.51.100

Compare: https://sqlite.org/src/vdiff?from=3.51.0&to=3.51.100

Auto-updated by [this
workflow](https://github.com/oven-sh/bun/actions/workflows/update-sqlite3.yml)

Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2025-12-01 11:58:34 -08:00
robobun
e624f1e571 fix(jest): handle null SourceOrigin in jest.mock() to prevent crash (#25281)
## Summary
- Added null check for `sourceOrigin` before accessing its URL in
`jest.mock()`
- When `callerSourceOrigin()` returns null (e.g., when called with
invalid arguments), the code now safely returns early instead of
crashing

## Test plan
- [x] Added regression test `test/regression/issue/ENG-24434.test.ts`
- [x] `bun bd test test/regression/issue/ENG-24434.test.ts` passes

Fixes ENG-24434

🤖 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>
2025-12-01 11:54:03 -08:00
robobun
27381063b6 fix(windows): use GetConsoleCP() instead of GetConsoleOutputCP() for input codepage (#25252)
## Summary

- Fix typo where `GetConsoleOutputCP()` was called twice instead of
calling `GetConsoleCP()` for the input codepage
- Add missing `GetConsoleCP()` extern declaration in windows.zig

The code was saving the output codepage twice, meaning the input
codepage was never properly saved and thus couldn't be correctly
restored.

## Note

This fix corrects a bug in the codepage save/restore logic, but **may
not fully resolve the garbled text issue** in #25151. The garbled text
problem occurs when `bunx` (without `--bun`) runs a package via Node.js,
and that package tries to spawn `bun`. The error message from cmd.exe
gets garbled on non-English Windows systems.

Further investigation may be needed to determine if additional codepage
handling is required when spawning processes.

Related to #25151

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2025-11-30 23:11:33 -08:00
Michael H
9ca8de6eb9 vscode test runner add the new test functions to static analysis (#25256) 2025-11-30 17:31:17 -08:00
robobun
fdcfac6a75 fix(node:tls): use SSL_session_reused for TLSSocket.isSessionReused (#25258)
## Summary
Fixes `TLSSocket.isSessionReused()` to use BoringSSL's
`SSL_session_reused()` API instead of incorrectly checking if a session
was set.

The previous implementation returned `!!this[ksession]` which would
return `true` if `setSession()` was called, even if the session wasn't
actually reused by the SSL layer. This fix correctly uses the native SSL
API like Node.js does.

## Changes
- Added native `isSessionReused` function in Zig that calls
`SSL_session_reused()`
- Updated `TLSSocket.prototype.isSessionReused` to use the native
implementation
- Added regression tests

## Test plan
- [x] `bun bd test test/regression/issue/25190.test.ts` passes
- [x] `bun bd test test/js/node/tls/node-tls-connect.test.ts` passes

Fixes #25190

🤖 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>
2025-11-30 17:00:25 -08:00
robobun
ce1981c525 fix(node:assert): handle Number and Boolean wrappers in deepStrictEqual (#25201)
## Summary
- Fixes `assert.deepStrictEqual()` to properly compare Number and
Boolean wrapper objects
- Previously, `new Number(1)` and `new Number(2)` were incorrectly
considered equal because they have no enumerable properties
- Now correctly extracts and compares internal values using
`JSC::sameValue()`, then falls through to check own properties

## Test plan
- [x] Run `bun bd test test/regression/issue/24045.test.ts` - all 6
tests pass
- [x] Verify tests fail with system Bun (`USE_SYSTEM_BUN=1`) to confirm
fix validity
- [x] Verified behavior matches Node.js exactly (see table below)

## Node.js Compatibility

| Test Case | Node.js | Bun |
|-----------|---------|-----|
| Different Number values (`new Number(1)` vs `new Number(2)`) | throws
| throws |
| Same Number values (`new Number(1)` vs `new Number(1)`) | equal |
equal |
| 0 vs -0 (`new Number(0)` vs `new Number(-0)`) | throws | throws |
| NaN equals NaN (`new Number(NaN)` vs `new Number(NaN)`) | equal |
equal |
| Different Boolean values (`new Boolean(true)` vs `new Boolean(false)`)
| throws | throws |
| Same Boolean values | equal | equal |
| Number wrapper vs primitive (`new Number(1)` vs `1`) | throws | throws
|
| Number vs Boolean wrapper | throws | throws |
| Same value, different own properties | throws | throws |
| Same value, same own properties | equal | equal |
| Different own property values | throws | throws |

## Example

Before (bug):
```javascript
assert.deepStrictEqual(new Number(1), new Number(2)); // passes incorrectly
```

After (fixed):
```javascript
assert.deepStrictEqual(new Number(1), new Number(2)); // throws AssertionError
```

Closes #24045

🤖 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: Jarred Sumner <jarred@jarredsumner.com>
2025-11-30 16:58:58 -08:00
Dylan Conway
cc3fc5a1d3 fix ENG-24015 (#25222)
### What does this PR do?
Ensures `ptr` is either a number or heap big int before converting to a
number.

also fixes ENG-24039
### How did you verify your code works?
Added a test
2025-11-29 19:13:32 -08:00
Dylan Conway
d83e0eb1f1 fix ENG-24017 (#25224)
### What does this PR do?
Fixes checking for exceptions when creating empty or used readable
streams

also fixes ENG-24038
### How did you verify your code works?
Added a test for creating empty streams
2025-11-29 19:13:06 -08:00
Dylan Conway
72b9525507 update bunfig telemetry docs (#25237)
### What does this PR do?

### How did you verify your code works?
2025-11-29 19:12:18 -08:00
robobun
0f7494569e fix(console): implement %j format specifier for JSON output (#25195)
## Summary
- Implements the `%j` format specifier for `console.log` and related
console methods
- `%j` outputs the JSON stringified representation of the value
- Previously, `%j` was not recognized and was left as literal text in
the output

## Test plan
- [x] Run `bun bd test test/regression/issue/24234.test.ts` - all 5
tests pass
- [x] Verify tests fail with system Bun (`USE_SYSTEM_BUN=1`) to confirm
fix validity
- [x] Manual verification: `console.log('%j', {foo: 'bar'})` outputs
`{"foo":"bar"}`

## Example

Before (bug):
```
$ bun -e "console.log('%j %s', {foo: 'bar'}, 'hello')"
%j [object Object] hello
```

After (fixed):
```
$ bun -e "console.log('%j %s', {foo: 'bar'}, 'hello')"
{"foo":"bar"} hello
```

Closes #24234

🤖 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>
2025-11-28 22:57:55 -08:00
Meghan Denny
9fd6b54c10 ci: fix windows git dependencies tests (#25213) 2025-11-28 22:56:54 -08:00
robobun
19acc4dcac fix(buffer): handle string allocation failures in encoding operations (#25214)
## Summary
- Add proper bounds checking for encoding operations that produce larger
output than input
- Handle allocation failures gracefully by returning appropriate errors
- Add defensive checks in string initialization functions

## Test plan
- Added test case for encoding operations with large buffers
- Verified existing buffer tests still pass

🤖 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>
2025-11-28 22:56:28 -08:00
Meghan Denny
56da7c4fd9 zig: address a VM todo (#23678) 2025-11-28 19:38:26 -08:00
Meghan Denny
5bdb8ec0cb all: update to debian 13 (#24055) [publish images] 2025-11-28 15:01:40 -08:00
Meghan Denny
4cf9b794c9 ci: update buildkite agent to v3.114.0 (#25127) [publish images] 2025-11-28 15:01:20 -08:00
Meghan Denny
998ec54da9 test: fix spacing in sql.test.ts (#24691) 2025-11-28 14:40:58 -08:00
Jarred Sumner
0305f3d4d2 feat(url): implement URLPattern API (#25168)
## Summary

Implements the [URLPattern Web
API](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) based
on WebKit's implementation. URLPattern provides declarative pattern
matching for URLs, similar to how regular expressions work for strings.

### Features

- **Constructor**: Create patterns from strings or `URLPatternInit`
dictionaries
- **`test()`**: Check if a URL matches the pattern (returns boolean)
- **`exec()`**: Extract matched groups from a URL (returns
`URLPatternResult` or null)
- **Pattern properties**: `protocol`, `username`, `password`,
`hostname`, `port`, `pathname`, `search`, `hash`
- **`hasRegExpGroups`**: Detect if the pattern uses custom regular
expressions

### Example Usage

```js
// Match URLs with a user ID parameter
const pattern = new URLPattern({ pathname: '/users/:id' });

pattern.test('https://example.com/users/123'); // true
pattern.test('https://example.com/posts/456'); // false

const result = pattern.exec('https://example.com/users/123');
console.log(result.pathname.groups.id); // "123"

// Wildcard matching
const filesPattern = new URLPattern({ pathname: '/files/*' });
const match = filesPattern.exec('https://example.com/files/image.png');
console.log(match.pathname.groups[0]); // "image.png"
```

## Implementation Notes

- Adapted from WebKit's URLPattern implementation
- Modified JS bindings to work with Bun's infrastructure (simpler
`convertDictionary` patterns, WTF::Variant handling)
- Added IsoSubspaces for proper GC integration

## Test Plan

- [x] 408 tests from Web Platform Tests pass
- [x] Tests fail with system Bun (URLPattern not defined), pass with
debug build
- [x] Manual testing of basic functionality

Fixes https://github.com/oven-sh/bun/issues/2286

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-11-28 00:04:30 -08:00
Henrique Fonseca
1006a4fac2 Fix incorrect file name in React component test example 🌟 (#25167)
# What does this PR do?

Nyaa~ This PR fixes a small mistake in the documentation where the code
block for a React component test example was using the wrong filename!
(;ω;)💦

It was previously labeled as `matchers.d.ts`, but it should be something
like `myComponent.test.tsx` to properly reflect a test file for a React
component using `@testing-library/react`. 🧁

This makes the example clearer and more accurate for developers using
Bun to test their React components~! 💻🌸💕

# How did you verify your code works?

It's just docs, one single line 🥺

Pwease review and merge it when you can, senpai~~! UwU 🌈🫧
2025-11-27 23:15:34 -08:00
Michael H
c7f7d9bb82 run fmt (#25148)
prettier released a new update which seems to have changed a few
logistics
2025-11-28 17:51:45 +11:00
robobun
37bce389a0 docs: document inlining process.env.* values in static HTML bundling (#25084)
## Summary

- Add documentation for the `env` option that inlines `process.env.*`
values in frontend code when bundling HTML files
- Document runtime configuration via `bunfig.toml` `[serve.static]`
section for `bun ./index.html`
- Document production build configuration via CLI (`--env=PUBLIC_*`) and
`Bun.build` API (`env: "PUBLIC_*"`)
- Explain prefix filtering to avoid exposing sensitive environment
variables

## Test plan

- [x] Verify documentation renders correctly in local preview
- [x] Cross-reference with existing `env` documentation in
bundler/index.mdx

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Michael H <git@riskymh.dev>
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>
2025-11-28 17:51:11 +11:00
robobun
bab583497c fix(cli): correct --dry-run help text for bun publish (#25137)
## Summary
- Fix `bun publish --help` showing incorrect `--dry-run` description
("Don't install anything" → "Perform a dry run without making changes")
- The `--dry-run` flag is in a shared params array used by multiple
commands, so the new generic message works for all of them

Fixes #24806

## Test plan
- [x] Verify `bun publish --help` shows "Perform a dry run without
making changes" for --dry-run
- [x] Regression test added that validates the correct help text is
shown
- [x] Test passes with debug build, fails with system bun (validating it
tests the right thing)

🤖 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>
2025-11-27 18:12:07 -08:00
robobun
a83fceafc7 fix(http2): return server from setTimeout for method chaining (#25138)
## Summary
- Make `Http2Server.setTimeout()` and `Http2SecureServer.setTimeout()`
return `this` to enable method chaining
- Matches Node.js behavior where `server.setTimeout(1000).listen()`
works

Fixes #24924

## Test plan
- [x] Test that `Http2Server.setTimeout()` returns server instance
- [x] Test that `Http2SecureServer.setTimeout()` returns server instance
- [x] Test method chaining works (e.g.,
`server.setTimeout(1000).close()`)
- [x] Tests pass with debug build, fail with system bun

🤖 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>
2025-11-27 16:46:15 -08:00
robobun
ef8eef3df8 fix(http): stricter validation in chunked encoding parser (#25159)
## Summary
- Adds stricter validation for chunk boundaries in the HTTP chunked
transfer encoding parser
- Ensures conformance with RFC 9112 requirements for chunk formatting
- Adds additional test coverage for chunked encoding edge cases

## Test plan
- Added new tests in `test/js/bun/http/request-smuggling.test.ts`
- All existing HTTP tests pass
- `bun bd test test/js/bun/http/request-smuggling.test.ts` passes

🤖 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>
2025-11-27 16:29:35 -08:00
robobun
69b571da41 Delete claude.yml workflow (#25157) 2025-11-27 12:26:50 -08:00
robobun
908ab9ce30 feat(fetch): add proxy object format with headers support (#25090)
## Summary

- Extends `fetch()` proxy option to accept an object format: `proxy: {
url: string, headers?: Headers }`
- Allows sending custom headers to the proxy server (useful for proxy
authentication, custom routing headers, etc.)
- Headers are sent in CONNECT requests (for HTTPS targets) and direct
proxy requests (for HTTP targets)
- User-provided `Proxy-Authorization` header overrides auto-generated
credentials from URL

## Usage

```typescript
// Old format (still works)
fetch(url, { proxy: "http://proxy.example.com:8080" });

// New object format with headers
fetch(url, {
  proxy: {
    url: "http://proxy.example.com:8080",
    headers: {
      "Proxy-Authorization": "Bearer token",
      "X-Custom-Proxy-Header": "value"
    }
  }
});
```

## Test plan

- [x] Test proxy object with url string works same as string proxy
- [x] Test proxy object with headers sends headers to proxy (HTTP
target)
- [x] Test proxy object with headers sends headers in CONNECT request
(HTTPS target)
- [x] Test proxy object with Headers instance
- [x] Test proxy object with empty headers
- [x] Test proxy object with undefined headers
- [x] Test user-provided Proxy-Authorization overrides URL credentials
- [x] All existing proxy tests pass (25 total)

🤖 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>
2025-11-26 15:11:45 -08:00
robobun
43c46b1f77 fix(FormData): throw error instead of assertion failure on very large input (#25006)
## Summary

- Fix crash in `FormData.from()` when called with very large ArrayBuffer
input
- Add length check in C++ `toString` function against both Bun's
synthetic limit and WebKit's `String::MaxLength`
- For UTF-8 tagged strings, use simdutf to calculate actual UTF-16
length only when byte length exceeds the limit

## Root Cause

When `FormData.from()` was called with a very large ArrayBuffer (e.g.,
`new Uint32Array(913148244)` = ~3.6GB), the code would crash with:

```
ASSERTION FAILED: data.size() <= MaxLength
vendor/WebKit/Source/WTF/wtf/text/StringImpl.h(886)
```

The `toString()` function in `helpers.h` was only checking against
`Bun__stringSyntheticAllocationLimit` (which defaults to ~4GB), but not
against WebKit's `String::MaxLength` (INT32_MAX, ~2GB). When the input
exceeded `String::MaxLength`, `createWithoutCopying()` would fail with
an assertion.

## Changes

1. **helpers.h**: Added `|| str.len > WTF::String::MaxLength` checks to
all three code paths in `toString()`:
- UTF-8 tagged pointer path (with simdutf length calculation only when
needed)
   - External pointer path
   - Non-copying creation path

2. **url.zig**: Reverted the incorrect Zig-side check (UTF-8 byte length
!= UTF-16 character length)

## Test plan

- [x] Added test that verifies FormData.from with oversized input
doesn't crash
- [x] Verified original crash case now returns empty FormData instead of
crashing:
  ```js
  const v3 = new Uint32Array(913148244);
  FormData.from(v3); // No longer crashes
  ```

🤖 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: Jarred Sumner <jarred@jarredsumner.com>
2025-11-26 13:46:08 -08:00
robobun
a0c5f3dc69 fix(mmap): use coerceToInt64 for offset/size to prevent assertion failure (#25101)
## Summary

- Fix assertion failure in `Bun.mmap` when `offset` or `size` options
are non-numeric values
- Add validation to reject negative `offset`/`size` with clear error
messages

Minimal reproduction: `Bun.mmap("", { offset: null });`

## Root Cause

`Bun.mmap` was calling `toInt64()` directly on the `offset` and `size`
options without validating they are numbers first. `toInt64()` has an
assertion that the value must be a number or BigInt, which fails when
non-numeric values like `null` or functions are passed.

## Test plan

- [x] Added tests for negative offset/size rejection
- [x] Added tests for non-number inputs (null, undefined)
- [x] `bun bd test test/js/bun/util/mmap.test.js` passes

Closes ENG-22413

🤖 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>
2025-11-26 13:37:41 -08:00
robobun
5965ff18ea fix(test): fix assertion failure in expect.extend with non-JSFunction callables (#25099)
## Summary

- Fix debug assertion failure in `JSWrappingFunction` when
`expect.extend()` is called with objects containing non-`JSFunction`
callables
- The crash occurred because `jsCast<JSFunction*>` was used, which
asserts the value inherits from `JSFunction`, but callable class
constructors (like `Expect`) inherit from `InternalFunction` instead

## Changes

- Change `JSWrappingFunction` to store `JSObject*` instead of
`JSFunction*`
- Use `jsDynamicCast` instead of `jsCast` in `getWrappedFunction`
- Use `getObject()` instead of `jsCast` in `create()`

## Reproduction

```js
const jest = Bun.jest();
jest.expect.extend(jest);
```

Before fix (debug build):
```
ASSERTION FAILED: !from || from->JSCell::inherits(std::remove_pointer<To>::type::info())
JSCast.h(40) : To JSC::jsCast(From *) [To = JSC::JSFunction *, From = JSC::JSCell]
```

After fix: Properly throws `TypeError: expect.extend: 'jest' is not a
valid matcher`

## Test plan

- [x] Added regression test
`test/regression/issue/fuzzer-ENG-22942.test.ts`
- [x] Existing `expect-extend.test.js` tests pass (27 tests)
- [x] Build succeeds

Fixes ENG-22942

🤖 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>
2025-11-26 13:34:02 -08:00
493 changed files with 72040 additions and 36437 deletions

View File

@@ -125,16 +125,13 @@ const testPlatforms = [
{ os: "darwin", arch: "aarch64", release: "13", tier: "previous" },
{ os: "darwin", arch: "x64", release: "14", tier: "latest" },
{ os: "darwin", arch: "x64", release: "13", tier: "previous" },
{ os: "linux", arch: "aarch64", distro: "debian", release: "12", tier: "latest" },
{ os: "linux", arch: "x64", distro: "debian", release: "12", tier: "latest" },
{ os: "linux", arch: "x64", baseline: true, distro: "debian", release: "12", tier: "latest" },
{ os: "linux", arch: "x64", profile: "asan", distro: "debian", release: "12", tier: "latest" },
{ os: "linux", arch: "aarch64", distro: "debian", release: "13", tier: "latest" },
{ os: "linux", arch: "x64", distro: "debian", release: "13", tier: "latest" },
{ os: "linux", arch: "x64", baseline: true, distro: "debian", release: "13", tier: "latest" },
{ os: "linux", arch: "x64", profile: "asan", distro: "debian", release: "13", tier: "latest" },
{ os: "linux", arch: "aarch64", distro: "ubuntu", release: "25.04", tier: "latest" },
{ os: "linux", arch: "aarch64", distro: "ubuntu", release: "24.04", tier: "latest" },
{ os: "linux", arch: "x64", distro: "ubuntu", release: "25.04", tier: "latest" },
{ os: "linux", arch: "x64", distro: "ubuntu", release: "24.04", tier: "latest" },
{ os: "linux", arch: "x64", baseline: true, distro: "ubuntu", release: "25.04", tier: "latest" },
{ os: "linux", arch: "x64", baseline: true, distro: "ubuntu", release: "24.04", tier: "latest" },
{ os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.22", tier: "latest" },
{ os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.22", tier: "latest" },
{ os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.22", tier: "latest" },
@@ -575,6 +572,7 @@ function getTestBunStep(platform, options, testOptions = {}) {
if (buildId) {
args.push(`--build-id=${buildId}`);
}
if (testFiles) {
args.push(...testFiles.map(testFile => `--include=${testFile}`));
}

View File

@@ -1,5 +1,9 @@
language: en-US
issue_enrichment:
auto_enrich:
enabled: false
reviews:
profile: assertive
request_changes_workflow: false

View File

@@ -1,66 +0,0 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
github.repository == 'oven-sh/bun' &&
(
(github.event_name == 'issue_comment' && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR')) ||
(github.event_name == 'pull_request_review_comment' && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR')) ||
(github.event_name == 'pull_request_review' && (github.event.review.author_association == 'MEMBER' || github.event.review.author_association == 'OWNER' || github.event.review.author_association == 'COLLABORATOR')) ||
(github.event_name == 'issues' && (github.event.issue.author_association == 'MEMBER' || github.event.issue.author_association == 'OWNER' || github.event.issue.author_association == 'COLLABORATOR'))
) &&
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: claude
env:
IS_SANDBOX: 1
container:
image: localhost:5000/claude-bun:latest
options: --privileged --user 1000:1000
permissions:
contents: read
id-token: write
steps:
- name: Checkout repository
working-directory: /workspace/bun
run: |
git config --global user.email "claude-bot@bun.sh" && \
git config --global user.name "Claude Bot" && \
git config --global url."git@github.com:".insteadOf "https://github.com/" && \
git config --global url."git@github.com:".insteadOf "http://github.com/" && \
git config --global --add safe.directory /workspace/bun && \
git config --global push.default current && \
git config --global pull.rebase true && \
git config --global init.defaultBranch main && \
git config --global core.editor "vim" && \
git config --global color.ui auto && \
git config --global fetch.prune true && \
git config --global diff.colorMoved zebra && \
git config --global merge.conflictStyle diff3 && \
git config --global rerere.enabled true && \
git config --global core.autocrlf input
git fetch origin ${{ github.event.pull_request.head.sha }}
git checkout ${{ github.event.pull_request.head.ref }}
git reset --hard origin/${{ github.event.pull_request.head.ref }}
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
timeout_minutes: "180"
claude_args: |
--dangerously-skip-permissions
--system-prompt "You are working on the Bun codebase"
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

View File

@@ -1,58 +0,0 @@
name: Codex Test Sync
on:
pull_request:
types: [labeled, opened]
env:
BUN_VERSION: "1.2.15"
jobs:
sync-node-tests:
runs-on: ubuntu-latest
if: |
(github.event.action == 'labeled' && github.event.label.name == 'codex') ||
(github.event.action == 'opened' && contains(github.event.pull_request.labels.*.name, 'codex')) ||
contains(github.head_ref, 'codex')
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Setup Bun
uses: ./.github/actions/setup-bun
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v44
with:
files: |
test/js/node/test/parallel/**/*.{js,mjs,ts}
test/js/node/test/sequential/**/*.{js,mjs,ts}
- name: Sync tests
if: steps.changed-files.outputs.any_changed == 'true'
shell: bash
run: |
echo "Changed test files:"
echo "${{ steps.changed-files.outputs.all_changed_files }}"
# Process each changed test file
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
# Extract test name from file path
test_name=$(basename "$file" | sed 's/\.[^.]*$//')
echo "Syncing test: $test_name"
bun node:test:cp "$test_name"
done
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "Sync Node.js tests with upstream"

16
.vscode/settings.json vendored
View File

@@ -27,18 +27,22 @@
"git.ignoreLimitWarning": true,
// Zig
"zig.initialSetupDone": true,
"zig.buildOption": "build",
// "zig.initialSetupDone": true,
// "zig.buildOption": "build",
"zig.zls.zigLibPath": "${workspaceFolder}/vendor/zig/lib",
"zig.buildArgs": ["-Dgenerated-code=./build/debug/codegen", "--watch", "-fincremental"],
"zig.zls.buildOnSaveStep": "check",
"zig.buildOnSaveArgs": [
"-Dgenerated-code=./build/debug/codegen",
"--watch",
"-fincremental"
],
// "zig.zls.buildOnSaveStep": "check",
// "zig.zls.enableBuildOnSave": true,
// "zig.buildOnSave": true,
"zig.buildFilePath": "${workspaceFolder}/build.zig",
// "zig.buildFilePath": "${workspaceFolder}/build.zig",
"zig.path": "${workspaceFolder}/vendor/zig/zig.exe",
"zig.zls.path": "${workspaceFolder}/vendor/zig/zls.exe",
"zig.formattingProvider": "zls",
"zig.zls.enableInlayHints": false,
// "zig.zls.enableInlayHints": false,
"[zig]": {
"editor.tabSize": 4,
"editor.useTabStops": false,

View File

@@ -6,7 +6,7 @@ This is the Bun repository - an all-in-one JavaScript runtime & toolkit designed
- **Build Bun**: `bun bd`
- Creates a debug build at `./build/debug/bun-debug`
- **CRITICAL**: no need for a timeout, the build is really fast!
- **CRITICAL**: do not set a timeout when running `bun bd`
- **Run tests with your debug build**: `bun bd test <test-file>`
- **CRITICAL**: Never use `bun test` directly - it won't include your changes
- **Run any command with debug build**: `bun bd <command>`
@@ -94,7 +94,7 @@ test("(multi-file test) my feature", async () => {
- Always use `port: 0`. Do not hardcode ports. Do not use your own random port number function.
- Use `normalizeBunSnapshot` to normalize snapshot output of the test.
- NEVER write tests that check for no "panic" or "uncaught exception" or similar in the test output. That is NOT a valid test.
- NEVER write tests that check for no "panic" or "uncaught exception" or similar in the test output. These tests will never fail in CI.
- Use `tempDir` from `"harness"` to create a temporary directory. **Do not** use `tmpdirSync` or `fs.mkdtempSync` to create temporary directories.
- When spawning processes, tests should expect(stdout).toBe(...) BEFORE expect(exitCode).toBe(0). This gives you a more useful error message on test failure.
- **CRITICAL**: Do not write flaky tests. Do not use `setTimeout` in tests. Instead, `await` the condition to be met. You are not testing the TIME PASSING, you are testing the CONDITION.

2
LATEST
View File

@@ -1 +1 @@
1.3.3
1.3.5

View File

@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "bench",

View File

@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "installbench",
@@ -12,11 +13,11 @@
"@trpc/server": "^11.0.0",
"drizzle-orm": "^0.41.0",
"esbuild": "^0.25.11",
"next": "^15.2.3",
"next-auth": "5.0.0-beta.25",
"next": "^16.1.0",
"next-auth": "5.0.0-beta.30",
"postgres": "^3.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"server-only": "^0.0.1",
"superjson": "^2.2.1",
"zod": "^3.24.2",
@@ -25,8 +26,8 @@
"@biomejs/biome": "1.9.4",
"@tailwindcss/postcss": "^4.0.15",
"@types/node": "^20.14.10",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/react": "^19.2.3",
"@types/react-dom": "^19.2.3",
"drizzle-kit": "^0.30.5",
"postcss": "^8.5.3",
"tailwindcss": "^4.0.15",
@@ -175,23 +176,23 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "3.1.2", "@jridgewell/sourcemap-codec": "1.5.5" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@next/env": ["@next/env@15.5.6", "", {}, "sha512-3qBGRW+sCGzgbpc5TS1a0p7eNxnOarGVQhZxfvTdnV0gFI61lX7QNtQ4V1TSREctXzYn5NetbUsLvyqwLFJM6Q=="],
"@next/env": ["@next/env@16.1.0", "", {}, "sha512-Dd23XQeFHmhf3KBW76leYVkejHlCdB7erakC2At2apL1N08Bm+dLYNP+nNHh0tzUXfPQcNcXiQyacw0PG4Fcpw=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ES3nRz7N+L5Umz4KoGfZ4XX6gwHplwPhioVRc25+QNsDa7RtUF/z8wJcbuQ2Tffm5RZwuN2A063eapoJ1u4nPg=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-onHq8dl8KjDb8taANQdzs3XmIqQWV3fYdslkGENuvVInFQzZnuBYYOG2HGHqqtvgmEU7xWzhgndXXxnhk4Z3fQ=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-JIGcytAyk9LQp2/nuVZPAtj8uaJ/zZhsKOASTjxDug0SPU9LAM3wy6nPU735M1OqacR4U20LHVF5v5Wnl9ptTA=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.1.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Am6VJTp8KhLuAH13tPrAoVIXzuComlZlMwGr++o2KDjWiKPe3VwpxYhgV6I4gKls2EnsIMggL4y7GdXyDdJcFA=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-qvz4SVKQ0P3/Im9zcS2RmfFL/UCQnsJKJwQSkissbngnB/12c6bZTCB0gHTexz1s6d/mD0+egPKXAIRFVS7hQg=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-fVicfaJT6QfghNyg8JErZ+EMNQ812IS0lmKfbmC01LF1nFBcKfcs4Q75Yy8IqnsCqH/hZwGhqzj3IGVfWV6vpA=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-FsbGVw3SJz1hZlvnWD+T6GFgV9/NYDeLTNQB2MXoPN5u9VA9OEDy6fJEfePfsUKAhJufFbZLgp0cPxMuV6SV0w=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-TojQnDRoX7wJWXEEwdfuJtakMDW64Q7NrxQPviUnfYJvAx5/5wcGE+1vZzQ9F17m+SdpFeeXuOr6v3jbyusYMQ=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-3QnHGFWlnvAgyxFxt2Ny8PTpXtQD7kVEeaFat5oPAHHI192WKYB+VIKZijtHLGdBBvc16tiAkPTDmQNOQ0dyrA=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-quhNFVySW4QwXiZkZ34SbfzNBm27vLrxZ2HwTfFFO1BBP0OY1+pI0nbyewKeq1FriqU+LZrob/cm26lwsiAi8Q=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-OsGX148sL+TqMK9YFaPFPoIaJKbFJJxFzkXZljIgA9hjMjdruKht6xDCEv1HLtlLNfkx3c5w2GLKhj7veBQizQ=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-6JW0z2FZUK5iOVhUIWqE4RblAhUj1EwhZ/MwteGb//SpFTOHydnhbp3868gxalwea+mbOLWO6xgxj9wA9wNvNw=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-ONOMrqWxdzXDJNh2n60H6gGyKed42Ieu6UTVPZteXpuKbLZTH4G4eBMsr5qWgOBA+s7F+uB4OJbZnrkEDnZ5Fg=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.1.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-+DK/akkAvvXn5RdYN84IOmLkSy87SCmpofJPdB8vbLmf01BzntPBSYXnMvnEEv/Vcf3HYJwt24QZ/s6sWAwOMQ=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-pxK4VIjFRx1MY92UycLOOw7dTdvccWsNETQ0kDHkBlcFH1GrTLUjSiHU1ohrznnux6TqRHgv5oflhfIWZwVROQ=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.1.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Tr0j94MphimCCks+1rtYPzQFK+faJuhHWCegU9S9gDlgyOk8Y3kPmO64UcjyzZAlligeBtYZ/2bEyrKq0d2wqQ=="],
"@panva/hkdf": ["@panva/hkdf@1.2.1", "", {}, "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw=="],
@@ -243,13 +244,13 @@
"@trpc/server": ["@trpc/server@11.7.1", "", { "peerDependencies": { "typescript": "5.9.3" } }, "sha512-N3U8LNLIP4g9C7LJ/sLkjuPHwqlvE3bnspzC4DEFVdvx2+usbn70P80E3wj5cjOTLhmhRiwJCSXhlB+MHfGeCw=="],
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
"@types/node": ["@types/node@20.19.24", "", { "dependencies": { "undici-types": "6.21.0" } }, "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA=="],
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "3.1.3" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
"@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="],
"@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "19.2.2" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="],
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.11", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ=="],
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
@@ -257,11 +258,9 @@
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
"cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="],
"copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "5.5.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
@@ -323,9 +322,9 @@
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"next": ["next@15.5.6", "", { "dependencies": { "@next/env": "15.5.6", "@swc/helpers": "0.5.15", "caniuse-lite": "1.0.30001752", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.6", "@next/swc-darwin-x64": "15.5.6", "@next/swc-linux-arm64-gnu": "15.5.6", "@next/swc-linux-arm64-musl": "15.5.6", "@next/swc-linux-x64-gnu": "15.5.6", "@next/swc-linux-x64-musl": "15.5.6", "@next/swc-win32-arm64-msvc": "15.5.6", "@next/swc-win32-x64-msvc": "15.5.6", "sharp": "0.34.4" }, "peerDependencies": { "react": "19.2.0", "react-dom": "19.2.0" }, "bin": { "next": "dist/bin/next" } }, "sha512-zTxsnI3LQo3c9HSdSf91O1jMNsEzIXDShXd4wVdg9y5shwLqBXi4ZtUUJyB86KGVSJLZx0PFONvO54aheGX8QQ=="],
"next": ["next@16.1.0", "", { "dependencies": { "@next/env": "16.1.0", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.0", "@next/swc-darwin-x64": "16.1.0", "@next/swc-linux-arm64-gnu": "16.1.0", "@next/swc-linux-arm64-musl": "16.1.0", "@next/swc-linux-x64-gnu": "16.1.0", "@next/swc-linux-x64-musl": "16.1.0", "@next/swc-win32-arm64-msvc": "16.1.0", "@next/swc-win32-x64-msvc": "16.1.0", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-Y+KbmDbefYtHDDQKLNrmzE/YYzG2msqo2VXhzh5yrJ54tx/6TmGdkR5+kP9ma7i7LwZpZMfoY3m/AoPPPKxtVw=="],
"next-auth": ["next-auth@5.0.0-beta.25", "", { "dependencies": { "@auth/core": "0.37.2" }, "peerDependencies": { "next": "15.5.6", "react": "19.2.0" } }, "sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog=="],
"next-auth": ["next-auth@5.0.0-beta.30", "", { "dependencies": { "@auth/core": "0.41.0" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "next": "^14.0.0-0 || ^15.0.0 || ^16.0.0", "nodemailer": "^7.0.7", "react": "^18.2.0 || ^19.0.0" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-+c51gquM3F6nMVmoAusRJ7RIoY0K4Ts9HCCwyy/BRoe4mp3msZpOzYMyb5LAYc1wSo74PMQkGDcaghIO7W6Xjg=="],
"oauth4webapi": ["oauth4webapi@3.8.2", "", {}, "sha512-FzZZ+bht5X0FKe7Mwz3DAVAmlH1BV5blSak/lHMBKz0/EBMhX6B10GlQYI51+oRp8ObJaX0g6pXrAxZh5s8rjw=="],
@@ -339,11 +338,9 @@
"preact-render-to-string": ["preact-render-to-string@6.5.11", "", { "peerDependencies": { "preact": "10.24.3" } }, "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw=="],
"pretty-format": ["pretty-format@3.8.0", "", {}, "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="],
"react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="],
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "0.27.0" }, "peerDependencies": { "react": "19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
"react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="],
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
@@ -387,7 +384,7 @@
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "3.3.11", "picocolors": "1.1.1", "source-map-js": "1.2.1" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
"next-auth/@auth/core": ["@auth/core@0.37.2", "", { "dependencies": { "@panva/hkdf": "1.2.1", "@types/cookie": "0.6.0", "cookie": "0.7.1", "jose": "5.10.0", "oauth4webapi": "3.8.2", "preact": "10.11.3", "preact-render-to-string": "5.2.3" } }, "sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw=="],
"next-auth/@auth/core": ["@auth/core@0.41.0", "", { "dependencies": { "@panva/hkdf": "^1.2.1", "jose": "^6.0.6", "oauth4webapi": "^3.3.0", "preact": "10.24.3", "preact-render-to-string": "6.5.11" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "nodemailer": "^6.8.0" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
@@ -478,11 +475,5 @@
"drizzle-kit/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ=="],
"drizzle-kit/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="],
"next-auth/@auth/core/jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="],
"next-auth/@auth/core/preact": ["preact@10.11.3", "", {}, "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg=="],
"next-auth/@auth/core/preact-render-to-string": ["preact-render-to-string@5.2.3", "", { "dependencies": { "pretty-format": "3.8.0" }, "peerDependencies": { "preact": "10.11.3" } }, "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA=="],
}
}

View File

@@ -26,11 +26,11 @@
"@trpc/server": "^11.0.0",
"drizzle-orm": "^0.41.0",
"esbuild": "^0.25.11",
"next": "^15.2.3",
"next-auth": "5.0.0-beta.25",
"next": "^16.1.0",
"next-auth": "5.0.0-beta.30",
"postgres": "^3.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"server-only": "^0.0.1",
"superjson": "^2.2.1",
"zod": "^3.24.2"
@@ -39,8 +39,8 @@
"@biomejs/biome": "1.9.4",
"@tailwindcss/postcss": "^4.0.15",
"@types/node": "^20.14.10",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/react": "^19.2.3",
"@types/react-dom": "^19.2.3",
"drizzle-kit": "^0.30.5",
"postcss": "^8.5.3",
"tailwindcss": "^4.0.15",

View File

@@ -1,18 +1,19 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "react-hello-world",
"dependencies": {
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react": "^19.2.3",
"react-dom": "^19.2.3",
},
},
},
"packages": {
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
"react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="],
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
"react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
}

View File

@@ -11,7 +11,7 @@
"author": "Colin McDonnell",
"license": "ISC",
"dependencies": {
"react": "^19.2.0",
"react-dom": "^19.2.0"
"react": "^19.2.3",
"react-dom": "^19.2.3"
}
}

View File

@@ -0,0 +1,48 @@
import { bench, group, run } from "../runner.mjs";
const patterns = [
{ name: "string pattern", input: "https://(sub.)?example(.com/)foo" },
{ name: "hostname IDN", input: { hostname: "xn--caf-dma.com" } },
{
name: "pathname + search + hash + baseURL",
input: {
pathname: "/foo",
search: "bar",
hash: "baz",
baseURL: "https://example.com:8080",
},
},
{ name: "pathname with regex", input: { pathname: "/([[a-z]--a])" } },
{ name: "named groups", input: { pathname: "/users/:id/posts/:postId" } },
{ name: "wildcard", input: { pathname: "/files/*" } },
];
const testURL = "https://sub.example.com/foo";
group("URLPattern parse (constructor)", () => {
for (const { name, input } of patterns) {
bench(name, () => {
return new URLPattern(input);
});
}
});
group("URLPattern.test()", () => {
for (const { name, input } of patterns) {
const pattern = new URLPattern(input);
bench(name, () => {
return pattern.test(testURL);
});
}
});
group("URLPattern.exec()", () => {
for (const { name, input } of patterns) {
const pattern = new URLPattern(input);
bench(name, () => {
return pattern.exec(testURL);
});
}
});
await run();

View File

@@ -607,7 +607,7 @@ fn configureObj(b: *Build, opts: *BunBuildOptions, obj: *Compile) void {
obj.llvm_codegen_threads = opts.llvm_codegen_threads orelse 0;
}
obj.no_link_obj = opts.os != .windows;
obj.no_link_obj = opts.os != .windows and !opts.no_llvm;
if (opts.enable_asan and !enableFastBuild(b)) {

View File

@@ -36,6 +36,7 @@
},
"overrides": {
"@types/bun": "workspace:packages/@types/bun",
"@types/node": "25.0.0",
"bun-types": "workspace:packages/bun-types",
},
"packages": {
@@ -155,7 +156,7 @@
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"@types/node": ["@types/node@25.0.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-rl78HwuZlaDIUSeUKkmogkhebA+8K1Hy7tddZuJ3D0xV8pZSfsYGTsliGUol1JPzu9EKnTxPC4L1fiWouStRew=="],
"aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],

View File

@@ -872,6 +872,7 @@ target_include_directories(${bun} PRIVATE
${CODEGEN_PATH}
${VENDOR_PATH}
${VENDOR_PATH}/picohttpparser
${VENDOR_PATH}/zlib
${NODEJS_HEADERS_PATH}/include
${NODEJS_HEADERS_PATH}/include/node
)
@@ -1199,6 +1200,29 @@ set_target_properties(${bun} PROPERTIES LINK_DEPENDS ${BUN_SYMBOLS_PATH})
include(SetupWebKit)
if(BUN_LINK_ONLY)
register_command(
TARGET
${bun}
TARGET_PHASE
POST_BUILD
COMMENT
"Uploading link metadata"
COMMAND
${CMAKE_COMMAND} -E env
BUN_VERSION=${VERSION}
WEBKIT_DOWNLOAD_URL=${WEBKIT_DOWNLOAD_URL}
WEBKIT_VERSION=${WEBKIT_VERSION}
ZIG_COMMIT=${ZIG_COMMIT}
${BUN_EXECUTABLE} ${CWD}/scripts/create-link-metadata.mjs ${BUILD_PATH} ${bun}
SOURCES
${BUN_ZIG_OUTPUT}
${BUN_CPP_OUTPUT}
ARTIFACTS
${BUILD_PATH}/link-metadata.json
)
endif()
if(WIN32)
if(DEBUG)
target_link_libraries(${bun} PRIVATE

View File

@@ -4,7 +4,7 @@ register_repository(
REPOSITORY
c-ares/c-ares
COMMIT
d3a507e920e7af18a5efb7f9f1d8044ed4750013
3ac47ee46edd8ea40370222f91613fc16c434853
)
register_cmake_command(

View File

@@ -2,7 +2,7 @@ option(WEBKIT_VERSION "The version of WebKit to use")
option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading")
if(NOT WEBKIT_VERSION)
set(WEBKIT_VERSION 6d0f3aac0b817cc01a846b3754b21271adedac12)
set(WEBKIT_VERSION 8400ec68b2649d7b95f10576ad648c3aa8a92b77)
endif()
string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX)

View File

@@ -1,4 +1,4 @@
FROM debian:bookworm-slim AS build
FROM debian:trixie-slim AS build
# https://github.com/oven-sh/bun/releases
ARG BUN_VERSION=latest
@@ -55,7 +55,7 @@ RUN apt-get update -qq \
&& which bun \
&& bun --version
FROM debian:bookworm-slim
FROM debian:trixie-slim
# Disable the runtime transpiler cache by default inside Docker containers.
# On ephemeral containers, the cache is not useful

View File

@@ -1,4 +1,4 @@
FROM debian:bookworm-slim AS build
FROM debian:trixie-slim AS build
# https://github.com/oven-sh/bun/releases
ARG BUN_VERSION=latest
@@ -56,7 +56,7 @@ RUN apt-get update -qq \
&& rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \
&& chmod +x /usr/local/bin/bun
FROM debian:bookworm
FROM debian:trixie
COPY docker-entrypoint.sh /usr/local/bin
COPY --from=build /usr/local/bin/bun /usr/local/bin/bun

View File

@@ -1,4 +1,4 @@
FROM debian:bookworm-slim AS build
FROM debian:trixie-slim AS build
# https://github.com/oven-sh/bun/releases
ARG BUN_VERSION=latest
@@ -55,7 +55,7 @@ RUN apt-get update -qq \
&& which bun \
&& bun --version
FROM gcr.io/distroless/base-nossl-debian11
FROM gcr.io/distroless/base-nossl-debian13
# Disable the runtime transpiler cache by default inside Docker containers.
# On ephemeral containers, the cache is not useful
@@ -71,6 +71,7 @@ ENV PATH "${PATH}:/usr/local/bun-node-fallback-bin"
# Temporarily use the `build`-stage image binaries to create a symlink:
RUN --mount=type=bind,from=build,source=/usr/bin,target=/usr/bin \
--mount=type=bind,from=build,source=/etc/alternatives/which,target=/etc/alternatives/which \
--mount=type=bind,from=build,source=/bin,target=/bin \
--mount=type=bind,from=build,source=/usr/lib,target=/usr/lib \
--mount=type=bind,from=build,source=/lib,target=/lib \

View File

@@ -65,6 +65,7 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot
| `--chunk-names` | `--chunk-naming` | Renamed for consistency with naming in JS API |
| `--color` | n/a | Always enabled |
| `--drop` | `--drop` | |
| n/a | `--feature` | Bun-specific. Enable feature flags for compile-time dead-code elimination via `import { feature } from "bun:bundle"` |
| `--entry-names` | `--entry-naming` | Renamed for consistency with naming in JS API |
| `--global-name` | n/a | Not applicable, Bun does not support `iife` output at this time |
| `--ignore-annotations` | `--ignore-dce-annotations` | |

File diff suppressed because it is too large Load Diff

View File

@@ -427,8 +427,8 @@ This will allow you to use TailwindCSS utility classes in your HTML and CSS file
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="tailwindcss" />
<!-- [!code ++] -->
<link rel="stylesheet" href="tailwindcss" />
</head>
<!-- the rest of your HTML... -->
</html>
@@ -448,8 +448,8 @@ Alternatively, you can import TailwindCSS in your CSS file:
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="./style.css" />
<!-- [!code ++] -->
<link rel="stylesheet" href="./style.css" />
</head>
<!-- the rest of your HTML... -->
</html>
@@ -492,6 +492,28 @@ Bun will lazily resolve and load each plugin and use them to bundle your routes.
the CLI.
</Note>
## Inline Environment Variables
Bun can replace `process.env.*` references in your frontend JavaScript and TypeScript with their actual values at build time. Configure the `env` option in your `bunfig.toml`:
```toml title="bunfig.toml" icon="settings"
[serve.static]
env = "PUBLIC_*" # only inline env vars starting with PUBLIC_ (recommended)
# env = "inline" # inline all environment variables
# env = "disable" # disable env var replacement (default)
```
<Note>
This only works with literal `process.env.FOO` references, not `import.meta.env` or indirect access like `const env =
process.env; env.FOO`.
If an environment variable is not set, you may see runtime errors like `ReferenceError: process
is not defined` in the browser.
</Note>
See the [HTML & static sites documentation](/bundler/html-static#inline-environment-variables) for more details on build-time configuration and examples.
## How It Works
Bun uses `HTMLRewriter` to scan for `<script>` and `<link>` tags in HTML files, uses them as entrypoints for Bun's bundler, generates an optimized bundle for the JavaScript/TypeScript/TSX/JSX and CSS files, and serves the result.

View File

@@ -262,6 +262,93 @@ Then, reference TailwindCSS in your HTML via `<link>` tag, `@import` in CSS, or
<Info>Only one of those are necessary, not all three.</Info>
## Inline environment variables
Bun can replace `process.env.*` references in your JavaScript and TypeScript with their actual values at build time. This is useful for injecting configuration like API URLs or feature flags into your frontend code.
### Dev server (runtime)
To inline environment variables when using `bun ./index.html`, configure the `env` option in your `bunfig.toml`:
```toml title="bunfig.toml" icon="settings"
[serve.static]
env = "PUBLIC_*" # only inline env vars starting with PUBLIC_ (recommended)
# env = "inline" # inline all environment variables
# env = "disable" # disable env var replacement (default)
```
<Note>
This only works with literal `process.env.FOO` references, not `import.meta.env` or indirect access like `const env =
process.env; env.FOO`.
If an environment variable is not set, you may see runtime errors like `ReferenceError: process
is not defined` in the browser.
</Note>
Then run the dev server:
```bash terminal icon="terminal"
PUBLIC_API_URL=https://api.example.com bun ./index.html
```
### Build for production
When building static HTML for production, use the `env` option to inline environment variables:
<Tabs>
<Tab title="CLI">
```bash terminal icon="terminal"
# Inline all environment variables
bun build ./index.html --outdir=dist --env=inline
# Only inline env vars with a specific prefix (recommended)
bun build ./index.html --outdir=dist --env=PUBLIC_*
```
</Tab>
<Tab title="API">
```ts title="build.ts" icon="/icons/typescript.svg"
// Inline all environment variables
await Bun.build({
entrypoints: ["./index.html"],
outdir: "./dist",
env: "inline", // [!code highlight]
});
// Only inline env vars with a specific prefix (recommended)
await Bun.build({
entrypoints: ["./index.html"],
outdir: "./dist",
env: "PUBLIC_*", // [!code highlight]
});
```
</Tab>
</Tabs>
### Example
Given this source file:
```ts title="app.ts" icon="/icons/typescript.svg"
const apiUrl = process.env.PUBLIC_API_URL;
console.log(`API URL: ${apiUrl}`);
```
And running with `PUBLIC_API_URL=https://api.example.com`:
```bash terminal icon="terminal"
PUBLIC_API_URL=https://api.example.com bun build ./index.html --outdir=dist --env=PUBLIC_*
```
The bundled output will contain:
```js title="dist/app.js" icon="/icons/javascript.svg"
const apiUrl = "https://api.example.com";
console.log(`API URL: ${apiUrl}`);
```
## Echo console logs from browser to terminal
Bun's dev server supports streaming console logs from the browser to the terminal.

View File

@@ -1141,6 +1141,84 @@ Remove function calls from a bundle. For example, `--drop=console` will remove a
</Tab>
</Tabs>
### features
Enable compile-time feature flags for dead-code elimination. This provides a way to conditionally include or exclude code paths at bundle time using `import { feature } from "bun:bundle"`.
```ts title="app.ts" icon="/icons/typescript.svg"
import { feature } from "bun:bundle";
if (feature("PREMIUM")) {
// Only included when PREMIUM flag is enabled
initPremiumFeatures();
}
if (feature("DEBUG")) {
// Only included when DEBUG flag is enabled
console.log("Debug mode");
}
```
<Tabs>
<Tab title="JavaScript">
```ts title="build.ts" icon="/icons/typescript.svg"
await Bun.build({
entrypoints: ['./app.ts'],
outdir: './out',
features: ["PREMIUM"], // PREMIUM=true, DEBUG=false
})
```
</Tab>
<Tab title="CLI">
```bash terminal icon="terminal"
bun build ./app.ts --outdir ./out --feature PREMIUM
```
</Tab>
</Tabs>
The `feature()` function is replaced with `true` or `false` at bundle time. Combined with minification, unreachable code is eliminated:
```ts title="Input" icon="/icons/typescript.svg"
import { feature } from "bun:bundle";
const mode = feature("PREMIUM") ? "premium" : "free";
```
```js title="Output (with --feature PREMIUM --minify)" icon="/icons/javascript.svg"
var mode = "premium";
```
```js title="Output (without --feature PREMIUM, with --minify)" icon="/icons/javascript.svg"
var mode = "free";
```
**Key behaviors:**
- `feature()` requires a string literal argument — dynamic values are not supported
- The `bun:bundle` import is completely removed from the output
- Works with `bun build`, `bun run`, and `bun test`
- Multiple flags can be enabled: `--feature FLAG_A --feature FLAG_B`
- For type safety, augment the `Registry` interface to restrict `feature()` to known flags (see below)
**Use cases:**
- Platform-specific code (`feature("SERVER")` vs `feature("CLIENT")`)
- Environment-based features (`feature("DEVELOPMENT")`)
- Gradual feature rollouts
- A/B testing variants
- Paid tier features
**Type safety:** By default, `feature()` accepts any string. To get autocomplete and catch typos at compile time, create an `env.d.ts` file (or add to an existing `.d.ts`) and augment the `Registry` interface:
```ts title="env.d.ts" icon="/icons/typescript.svg"
declare module "bun:bundle" {
interface Registry {
features: "DEBUG" | "PREMIUM" | "BETA_FEATURES";
}
}
```
Ensure the file is included in your `tsconfig.json` (e.g., `"include": ["src", "env.d.ts"]`). Now `feature()` only accepts those flags, and invalid strings like `feature("TYPO")` become type errors.
## Outputs
The `Bun.build` function returns a `Promise<BuildOutput>`, defined as:

View File

@@ -326,6 +326,7 @@
"group": "Utilities",
"icon": "wrench",
"pages": [
"/guides/util/upgrade",
"/guides/util/detect-bun",
"/guides/util/version",
"/guides/util/hash-a-password",

View File

@@ -74,6 +74,12 @@ export default defineNuxtConfig({
});
```
Alternatively, you can set the preset via environment variable:
```sh terminal icon="terminal"
NITRO_PRESET=bun bun run build
```
<Note>
Some packages provide Bun-specific exports that Nitro will not bundle correctly using the default preset. In this
case, you need to use Bun preset so that the packages will work correctly in production builds.

View File

@@ -4,63 +4,59 @@ sidebarTitle: "SolidStart with Bun"
mode: center
---
<Warning>
SolidStart currently relies on Node.js APIs that Bun does not yet implement. The guide below uses Bun to initialize a
project and install dependencies, but it uses Node.js to run the dev server.
</Warning>
---
Initialize a SolidStart app with `create-solid`.
Initialize a SolidStart app with `create-solid`. You can specify the `--solidstart` flag to create a SolidStart project, and `--ts` for TypeScript support. When prompted for a template, select `basic` for a minimal starter app.
```sh terminal icon="terminal"
bun create solid my-app
bun create solid my-app --solidstart --ts
```
```txt
create-solid version 0.2.31
Welcome to the SolidStart setup wizard!
There are definitely bugs and some feature might not work yet.
If you encounter an issue, have a look at
https://github.com/solidjs/solid-start/issues and open a new one,
if it is not already tracked.
✔ Which template do you want to use? todomvc
✔ Server Side Rendering? … yes
✔ Use TypeScript? … yes
cloned solidjs/solid-start#main to /path/to/my-app/.solid-start
✔ Copied project files
Create-Solid v0.6.11
◇ Project Name
│ my-app
◇ Which template would you like to use?
│ basic
◇ Project created 🎉
◇ To get started, run: ─╮
│ │
│ cd my-app │
│ bun install │
│ bun dev │
│ │
├────────────────────────╯
```
---
As instructed by the `create-solid` CLI, let's install our dependencies.
As instructed by the `create-solid` CLI, install the dependencies.
```sh terminal icon="terminal"
cd my-app
bun install
```
---
Then run the development server.
Then run the development server with `bun dev`.
```sh terminal icon="terminal"
bun run dev
# or, equivalently
bunx solid-start dev
bun dev
```
---
```txt
$ vinxi dev
vinxi v0.5.8
vinxi starting dev server
➜ Local: http://localhost:3000/
➜ Network: use --host to expose
```
Open [localhost:3000](http://localhost:3000). Any changes you make to `src/routes/index.tsx` will be hot-reloaded automatically.
<Frame>
![SolidStart demo app](https://github.com/oven-sh/bun/assets/3084745/1e8043c4-49d1-498c-9add-c1eaab6c7167)
</Frame>
---
Refer to the [SolidStart website](https://start.solidjs.com/getting-started/what-is-solidstart) for complete framework documentation.
Refer to the [SolidStart website](https://docs.solidjs.com/solid-start) for complete framework documentation.

View File

@@ -9,18 +9,42 @@ In Bun, `fetch` supports sending requests through an HTTP or HTTPS proxy. This i
```ts proxy.ts icon="/icons/typescript.svg"
await fetch("https://example.com", {
// The URL of the proxy server
proxy: "https://usertitle:password@proxy.example.com:8080",
proxy: "https://username:password@proxy.example.com:8080",
});
```
---
The `proxy` option is a URL string that specifies the proxy server. It can include the username and password if the proxy requires authentication. It can be `http://` or `https://`.
The `proxy` option can be a URL string or an object with `url` and optional `headers`. The URL can include the username and password if the proxy requires authentication. It can be `http://` or `https://`.
---
## Custom proxy headers
To send custom headers to the proxy server (useful for proxy authentication tokens, custom routing, etc.), use the object format:
```ts proxy-headers.ts icon="/icons/typescript.svg"
await fetch("https://example.com", {
proxy: {
url: "https://proxy.example.com:8080",
headers: {
"Proxy-Authorization": "Bearer my-token",
"X-Proxy-Region": "us-east-1",
},
},
});
```
The `headers` property accepts a plain object or a `Headers` instance. These headers are sent directly to the proxy server in `CONNECT` requests (for HTTPS targets) or in the proxy request (for HTTP targets).
If you provide a `Proxy-Authorization` header, it will override any credentials specified in the proxy URL.
---
## Environment variables
You can also set the `$HTTP_PROXY` or `$HTTPS_PROXY` environment variable to the proxy URL. This is useful when you want to use the same proxy for all requests.
```sh terminal icon="terminal"
HTTPS_PROXY=https://usertitle:password@proxy.example.com:8080 bun run index.ts
HTTPS_PROXY=https://username:password@proxy.example.com:8080 bun run index.ts
```

View File

@@ -8,7 +8,9 @@ Unlike other npm clients, Bun does not execute arbitrary lifecycle scripts for i
<Note>
Bun includes a default allowlist of popular packages containing `postinstall` scripts that are known to be safe. You
can see this list [here](https://github.com/oven-sh/bun/blob/main/src/install/default-trusted-dependencies.txt).
can see this list [here](https://github.com/oven-sh/bun/blob/main/src/install/default-trusted-dependencies.txt). This
default list only applies to packages installed from npm. For packages from other sources (such as `file:`, `link:`,
`git:`, or `github:` dependencies), you must explicitly add them to `trustedDependencies`.
</Note>
---

View File

@@ -14,16 +14,6 @@ process.on("SIGINT", () => {
---
If you don't know which signal to listen for, you listen to the umbrella `"exit"` event.
```ts
process.on("exit", code => {
console.log(`Process exited with code ${code}`);
});
```
---
If you don't know which signal to listen for, you listen to the [`"beforeExit"`](https://nodejs.org/api/process.html#event-beforeexit) and [`"exit"`](https://nodejs.org/api/process.html#event-exit) events.
```ts

View File

@@ -60,7 +60,7 @@ test("random", async () => {
expect(random).toHaveBeenCalled();
expect(random).toHaveBeenCalledTimes(3);
expect(random.mock.args).toEqual([[1], [2], [3]]);
expect(random.mock.calls).toEqual([[1], [2], [3]]);
expect(random.mock.results[0]).toEqual({ type: "return", value: a });
});
```

View File

@@ -76,7 +76,7 @@ declare module "bun:test" {
You should now be able to use Testing Library in your tests
```ts matchers.d.ts icon="/icons/typescript.svg"
```tsx myComponent.test.tsx icon="/icons/typescript.svg"
import { test, expect } from "bun:test";
import { screen, render } from "@testing-library/react";
import { MyComponent } from "./myComponent";

View File

@@ -4,22 +4,25 @@ sidebarTitle: Detect Bun
mode: center
---
The recommended way to conditionally detect when code is being executed with `bun` is to check for the existence of the `Bun` global.
This is similar to how you'd check for the existence of the `window` variable to detect when code is being executed in a browser.
```ts
if (typeof Bun !== "undefined") {
// this code will only run when the file is run with Bun
}
```
---
In TypeScript environments, the previous approach will result in a type error unless `@types/bun` is installed. To avoid this, you can check `process.versions` instead.
The recommended way to detect when code is being executed with Bun is to check `process.versions.bun`. This works in both JavaScript and TypeScript without requiring any additional type definitions.
```ts
if (process.versions.bun) {
// this code will only run when the file is run with Bun
}
```
---
Alternatively, you can check for the existence of the `Bun` global. This is similar to how you'd check for the existence of the `window` variable to detect when code is being executed in a browser.
<Note>
This approach will result in a type error in TypeScript unless `@types/bun` is installed. You can install it with `bun
add -d @types/bun`.
</Note>
```ts
if (typeof Bun !== "undefined") {
// this code will only run when the file is run with Bun
}
```

View File

@@ -0,0 +1,93 @@
---
title: Upgrade Bun to the latest version
sidebarTitle: Upgrade Bun
mode: center
---
Bun can upgrade itself using the built-in `bun upgrade` command. This is the fastest way to get the latest features and bug fixes.
```bash terminal icon="terminal"
bun upgrade
```
This downloads and installs the latest stable version of Bun, replacing the currently installed version.
<Note>To see the current version of Bun, run `bun --version`.</Note>
---
## Verify the upgrade
After upgrading, verify the new version:
```bash terminal icon="terminal"
bun --version
# Output: 1.x.y
# See the exact commit of the Bun binary
bun --revision
# Output: 1.x.y+abc123def
```
---
## Upgrade to canary builds
Canary builds are automatically released on every commit to the `main` branch. These are untested but useful for trying new features or verifying bug fixes before they're released.
```bash terminal icon="terminal"
bun upgrade --canary
```
<Warning>Canary builds are not recommended for production use. They may contain bugs or breaking changes.</Warning>
---
## Switch back to stable
If you're on a canary build and want to return to the latest stable release:
```bash terminal icon="terminal"
bun upgrade --stable
```
---
## Install a specific version
To install a specific version of Bun, use the install script with a version tag:
<Tabs>
<Tab title="macOS & Linux">
```bash terminal icon="terminal"
curl -fsSL https://bun.sh/install | bash -s "bun-v1.3.3"
```
</Tab>
<Tab title="Windows">
```powershell PowerShell icon="windows"
iex "& {$(irm https://bun.sh/install.ps1)} -Version 1.3.3"
```
</Tab>
</Tabs>
---
## Package manager users
If you installed Bun via a package manager, use that package manager to upgrade instead of `bun upgrade` to avoid conflicts.
<Tip>
**Homebrew users** <br />
To avoid conflicts with Homebrew, use `brew upgrade bun` instead.
**Scoop users** <br />
To avoid conflicts with Scoop, use `scoop update bun` instead.
</Tip>
---
## See also
- [Installation](/installation) — Install Bun for the first time
- [Update packages](/pm/cli/update) — Update dependencies to latest versions

View File

@@ -3,6 +3,8 @@ title: "bunx"
description: "Run packages from npm"
---
import Bunx from "/snippets/cli/bunx.mdx";
<Note>`bunx` is an alias for `bun x`. The `bunx` CLI will be auto-installed when you install `bun`.</Note>
Use `bunx` to auto-install and run packages from `npm`. It's Bun's equivalent of `npx` or `yarn dlx`.
@@ -52,6 +54,8 @@ To pass additional command-line flags and arguments through to the executable, p
bunx my-cli --foo bar
```
---
## Shebangs
By default, Bun respects shebangs. If an executable is marked with `#!/usr/bin/env node`, Bun will spin up a `node` process to execute the file. However, in some cases it may be desirable to run executables using Bun's runtime, even if the executable indicates otherwise. To do so, include the `--bun` flag.
@@ -81,3 +85,7 @@ To force bun to always be used with a script, use a shebang.
```js dist/index.js icon="/icons/javascript.svg"
#!/usr/bin/env bun
```
---
<Bunx />

View File

@@ -46,6 +46,13 @@ Once added to `trustedDependencies`, install/re-install the package. Bun will re
The top 500 npm packages with lifecycle scripts are allowed by default. You can see the full list [here](https://github.com/oven-sh/bun/blob/main/src/install/default-trusted-dependencies.txt).
<Note>
The default trusted dependencies list only applies to packages installed from npm. For packages from other sources
(such as `file:`, `link:`, `git:`, or `github:` dependencies), you must explicitly add them to `trustedDependencies`
to run their lifecycle scripts, even if the package name matches an entry in the default list. This prevents malicious
packages from spoofing trusted package names through local file paths or git repositories.
</Note>
---
## `--ignore-scripts`

View File

@@ -107,12 +107,34 @@ Bun supports the following loaders:
### `telemetry`
The `telemetry` field permit to enable/disable the analytics records. Bun records bundle timings (so we can answer with data, "is Bun getting faster?") and feature usage (e.g., "are people actually using macros?"). The request body size is about 60 bytes, so it's not a lot of data. By default the telemetry is enabled. Equivalent of `DO_NOT_TRACK` env variable.
The `telemetry` field is used to enable/disable analytics. By default, telemetry is enabled. This is equivalent to the `DO_NOT_TRACK` environment variable.
Currently we do not collect telemetry and this setting is only used for enabling/disabling anonymous crash reports, but in the future we plan to collect information like which Bun APIs are used most or how long `bun build` takes.
```toml title="bunfig.toml" icon="settings"
telemetry = false
```
### `env`
Configure automatic `.env` file loading. By default, Bun automatically loads `.env` files. To disable this behavior:
```toml title="bunfig.toml" icon="settings"
# Disable automatic .env file loading
env = false
```
You can also use object syntax with the `file` property:
```toml title="bunfig.toml" icon="settings"
[env]
file = false
```
This is useful in production environments or CI/CD pipelines where you want to rely solely on system environment variables.
Note: Explicitly provided environment files via `--env-file` will still be loaded even when default loading is disabled.
### `console`
Configure console output behavior.

View File

@@ -315,6 +315,109 @@ if (typeof Bun !== "undefined") {
---
## Terminal (PTY) support
For interactive terminal applications, you can spawn a subprocess with a pseudo-terminal (PTY) attached using the `terminal` option. This makes the subprocess think it's running in a real terminal, enabling features like colored output, cursor movement, and interactive prompts.
```ts
const proc = Bun.spawn(["bash"], {
terminal: {
cols: 80,
rows: 24,
data(terminal, data) {
// Called when data is received from the terminal
process.stdout.write(data);
},
},
});
// Write to the terminal
proc.terminal.write("echo hello\n");
// Wait for the process to exit
await proc.exited;
// Close the terminal
proc.terminal.close();
```
When the `terminal` option is provided:
- The subprocess sees `process.stdout.isTTY` as `true`
- `stdin`, `stdout`, and `stderr` are all connected to the terminal
- `proc.stdin`, `proc.stdout`, and `proc.stderr` return `null` — use the terminal instead
- Access the terminal via `proc.terminal`
### Terminal options
| Option | Description | Default |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ |
| `cols` | Number of columns | `80` |
| `rows` | Number of rows | `24` |
| `name` | Terminal type for PTY configuration (set `TERM` env var separately via `env` option) | `"xterm-256color"` |
| `data` | Callback when data is received `(terminal, data) => void` | — |
| `exit` | Callback when PTY stream closes (EOF or error). `exitCode` is PTY lifecycle status (0=EOF, 1=error), not subprocess exit code. Use `proc.exited` for process exit. | — |
| `drain` | Callback when ready for more data `(terminal) => void` | — |
### Terminal methods
The `Terminal` object returned by `proc.terminal` has the following methods:
```ts
// Write data to the terminal
proc.terminal.write("echo hello\n");
// Resize the terminal
proc.terminal.resize(120, 40);
// Set raw mode (disable line buffering and echo)
proc.terminal.setRawMode(true);
// Keep event loop alive while terminal is open
proc.terminal.ref();
proc.terminal.unref();
// Close the terminal
proc.terminal.close();
```
### Reusable Terminal
You can create a terminal independently and reuse it across multiple subprocesses:
```ts
await using terminal = new Bun.Terminal({
cols: 80,
rows: 24,
data(term, data) {
process.stdout.write(data);
},
});
// Spawn first process
const proc1 = Bun.spawn(["echo", "first"], { terminal });
await proc1.exited;
// Reuse terminal for another process
const proc2 = Bun.spawn(["echo", "second"], { terminal });
await proc2.exited;
// Terminal is closed automatically by `await using`
```
When passing an existing `Terminal` object:
- The terminal can be reused across multiple spawns
- You control when to close the terminal
- The `exit` callback fires when you call `terminal.close()`, not when each subprocess exits
- Use `proc.exited` to detect individual subprocess exits
This is useful for running multiple commands in sequence through the same terminal session.
<Note>Terminal support is only available on POSIX systems (Linux, macOS). It is not available on Windows.</Note>
---
## Blocking API (`Bun.spawnSync()`)
Bun provides a synchronous equivalent of `Bun.spawn` called `Bun.spawnSync`. This is a blocking API that supports the same inputs and parameters as `Bun.spawn`. It returns a `SyncSubprocess` object, which differs from `Subprocess` in a few ways.
@@ -407,6 +510,7 @@ namespace SpawnOptions {
timeout?: number;
killSignal?: string | number;
maxBuffer?: number;
terminal?: TerminalOptions; // PTY support (POSIX only)
}
type Readable =
@@ -435,10 +539,11 @@ namespace SpawnOptions {
}
interface Subprocess extends AsyncDisposable {
readonly stdin: FileSink | number | undefined;
readonly stdout: ReadableStream<Uint8Array> | number | undefined;
readonly stderr: ReadableStream<Uint8Array> | number | undefined;
readonly readable: ReadableStream<Uint8Array> | number | undefined;
readonly stdin: FileSink | number | undefined | null;
readonly stdout: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
readonly stderr: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
readonly readable: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
readonly terminal: Terminal | undefined;
readonly pid: number;
readonly exited: Promise<number>;
readonly exitCode: number | null;
@@ -465,6 +570,28 @@ interface SyncSubprocess {
pid: number;
}
interface TerminalOptions {
cols?: number;
rows?: number;
name?: string;
data?: (terminal: Terminal, data: Uint8Array<ArrayBuffer>) => void;
/** Called when PTY stream closes (EOF or error). exitCode is PTY lifecycle status (0=EOF, 1=error), not subprocess exit code. */
exit?: (terminal: Terminal, exitCode: number, signal: string | null) => void;
drain?: (terminal: Terminal) => void;
}
interface Terminal extends AsyncDisposable {
readonly stdin: number;
readonly stdout: number;
readonly closed: boolean;
write(data: string | BufferSource): number;
resize(cols: number, rows: number): void;
setRawMode(enabled: boolean): void;
ref(): void;
unref(): void;
close(): void;
}
interface ResourceUsage {
contextSwitches: {
voluntary: number;

View File

@@ -193,15 +193,17 @@ This is the maximum amount of time a connection is allowed to be idle before the
Thus far, the examples on this page have used the explicit `Bun.serve` API. Bun also supports an alternate syntax.
```ts server.ts
import { type Serve } from "bun";
import type { Serve } from "bun";
export default {
fetch(req) {
return new Response("Bun!");
},
} satisfies Serve;
} satisfies Serve.Options<undefined>;
```
The type parameter `<undefined>` represents WebSocket data — if you add a `websocket` handler with custom data attached via `server.upgrade(req, { data: ... })`, replace `undefined` with your data type.
Instead of passing the server options into `Bun.serve`, `export default` it. This file can be executed as-is; when Bun sees a file with a `default` export containing a `fetch` handler, it passes it into `Bun.serve` under the hood.
---

View File

@@ -51,7 +51,7 @@ const response = await fetch("http://example.com", {
### Proxying requests
To proxy a request, pass an object with the `proxy` property set to a URL.
To proxy a request, pass an object with the `proxy` property set to a URL string:
```ts
const response = await fetch("http://example.com", {
@@ -59,6 +59,22 @@ const response = await fetch("http://example.com", {
});
```
You can also use an object format to send custom headers to the proxy server:
```ts
const response = await fetch("http://example.com", {
proxy: {
url: "http://proxy.com",
headers: {
"Proxy-Authorization": "Bearer my-token",
"X-Custom-Proxy-Header": "value",
},
},
});
```
The `headers` are sent directly to the proxy in `CONNECT` requests (for HTTPS targets) or in the proxy request (for HTTP targets). If you provide a `Proxy-Authorization` header, it overrides any credentials in the proxy URL.
### Custom headers
To set custom headers, pass an object with the `headers` property set to an object.

View File

@@ -23,10 +23,11 @@ if (!githubToken) {
name: "github-token",
value: githubToken,
});
console.log("GitHub token stored");
}
const response = await fetch("https://api.github.com/name", {
const response = await fetch("https://api.github.com/user", {
headers: { Authorization: `token ${githubToken}` },
});

View File

@@ -172,12 +172,25 @@ const query = db.query(`select "Hello world" as message`);
```
<Note>
Use the `.prepare()` method to prepare a query _without_ caching it on the `Database` instance.
**What does "cached" mean?**
```ts
// compile the prepared statement
const query = db.prepare("SELECT * FROM foo WHERE bar = ?");
```
The caching refers to the **compiled prepared statement** (the SQL bytecode), not the query results. When you call `db.query()` with the same SQL string multiple times, Bun returns the same cached `Statement` object instead of recompiling the SQL.
It is completely safe to reuse a cached statement with different parameter values:
```ts
const query = db.query("SELECT * FROM users WHERE id = ?");
query.get(1); // ✓ Works
query.get(2); // ✓ Also works - parameters are bound fresh each time
query.get(3); // ✓ Still works
```
Use `.prepare()` instead of `.query()` when you want a fresh `Statement` instance that isn't cached, for example if you're dynamically generating SQL and don't want to fill the cache with one-off queries.
```ts
// compile the prepared statement without caching
const query = db.prepare("SELECT * FROM foo WHERE bar = ?");
```
</Note>
@@ -190,7 +203,7 @@ SQLite supports [write-ahead log mode](https://www.sqlite.org/wal.html) (WAL) wh
To enable WAL mode, run this pragma query at the beginning of your application:
```ts db.ts icon="/icons/typescript.svg"
db.exec("PRAGMA journal_mode = WAL;");
db.run("PRAGMA journal_mode = WAL;");
```
<Accordion title="What is WAL mode?">
@@ -290,7 +303,7 @@ Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sq
### `.run()`
Use `.run()` to run a query and get back `undefined`. This is useful for schema-modifying queries (e.g. `CREATE TABLE`) or bulk write operations.
Use `.run()` to run a query and get back an object with execution metadata. This is useful for schema-modifying queries (e.g. `CREATE TABLE`) or bulk write operations.
```ts db.ts icon="/icons/typescript.svg" highlight={2}
const query = db.query(`create table foo;`);
@@ -650,8 +663,8 @@ class Database {
},
);
prepare<ReturnType, Params>(sql: string): Statement<ReturnType, Params>;
query<ReturnType, Params>(sql: string): Statement<ReturnType, Params>;
query<ReturnType, ParamsType>(sql: string): Statement<ReturnType, ParamsType>;
prepare<ReturnType, ParamsType>(sql: string): Statement<ReturnType, ParamsType>;
run(sql: string, params?: SQLQueryBindings): { lastInsertRowid: number; changes: number };
exec = this.run;
@@ -664,14 +677,14 @@ class Database {
close(throwOnError?: boolean): void;
}
class Statement<ReturnType, Params> {
all(params: Params): ReturnType[];
get(params: Params): ReturnType | undefined;
run(params: Params): {
class Statement<ReturnType, ParamsType> {
all(...params: ParamsType[]): ReturnType[];
get(...params: ParamsType[]): ReturnType | null;
run(...params: ParamsType[]): {
lastInsertRowid: number;
changes: number;
};
values(params: Params): unknown[][];
values(...params: ParamsType[]): unknown[][];
finalize(): void; // destroy statement and clean up resources
toString(): string; // serialize to SQL
@@ -682,7 +695,7 @@ class Statement<ReturnType, Params> {
paramsCount: number; // the number of parameters expected by the statement
native: any; // the native object representing the statement
as(Class: new () => ReturnType): this;
as<T>(Class: new (...args: any[]) => T): Statement<T, ParamsType>;
}
type SQLQueryBindings =

View File

@@ -0,0 +1,49 @@
## Usage
```bash
bunx [flags] <package>[@version] [flags and arguments for the package]
```
Execute an npm package executable (CLI), automatically installing into a global shared cache if not installed in `node_modules`.
### Flags
<ParamField path="--bun" type="boolean">
Force the command to run with Bun instead of Node.js, even if the executable contains a Node shebang (`#!/usr/bin/env
node`)
</ParamField>
<ParamField path="-p, --package" type="string">
Specify package to install when binary name differs from package name
</ParamField>
<ParamField path="--no-install" type="boolean">
Skip installation if package is not already installed
</ParamField>
<ParamField path="--verbose" type="boolean">
Enable verbose output during installation
</ParamField>
<ParamField path="--silent" type="boolean">
Suppress output during installation
</ParamField>
### Examples
```bash terminal icon="terminal"
# Run Prisma migrations
bunx prisma migrate
# Format a file with Prettier
bunx prettier foo.js
# Run a specific version of a package
bunx uglify-js@3.14.0 app.js
# Use --package when binary name differs from package name
bunx -p @angular/cli ng new my-app
# Force running with Bun instead of Node.js, even if the executable contains a Node shebang
bunx --bun vite dev foo.js
```

View File

@@ -376,16 +376,18 @@ timeout = 10000
## Environment Variables
You can also set environment variables in your configuration that affect test behavior:
Environment variables for tests should be set using `.env` files. Bun automatically loads `.env` files from your project root. For test-specific variables, create a `.env.test` file:
```toml title="bunfig.toml" icon="settings"
[env]
NODE_ENV = "test"
DATABASE_URL = "postgresql://localhost:5432/test_db"
LOG_LEVEL = "error"
```ini title=".env.test" icon="settings"
NODE_ENV=test
DATABASE_URL=postgresql://localhost:5432/test_db
LOG_LEVEL=error
```
[test]
coverage = true
Then load it with `--env-file`:
```bash terminal icon="terminal"
bun test --env-file=.env.test
```
## Complete Configuration Example
@@ -398,13 +400,6 @@ Here's a comprehensive example showing all available test configuration options:
registry = "https://registry.npmjs.org/"
exact = true
[env]
# Environment variables for tests
NODE_ENV = "test"
DATABASE_URL = "postgresql://localhost:5432/test_db"
API_URL = "http://localhost:3001"
LOG_LEVEL = "error"
[test]
# Test discovery
root = "src"

View File

@@ -428,26 +428,26 @@ test("foo, bar, baz", () => {
const barSpy = spyOn(barModule, "bar");
const bazSpy = spyOn(bazModule, "baz");
// Original values
expect(fooSpy).toBe("foo");
expect(barSpy).toBe("bar");
expect(bazSpy).toBe("baz");
// Original implementations still work
expect(fooModule.foo()).toBe("foo");
expect(barModule.bar()).toBe("bar");
expect(bazModule.baz()).toBe("baz");
// Mock implementations
fooSpy.mockImplementation(() => 42);
barSpy.mockImplementation(() => 43);
bazSpy.mockImplementation(() => 44);
expect(fooSpy()).toBe(42);
expect(barSpy()).toBe(43);
expect(bazSpy()).toBe(44);
expect(fooModule.foo()).toBe(42);
expect(barModule.bar()).toBe(43);
expect(bazModule.baz()).toBe(44);
// Restore all
mock.restore();
expect(fooSpy()).toBe("foo");
expect(barSpy()).toBe("bar");
expect(bazSpy()).toBe("baz");
expect(fooModule.foo()).toBe("foo");
expect(barModule.bar()).toBe("bar");
expect(bazModule.baz()).toBe("baz");
});
```
@@ -455,10 +455,10 @@ Using `mock.restore()` can reduce the amount of code in your tests by adding it
## Vitest Compatibility
For added compatibility with tests written for Vitest, Bun provides the `vi` global object as an alias for parts of the Jest mocking API:
For added compatibility with tests written for Vitest, Bun provides the `vi` object as an alias for parts of the Jest mocking API:
```ts title="test.ts" icon="/icons/typescript.svg"
import { test, expect } from "bun:test";
import { test, expect, vi } from "bun:test";
// Using the 'vi' alias similar to Vitest
test("vitest compatibility", () => {

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "bun",
"version": "1.3.4",
"version": "1.3.6",
"workspaces": [
"./packages/bun-types",
"./packages/@types/bun"
@@ -23,7 +23,8 @@
},
"resolutions": {
"bun-types": "workspace:packages/bun-types",
"@types/bun": "workspace:packages/@types/bun"
"@types/bun": "workspace:packages/@types/bun",
"@types/node": "25.0.0"
},
"scripts": {
"build": "bun --silent run build:debug",
@@ -87,7 +88,7 @@
"node:test:cp": "bun ./scripts/fetch-node-test.ts ",
"clean:zig": "rm -rf build/debug/cache/zig build/debug/CMakeCache.txt 'build/debug/*.o' .zig-cache zig-out || true",
"machine:linux:ubuntu": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=linux --distro=ubuntu --release=25.04",
"machine:linux:debian": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=linux --distro=debian --release=12",
"machine:linux:debian": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=linux --distro=debian --release=13",
"machine:linux:alpine": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=linux --distro=alpine --release=3.22",
"machine:linux:amazonlinux": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=linux --distro=amazonlinux --release=2023",
"machine:windows:2019": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=windows --release=2019",

View File

@@ -615,7 +615,6 @@ const NativeStackFrame = ({
<div
title={StackFrameScope[scope]}
className="BunError-StackFrame-identifier"
// @ts-expect-error Custom CSS variables are not known by TypeScript
style={{ "--max-length": `${maxLength}ch` }}
>
{getNativeStackFrameIdentifier(frame)}

View File

@@ -1740,7 +1740,6 @@ declare module "bun" {
* @default "esm"
*/
format?: /**
* ECMAScript Module format
*/
| "esm"
@@ -1892,6 +1891,24 @@ declare module "bun" {
*/
drop?: string[];
/**
* Enable feature flags for dead-code elimination via `import { feature } from "bun:bundle"`.
*
* When `feature("FLAG_NAME")` is called, it returns `true` if FLAG_NAME is in this array,
* or `false` otherwise. This enables static dead-code elimination at bundle time.
*
* Equivalent to the CLI `--feature` flag.
*
* @example
* ```ts
* await Bun.build({
* entrypoints: ['./src/index.ts'],
* features: ['FEATURE_A', 'FEATURE_B'],
* });
* ```
*/
features?: string[];
/**
* - When set to `true`, the returned promise rejects with an AggregateError when a build failure happens.
* - When set to `false`, returns a {@link BuildOutput} with `{success: false}`
@@ -1953,6 +1970,26 @@ declare module "bun" {
* @default true
*/
autoloadBunfig?: boolean;
/**
* Whether to autoload tsconfig.json when the standalone executable runs
*
* Standalone-only: applies only when building/running the standalone executable.
*
* Equivalent CLI flags: `--compile-autoload-tsconfig`, `--no-compile-autoload-tsconfig`
*
* @default false
*/
autoloadTsconfig?: boolean;
/**
* Whether to autoload package.json when the standalone executable runs
*
* Standalone-only: applies only when building/running the standalone executable.
*
* Equivalent CLI flags: `--compile-autoload-package-json`, `--no-compile-autoload-package-json`
*
* @default false
*/
autoloadPackageJson?: boolean;
windows?: {
hideConsole?: boolean;
icon?: string;
@@ -3888,6 +3925,9 @@ declare module "bun" {
static readonly byteLength: 32;
}
/** Extends the standard web formats with `brotli` and `zstd` support. */
type CompressionFormat = "gzip" | "deflate" | "deflate-raw" | "brotli" | "zstd";
/** Compression options for `Bun.deflateSync` and `Bun.gzipSync` */
interface ZlibCompressionOptions {
/**
@@ -5677,6 +5717,44 @@ declare module "bun" {
* ```
*/
lazy?: boolean;
/**
* Spawn the subprocess with a pseudo-terminal (PTY) attached.
*
* When this option is provided:
* - `stdin`, `stdout`, and `stderr` are all connected to the terminal
* - The subprocess sees itself running in a real terminal (`isTTY = true`)
* - Access the terminal via `subprocess.terminal`
* - `subprocess.stdin`, `subprocess.stdout`, `subprocess.stderr` return `null`
*
* Only available on POSIX systems (Linux, macOS).
*
* @example
* ```ts
* const proc = Bun.spawn(["bash"], {
* terminal: {
* cols: 80,
* rows: 24,
* data: (term, data) => console.log(data.toString()),
* },
* });
*
* proc.terminal.write("echo hello\n");
* await proc.exited;
* proc.terminal.close();
* ```
*
* You can also pass an existing `Terminal` object for reuse across multiple spawns:
* ```ts
* const terminal = new Bun.Terminal({ ... });
* const proc1 = Bun.spawn(["echo", "first"], { terminal });
* await proc1.exited;
* const proc2 = Bun.spawn(["echo", "second"], { terminal });
* await proc2.exited;
* terminal.close();
* ```
*/
terminal?: TerminalOptions | Terminal;
}
type ReadableToIO<X extends Readable> = X extends "pipe" | undefined
@@ -5791,6 +5869,24 @@ declare module "bun" {
readonly stdout: SpawnOptions.ReadableToIO<Out>;
readonly stderr: SpawnOptions.ReadableToIO<Err>;
/**
* The terminal attached to this subprocess, if spawned with the `terminal` option.
* Returns `undefined` if no terminal was attached.
*
* When a terminal is attached, `stdin`, `stdout`, and `stderr` return `null`.
* Use `terminal.write()` and the `data` callback instead.
*
* @example
* ```ts
* const proc = Bun.spawn(["bash"], {
* terminal: { data: (term, data) => console.log(data.toString()) },
* });
*
* proc.terminal?.write("echo hello\n");
* ```
*/
readonly terminal: Terminal | undefined;
/**
* Access extra file descriptors passed to the `stdio` option in the options object.
*/
@@ -6082,6 +6178,154 @@ declare module "bun" {
"ignore" | "inherit" | null | undefined
>;
/**
* Options for creating a pseudo-terminal (PTY).
*/
interface TerminalOptions {
/**
* Number of columns for the terminal.
* @default 80
*/
cols?: number;
/**
* Number of rows for the terminal.
* @default 24
*/
rows?: number;
/**
* Terminal name (e.g., "xterm-256color").
* @default "xterm-256color"
*/
name?: string;
/**
* Callback invoked when data is received from the terminal.
* @param terminal The terminal instance
* @param data The data received as a Uint8Array
*/
data?: (terminal: Terminal, data: Uint8Array<ArrayBuffer>) => void;
/**
* Callback invoked when the PTY stream closes (EOF or read error).
* Note: exitCode is a PTY lifecycle status (0=clean EOF, 1=error), NOT the subprocess exit code.
* Use Subprocess.exited or onExit callback for actual process exit information.
* @param terminal The terminal instance
* @param exitCode PTY lifecycle status (0 for EOF, 1 for error)
* @param signal Reserved for future signal reporting, currently null
*/
exit?: (terminal: Terminal, exitCode: number, signal: string | null) => void;
/**
* Callback invoked when the terminal is ready to receive more data.
* @param terminal The terminal instance
*/
drain?: (terminal: Terminal) => void;
}
/**
* A pseudo-terminal (PTY) that can be used to spawn interactive terminal programs.
*
* @example
* ```ts
* await using terminal = new Bun.Terminal({
* cols: 80,
* rows: 24,
* data(term, data) {
* console.log("Received:", new TextDecoder().decode(data));
* },
* });
*
* // Spawn a shell connected to the PTY
* const proc = Bun.spawn(["bash"], { terminal });
*
* // Write to the terminal
* terminal.write("echo hello\n");
*
* // Wait for process to exit
* await proc.exited;
*
* // Terminal is closed automatically by `await using`
* ```
*/
class Terminal implements AsyncDisposable {
constructor(options: TerminalOptions);
/**
* Whether the terminal is closed.
*/
readonly closed: boolean;
/**
* Write data to the terminal.
* @param data The data to write (string or BufferSource)
* @returns The number of bytes written
*/
write(data: string | BufferSource): number;
/**
* Resize the terminal.
* @param cols New number of columns
* @param rows New number of rows
*/
resize(cols: number, rows: number): void;
/**
* Set raw mode on the terminal.
* In raw mode, input is passed directly without processing.
* @param enabled Whether to enable raw mode
*/
setRawMode(enabled: boolean): void;
/**
* Reference the terminal to keep the event loop alive.
*/
ref(): void;
/**
* Unreference the terminal to allow the event loop to exit.
*/
unref(): void;
/**
* Close the terminal.
*/
close(): void;
/**
* Async dispose for use with `await using`.
*/
[Symbol.asyncDispose](): Promise<void>;
/**
* Terminal input flags (c_iflag from termios).
* Controls input processing behavior like ICRNL, IXON, etc.
* Returns 0 if terminal is closed.
* Setting returns true on success, false on failure.
*/
inputFlags: number;
/**
* Terminal output flags (c_oflag from termios).
* Controls output processing behavior like OPOST, ONLCR, etc.
* Returns 0 if terminal is closed.
* Setting returns true on success, false on failure.
*/
outputFlags: number;
/**
* Terminal local flags (c_lflag from termios).
* Controls local processing like ICANON, ECHO, ISIG, etc.
* Returns 0 if terminal is closed.
* Setting returns true on success, false on failure.
*/
localFlags: number;
/**
* Terminal control flags (c_cflag from termios).
* Controls hardware characteristics like CSIZE, PARENB, etc.
* Returns 0 if terminal is closed.
* Setting returns true on success, false on failure.
*/
controlFlags: number;
}
// Blocked on https://github.com/oven-sh/bun/issues/8329
// /**
// *

74
packages/bun-types/bundle.d.ts vendored Normal file
View File

@@ -0,0 +1,74 @@
/**
* The `bun:bundle` module provides compile-time utilities for dead-code elimination.
*
* @example
* ```ts
* import { feature } from "bun:bundle";
*
* if (feature("SUPER_SECRET")) {
* console.log("Secret feature enabled!");
* } else {
* console.log("Normal mode");
* }
* ```
*
* Enable feature flags via CLI:
* ```bash
* # During build
* bun build --feature=SUPER_SECRET index.ts
*
* # At runtime
* bun run --feature=SUPER_SECRET index.ts
*
* # In tests
* bun test --feature=SUPER_SECRET
* ```
*
* @module bun:bundle
*/
declare module "bun:bundle" {
/**
* Registry for type-safe feature flags.
*
* Augment this interface to get autocomplete and type checking for your feature flags:
*
* @example
* ```ts
* // env.d.ts
* declare module "bun:bundle" {
* interface Registry {
* features: "DEBUG" | "PREMIUM" | "BETA";
* }
* }
* ```
*
* Now `feature()` only accepts `"DEBUG"`, `"PREMIUM"`, or `"BETA"`:
* ```ts
* feature("DEBUG"); // OK
* feature("TYPO"); // Type error
* ```
*/
interface Registry {}
/**
* Check if a feature flag is enabled at compile time.
*
* This function is replaced with a boolean literal (`true` or `false`) at bundle time,
* enabling dead-code elimination. The bundler will remove unreachable branches.
*
* @param flag - The name of the feature flag to check
* @returns `true` if the flag was passed via `--feature=FLAG`, `false` otherwise
*
* @example
* ```ts
* import { feature } from "bun:bundle";
*
* // With --feature=DEBUG, this becomes: if (true) { ... }
* // Without --feature=DEBUG, this becomes: if (false) { ... }
* if (feature("DEBUG")) {
* console.log("Debug mode enabled");
* }
* ```
*/
function feature(flag: Registry extends { features: infer Features extends string } ? Features : string): boolean;
}

View File

@@ -1,9 +1,12 @@
declare module "bun" {
namespace __internal {
type NodeCryptoWebcryptoSubtleCrypto = import("crypto").webcrypto.SubtleCrypto;
type NodeCryptoWebcryptoCryptoKey = import("crypto").webcrypto.CryptoKey;
type NodeCryptoWebcryptoCryptoKeyPair = import("crypto").webcrypto.CryptoKeyPair;
type LibEmptyOrNodeCryptoWebcryptoSubtleCrypto = LibDomIsLoaded extends true
? {}
: import("crypto").webcrypto.SubtleCrypto;
type LibWorkerOrBunWorker = LibDomIsLoaded extends true ? {} : Bun.Worker;
type LibEmptyOrBunWebSocket = LibDomIsLoaded extends true ? {} : Bun.WebSocket;
@@ -14,7 +17,9 @@ declare module "bun" {
? {}
: import("node:stream/web").DecompressionStream;
type LibPerformanceOrNodePerfHooksPerformance = LibDomIsLoaded extends true ? {} : import("perf_hooks").Performance;
type LibPerformanceOrNodePerfHooksPerformance = LibDomIsLoaded extends true
? {}
: import("node:perf_hooks").Performance;
type LibEmptyOrPerformanceEntry = LibDomIsLoaded extends true ? {} : import("node:perf_hooks").PerformanceEntry;
type LibEmptyOrPerformanceMark = LibDomIsLoaded extends true ? {} : import("node:perf_hooks").PerformanceMark;
type LibEmptyOrPerformanceMeasure = LibDomIsLoaded extends true ? {} : import("node:perf_hooks").PerformanceMeasure;
@@ -83,6 +88,24 @@ declare var WritableStream: Bun.__internal.UseLibDomIfAvailable<
}
>;
interface CompressionStream extends Bun.__internal.LibEmptyOrNodeStreamWebCompressionStream {}
declare var CompressionStream: Bun.__internal.UseLibDomIfAvailable<
"CompressionStream",
{
prototype: CompressionStream;
new (format: Bun.CompressionFormat): CompressionStream;
}
>;
interface DecompressionStream extends Bun.__internal.LibEmptyOrNodeStreamWebDecompressionStream {}
declare var DecompressionStream: Bun.__internal.UseLibDomIfAvailable<
"DecompressionStream",
{
prototype: DecompressionStream;
new (format: Bun.CompressionFormat): DecompressionStream;
}
>;
interface Worker extends Bun.__internal.LibWorkerOrBunWorker {}
declare var Worker: Bun.__internal.UseLibDomIfAvailable<
"Worker",
@@ -206,7 +229,7 @@ interface TextEncoder extends Bun.__internal.LibEmptyOrNodeUtilTextEncoder {
* @param src The text to encode.
* @param dest The array to hold the encode result.
*/
encodeInto(src?: string, dest?: Bun.BufferSource): import("util").EncodeIntoResult;
encodeInto(src?: string, dest?: Bun.BufferSource): import("node:util").TextEncoderEncodeIntoResult;
}
declare var TextEncoder: Bun.__internal.UseLibDomIfAvailable<
"TextEncoder",
@@ -278,30 +301,6 @@ declare var Event: {
new (type: string, eventInitDict?: Bun.EventInit): Event;
};
/**
* Unimplemented in Bun
*/
interface CompressionStream extends Bun.__internal.LibEmptyOrNodeStreamWebCompressionStream {}
/**
* Unimplemented in Bun
*/
declare var CompressionStream: Bun.__internal.UseLibDomIfAvailable<
"CompressionStream",
typeof import("node:stream/web").CompressionStream
>;
/**
* Unimplemented in Bun
*/
interface DecompressionStream extends Bun.__internal.LibEmptyOrNodeStreamWebCompressionStream {}
/**
* Unimplemented in Bun
*/
declare var DecompressionStream: Bun.__internal.UseLibDomIfAvailable<
"DecompressionStream",
typeof import("node:stream/web").DecompressionStream
>;
interface EventTarget {
/**
* Adds a new handler for the `type` event. Any given `listener` is added only once per `type` and per `capture` option value.
@@ -958,7 +957,7 @@ declare function alert(message?: string): void;
declare function confirm(message?: string): boolean;
declare function prompt(message?: string, _default?: string): string | null;
interface SubtleCrypto extends Bun.__internal.NodeCryptoWebcryptoSubtleCrypto {}
interface SubtleCrypto extends Bun.__internal.LibEmptyOrNodeCryptoWebcryptoSubtleCrypto {}
declare var SubtleCrypto: {
prototype: SubtleCrypto;
new (): SubtleCrypto;
@@ -1694,6 +1693,10 @@ declare var EventSource: Bun.__internal.UseLibDomIfAvailable<
interface Performance extends Bun.__internal.LibPerformanceOrNodePerfHooksPerformance {}
declare var performance: Bun.__internal.UseLibDomIfAvailable<"performance", Performance>;
declare var Performance: Bun.__internal.UseLibDomIfAvailable<
"Performance",
{ new (): Performance; prototype: Performance }
>;
interface PerformanceEntry extends Bun.__internal.LibEmptyOrPerformanceEntry {}
declare var PerformanceEntry: Bun.__internal.UseLibDomIfAvailable<
@@ -1920,14 +1923,44 @@ interface BunFetchRequestInit extends RequestInit {
* Override http_proxy or HTTPS_PROXY
* This is a custom property that is not part of the Fetch API specification.
*
* Can be a string URL or an object with `url` and optional `headers`.
*
* @example
* ```js
* // String format
* const response = await fetch("http://example.com", {
* proxy: "https://username:password@127.0.0.1:8080"
* });
*
* // Object format with custom headers sent to the proxy
* const response = await fetch("http://example.com", {
* proxy: {
* url: "https://127.0.0.1:8080",
* headers: {
* "Proxy-Authorization": "Bearer token",
* "X-Custom-Proxy-Header": "value"
* }
* }
* });
* ```
*
* If a `Proxy-Authorization` header is provided in `proxy.headers`, it takes
* precedence over credentials parsed from the proxy URL.
*/
proxy?: string;
proxy?:
| string
| {
/**
* The proxy URL
*/
url: string;
/**
* Custom headers to send to the proxy server.
* These headers are sent in the CONNECT request (for HTTPS targets)
* or in the proxy request (for HTTP targets).
*/
headers?: Bun.HeadersInit;
};
/**
* Override the default S3 options

View File

@@ -23,6 +23,7 @@
/// <reference path="./serve.d.ts" />
/// <reference path="./sql.d.ts" />
/// <reference path="./security.d.ts" />
/// <reference path="./bundle.d.ts" />
/// <reference path="./bun.ns.d.ts" />

View File

@@ -86,7 +86,7 @@ declare global {
reallyExit(code?: number): never;
dlopen(module: { exports: any }, filename: string, flags?: number): void;
_exiting: boolean;
noDeprecation: boolean;
noDeprecation?: boolean | undefined;
binding(m: "constants"): {
os: typeof import("node:os").constants;
@@ -308,11 +308,11 @@ declare global {
}
}
declare module "fs/promises" {
declare module "node:fs/promises" {
function exists(path: Bun.PathLike): Promise<boolean>;
}
declare module "tls" {
declare module "node:tls" {
interface BunConnectionOptions extends Omit<ConnectionOptions, "key" | "ca" | "tls" | "cert"> {
/**
* Optionally override the trusted CA certificates. Default is to trust
@@ -359,3 +359,18 @@ declare module "tls" {
function connect(options: BunConnectionOptions, secureConnectListener?: () => void): TLSSocket;
}
declare module "console" {
interface Console {
/**
* Asynchronously read lines from standard input (fd 0)
*
* ```ts
* for await (const line of console) {
* console.log(line);
* }
* ```
*/
[Symbol.asyncIterator](): AsyncIterableIterator<string>;
}
}

View File

@@ -281,6 +281,24 @@ declare module "bun" {
*/
type?: string;
/**
* The Content-Disposition header value.
* Controls how the file is presented when downloaded.
*
* @example
* // Setting attachment disposition with filename
* const file = s3.file("report.pdf", {
* contentDisposition: "attachment; filename=\"quarterly-report.pdf\""
* });
*
* @example
* // Setting inline disposition
* await s3.write("image.png", imageData, {
* contentDisposition: "inline"
* });
*/
contentDisposition?: string | undefined;
/**
* By default, Amazon S3 uses the STANDARD Storage Class to store newly created objects.
*

View File

@@ -446,7 +446,7 @@ declare module "bun" {
closeOnBackpressureLimit?: boolean;
/**
* Sets the the number of seconds to wait before timing out a connection
* Sets the number of seconds to wait before timing out a connection
* due to no messages or pings.
*
* @default 120
@@ -758,7 +758,7 @@ declare module "bun" {
ipv6Only?: boolean;
/**
* Sets the the number of seconds to wait before timing out a connection
* Sets the number of seconds to wait before timing out a connection
* due to inactivity.
*
* @default 10
@@ -1082,6 +1082,15 @@ declare module "bun" {
*/
readonly hostname: string | undefined;
/**
* The protocol the server is listening on.
*
* - "http" for normal servers
* - "https" when TLS is enabled
* - null for unix sockets or when unavailable
*/
readonly protocol: "http" | "https" | null;
/**
* Is the server running in development mode?
*

View File

@@ -154,12 +154,6 @@ declare module "bun:sqlite" {
* | `bigint` | `INTEGER` |
* | `null` | `NULL` |
*
* @example
* ```ts
* db.run("CREATE TABLE foo (bar TEXT)");
* db.run("INSERT INTO foo VALUES (?)", ["baz"]);
* ```
*
* Useful for queries like:
* - `CREATE TABLE`
* - `INSERT INTO`
@@ -180,8 +174,14 @@ declare module "bun:sqlite" {
*
* @param sql The SQL query to run
* @param bindings Optional bindings for the query
* @returns A `Changes` object with `changes` and `lastInsertRowid` properties
*
* @returns `Database` instance
* @example
* ```ts
* db.run("CREATE TABLE foo (bar TEXT)");
* db.run("INSERT INTO foo VALUES (?)", ["baz"]);
* // => { changes: 1, lastInsertRowid: 1 }
* ```
*/
run<ParamsType extends SQLQueryBindings[]>(sql: string, ...bindings: ParamsType[]): Changes;
@@ -670,18 +670,19 @@ declare module "bun:sqlite" {
* Execute the prepared statement.
*
* @param params optional values to bind to the statement. If omitted, the statement is run with the last bound values or no parameters if there are none.
* @returns A `Changes` object with `changes` and `lastInsertRowid` properties
*
* @example
* ```ts
* const stmt = db.prepare("UPDATE foo SET bar = ?");
* stmt.run("baz");
* // => undefined
* const insert = db.prepare("INSERT INTO users (name) VALUES (?)");
* insert.run("Alice");
* // => { changes: 1, lastInsertRowid: 1 }
* insert.run("Bob");
* // => { changes: 1, lastInsertRowid: 2 }
*
* stmt.run();
* // => undefined
*
* stmt.run("foo");
* // => undefined
* const update = db.prepare("UPDATE users SET name = ? WHERE id = ?");
* update.run("Charlie", 1);
* // => { changes: 1, lastInsertRowid: 2 }
* ```
*
* The following types can be used when binding parameters:

View File

@@ -95,8 +95,15 @@ declare module "bun:test" {
function fn<T extends (...args: any[]) => any>(func?: T): Mock<T>;
function setSystemTime(now?: number | Date): void;
function setTimeout(milliseconds: number): void;
function useFakeTimers(): void;
function useRealTimers(): void;
function useFakeTimers(options?: { now?: number | Date }): typeof vi;
function useRealTimers(): typeof vi;
function advanceTimersByTime(milliseconds: number): typeof vi;
function advanceTimersToNextTimer(): typeof vi;
function runAllTimers(): typeof vi;
function runOnlyPendingTimers(): typeof vi;
function getTimerCount(): number;
function clearAllTimers(): void;
function isFakeTimers(): boolean;
function spyOn<T extends object, K extends keyof T>(
obj: T,
methodOrPropertyValue: K,
@@ -184,6 +191,13 @@ declare module "bun:test" {
resetAllMocks: typeof jest.resetAllMocks;
useFakeTimers: typeof jest.useFakeTimers;
useRealTimers: typeof jest.useRealTimers;
advanceTimersByTime: typeof jest.advanceTimersByTime;
advanceTimersToNextTimer: typeof jest.advanceTimersToNextTimer;
runAllTimers: typeof jest.runAllTimers;
runOnlyPendingTimers: typeof jest.runOnlyPendingTimers;
getTimerCount: typeof jest.getTimerCount;
clearAllTimers: typeof jest.clearAllTimers;
isFakeTimers: typeof jest.isFakeTimers;
};
interface FunctionLike {
@@ -414,6 +428,8 @@ declare module "bun:test" {
}
namespace __internal {
type IfNeverThenElse<T, Else> = [T] extends [never] ? Else : T;
type IsTuple<T> = T extends readonly unknown[]
? number extends T["length"]
? false // It's an array with unknown length, not a tuple
@@ -1083,8 +1099,8 @@ declare module "bun:test" {
*
* @param expected the expected value
*/
toContainKey(expected: keyof T): void;
toContainKey<X = T>(expected: NoInfer<keyof X>): void;
toContainKey(expected: __internal.IfNeverThenElse<keyof T, PropertyKey>): void;
toContainKey<X = T>(expected: __internal.IfNeverThenElse<NoInfer<keyof X>, PropertyKey>): void;
/**
* Asserts that an `object` contains all the provided keys.
@@ -1100,8 +1116,8 @@ declare module "bun:test" {
*
* @param expected the expected value
*/
toContainAllKeys(expected: Array<keyof T>): void;
toContainAllKeys<X = T>(expected: NoInfer<Array<keyof X>>): void;
toContainAllKeys(expected: Array<__internal.IfNeverThenElse<keyof T, PropertyKey>>): void;
toContainAllKeys<X = T>(expected: Array<__internal.IfNeverThenElse<NoInfer<keyof X>, PropertyKey>>): void;
/**
* Asserts that an `object` contains at least one of the provided keys.
@@ -1117,8 +1133,8 @@ declare module "bun:test" {
*
* @param expected the expected value
*/
toContainAnyKeys(expected: Array<keyof T>): void;
toContainAnyKeys<X = T>(expected: NoInfer<Array<keyof X>>): void;
toContainAnyKeys(expected: Array<__internal.IfNeverThenElse<keyof T, PropertyKey>>): void;
toContainAnyKeys<X = T>(expected: Array<__internal.IfNeverThenElse<NoInfer<keyof X>, PropertyKey>>): void;
/**
* Asserts that an `object` contain the provided value.
@@ -1210,8 +1226,8 @@ declare module "bun:test" {
*
* @param expected the expected value
*/
toContainKeys(expected: Array<keyof T>): void;
toContainKeys<X = T>(expected: NoInfer<Array<keyof X>>): void;
toContainKeys(expected: Array<__internal.IfNeverThenElse<keyof T, PropertyKey>>): void;
toContainKeys<X = T>(expected: Array<__internal.IfNeverThenElse<NoInfer<keyof X>, PropertyKey>>): void;
/**
* Asserts that a value contains and equals what is expected.

View File

@@ -100,8 +100,8 @@ declare module "bun" {
declare namespace WebAssembly {
interface ValueTypeMap extends Bun.WebAssembly.ValueTypeMap {}
interface GlobalDescriptor<T extends keyof ValueTypeMap = keyof ValueTypeMap>
extends Bun.WebAssembly.GlobalDescriptor<T> {}
interface GlobalDescriptor<T extends keyof ValueTypeMap = keyof ValueTypeMap> extends Bun.WebAssembly
.GlobalDescriptor<T> {}
interface MemoryDescriptor extends Bun.WebAssembly.MemoryDescriptor {}
interface ModuleExportDescriptor extends Bun.WebAssembly.ModuleExportDescriptor {}
interface ModuleImportDescriptor extends Bun.WebAssembly.ModuleImportDescriptor {}

View File

@@ -54,8 +54,8 @@ void us_listen_socket_close(int ssl, struct us_listen_socket_t *ls) {
s->next = loop->data.closed_head;
loop->data.closed_head = s;
/* Any socket with prev = context is marked as closed */
s->prev = (struct us_socket_t *) context;
/* Mark the socket as closed */
s->flags.is_closed = 1;
}
/* We cannot immediately free a listen socket as we can be inside an accept loop */
@@ -154,7 +154,9 @@ void us_internal_socket_context_unlink_connecting_socket(int ssl, struct us_sock
/* We always add in the top, so we don't modify any s.next */
void us_internal_socket_context_link_listen_socket(int ssl, struct us_socket_context_t *context, struct us_listen_socket_t *ls) {
struct us_socket_t* s = &ls->s;
if(us_socket_is_closed(ssl, s)) return;
s->context = context;
s->next = (struct us_socket_t *) context->head_listen_sockets;
s->prev = 0;
@@ -166,6 +168,8 @@ void us_internal_socket_context_link_listen_socket(int ssl, struct us_socket_con
}
void us_internal_socket_context_link_connecting_socket(int ssl, struct us_socket_context_t *context, struct us_connecting_socket_t *c) {
if(c->closed) return;
c->context = context;
c->next_pending = context->head_connecting_sockets;
c->prev_pending = 0;
@@ -180,6 +184,8 @@ void us_internal_socket_context_link_connecting_socket(int ssl, struct us_socket
/* We always add in the top, so we don't modify any s.next */
void us_internal_socket_context_link_socket(int ssl, struct us_socket_context_t *context, struct us_socket_t *s) {
if(us_socket_is_closed(ssl,s)) return;
s->context = context;
s->next = context->head_sockets;
s->prev = 0;
@@ -386,6 +392,9 @@ struct us_listen_socket_t *us_socket_context_listen(int ssl, struct us_socket_co
s->flags.low_prio_state = 0;
s->flags.is_paused = 0;
s->flags.is_ipc = 0;
s->flags.is_closed = 0;
s->flags.adopted = 0;
s->flags.is_tls = ssl;
s->next = 0;
s->flags.allow_half_open = (options & LIBUS_SOCKET_ALLOW_HALF_OPEN);
us_internal_socket_context_link_listen_socket(ssl, context, ls);
@@ -422,6 +431,9 @@ struct us_listen_socket_t *us_socket_context_listen_unix(int ssl, struct us_sock
s->flags.allow_half_open = (options & LIBUS_SOCKET_ALLOW_HALF_OPEN);
s->flags.is_paused = 0;
s->flags.is_ipc = 0;
s->flags.is_closed = 0;
s->flags.adopted = 0;
s->flags.is_tls = ssl;
s->next = 0;
us_internal_socket_context_link_listen_socket(ssl, context, ls);
@@ -430,7 +442,7 @@ struct us_listen_socket_t *us_socket_context_listen_unix(int ssl, struct us_sock
return ls;
}
struct us_socket_t* us_socket_context_connect_resolved_dns(struct us_socket_context_t *context, struct sockaddr_storage* addr, int options, int socket_ext_size) {
struct us_socket_t* us_socket_context_connect_resolved_dns(int ssl, struct us_socket_context_t *context, struct sockaddr_storage* addr, int options, int socket_ext_size) {
LIBUS_SOCKET_DESCRIPTOR connect_socket_fd = bsd_create_connect_socket(addr, options);
if (connect_socket_fd == LIBUS_SOCKET_ERROR) {
return NULL;
@@ -453,6 +465,9 @@ struct us_socket_t* us_socket_context_connect_resolved_dns(struct us_socket_cont
socket->flags.allow_half_open = (options & LIBUS_SOCKET_ALLOW_HALF_OPEN);
socket->flags.is_paused = 0;
socket->flags.is_ipc = 0;
socket->flags.is_closed = 0;
socket->flags.adopted = 0;
socket->flags.is_tls = ssl;
socket->connect_state = NULL;
socket->connect_next = NULL;
@@ -514,7 +529,7 @@ void *us_socket_context_connect(int ssl, struct us_socket_context_t *context, co
struct sockaddr_storage addr;
if (try_parse_ip(host, port, &addr)) {
*has_dns_resolved = 1;
return us_socket_context_connect_resolved_dns(context, &addr, options, socket_ext_size);
return us_socket_context_connect_resolved_dns(ssl, context, &addr, options, socket_ext_size);
}
struct addrinfo_request* ai_req;
@@ -534,7 +549,7 @@ void *us_socket_context_connect(int ssl, struct us_socket_context_t *context, co
struct sockaddr_storage addr;
init_addr_with_port(&entries->info, port, &addr);
*has_dns_resolved = 1;
struct us_socket_t *s = us_socket_context_connect_resolved_dns(context, &addr, options, socket_ext_size);
struct us_socket_t *s = us_socket_context_connect_resolved_dns(ssl, context, &addr, options, socket_ext_size);
Bun__addrinfo_freeRequest(ai_req, s == NULL);
return s;
}
@@ -583,6 +598,9 @@ int start_connections(struct us_connecting_socket_t *c, int count) {
flags->allow_half_open = (c->options & LIBUS_SOCKET_ALLOW_HALF_OPEN);
flags->is_paused = 0;
flags->is_ipc = 0;
flags->is_closed = 0;
flags->adopted = 0;
flags->is_tls = c->ssl;
/* Link it into context so that timeout fires properly */
us_internal_socket_context_link_socket(0, context, s);
@@ -760,6 +778,9 @@ struct us_socket_t *us_socket_context_connect_unix(int ssl, struct us_socket_con
connect_socket->flags.allow_half_open = (options & LIBUS_SOCKET_ALLOW_HALF_OPEN);
connect_socket->flags.is_paused = 0;
connect_socket->flags.is_ipc = 0;
connect_socket->flags.is_closed = 0;
connect_socket->flags.adopted = 0;
connect_socket->flags.is_tls = ssl;
connect_socket->connect_state = NULL;
connect_socket->connect_next = NULL;
us_internal_socket_context_link_socket(ssl, context, connect_socket);
@@ -780,10 +801,10 @@ struct us_socket_context_t *us_create_child_socket_context(int ssl, struct us_so
}
/* Note: This will set timeout to 0 */
struct us_socket_t *us_socket_context_adopt_socket(int ssl, struct us_socket_context_t *context, struct us_socket_t *s, int ext_size) {
struct us_socket_t *us_socket_context_adopt_socket(int ssl, struct us_socket_context_t *context, struct us_socket_t *s, int old_ext_size, int ext_size) {
#ifndef LIBUS_NO_SSL
if (ssl) {
return (struct us_socket_t *) us_internal_ssl_socket_context_adopt_socket((struct us_internal_ssl_socket_context_t *) context, (struct us_internal_ssl_socket_t *) s, ext_size);
return (struct us_socket_t *) us_internal_ssl_socket_context_adopt_socket((struct us_internal_ssl_socket_context_t *) context, (struct us_internal_ssl_socket_t *) s, old_ext_size, ext_size);
}
#endif
@@ -807,7 +828,18 @@ struct us_socket_t *us_socket_context_adopt_socket(int ssl, struct us_socket_con
struct us_socket_t *new_s = s;
if (ext_size != -1) {
struct us_poll_t *pool_ref = &s->p;
new_s = (struct us_socket_t *) us_poll_resize(pool_ref, loop, sizeof(struct us_socket_t) + ext_size);
new_s = (struct us_socket_t *) us_poll_resize(pool_ref, loop, sizeof(struct us_socket_t) + old_ext_size, sizeof(struct us_socket_t) + ext_size);
if(new_s != s) {
/* Mark the old socket as closed */
s->flags.is_closed = 1;
/* Link this socket to the close-list and let it be deleted after this iteration */
s->next = s->context->loop->data.closed_head;
s->context->loop->data.closed_head = s;
/* Mark the old socket as adopted (reallocated) */
s->flags.adopted = 1;
/* Tell the event loop what is the new socket so we can process to send info to the right place and callbacks like more data and EOF*/
s->prev = new_s;
}
if (c) {
c->connecting_head = new_s;
c->context = context;

View File

@@ -396,7 +396,7 @@ void us_internal_update_handshake(struct us_internal_ssl_socket_t *s) {
}
int result = SSL_do_handshake(s->ssl);
if (SSL_get_shutdown(s->ssl) & SSL_RECEIVED_SHUTDOWN) {
us_internal_ssl_socket_close(s, 0, NULL);
return;
@@ -417,7 +417,7 @@ void us_internal_update_handshake(struct us_internal_ssl_socket_t *s) {
}
s->handshake_state = HANDSHAKE_PENDING;
s->ssl_write_wants_read = 1;
s->s.flags.last_write_failed = 1;
return;
}
// success
@@ -434,6 +434,7 @@ ssl_on_close(struct us_internal_ssl_socket_t *s, int code, void *reason) {
struct us_internal_ssl_socket_t * ret = context->on_close(s, code, reason);
SSL_free(s->ssl); // free SSL after on_close
s->ssl = NULL; // set to NULL
return ret;
}
@@ -1855,15 +1856,16 @@ void us_internal_ssl_socket_shutdown(struct us_internal_ssl_socket_t *s) {
struct us_internal_ssl_socket_t *us_internal_ssl_socket_context_adopt_socket(
struct us_internal_ssl_socket_context_t *context,
struct us_internal_ssl_socket_t *s, int ext_size) {
struct us_internal_ssl_socket_t *s, int old_ext_size, int ext_size) {
// todo: this is completely untested
int new_old_ext_size = sizeof(struct us_internal_ssl_socket_t) - sizeof(struct us_socket_t) + old_ext_size;
int new_ext_size = ext_size;
if (ext_size != -1) {
new_ext_size = sizeof(struct us_internal_ssl_socket_t) - sizeof(struct us_socket_t) + ext_size;
}
return (struct us_internal_ssl_socket_t *)us_socket_context_adopt_socket(
0, &context->sc, &s->s,
new_ext_size);
new_old_ext_size, new_ext_size);
}
struct us_internal_ssl_socket_t *
@@ -1920,10 +1922,11 @@ ssl_wrapped_context_on_data(struct us_internal_ssl_socket_t *s, char *data,
struct us_wrapped_socket_context_t *wrapped_context =
(struct us_wrapped_socket_context_t *)us_internal_ssl_socket_context_ext(
context);
// raw data if needed
// raw data if needed
if (wrapped_context->old_events.on_data) {
wrapped_context->old_events.on_data((struct us_socket_t *)s, data, length);
}
// ssl wrapped data
return ssl_on_data(s, data, length);
}
@@ -2028,7 +2031,7 @@ us_internal_ssl_socket_open(struct us_internal_ssl_socket_t *s, int is_client,
// already opened
if (s->ssl)
return s;
// start SSL open
return ssl_on_open(s, is_client, ip, ip_length, NULL);
}
@@ -2040,6 +2043,7 @@ struct us_socket_t *us_socket_upgrade_to_tls(us_socket_r s, us_socket_context_r
struct us_internal_ssl_socket_t *socket =
(struct us_internal_ssl_socket_t *)us_socket_context_adopt_socket(
0, new_context, s,
sizeof(void*),
(sizeof(struct us_internal_ssl_socket_t) - sizeof(struct us_socket_t)) + sizeof(void*));
socket->ssl = NULL;
socket->ssl_write_wants_read = 0;
@@ -2058,7 +2062,7 @@ struct us_socket_t *us_socket_upgrade_to_tls(us_socket_r s, us_socket_context_r
struct us_internal_ssl_socket_t *us_internal_ssl_socket_wrap_with_tls(
struct us_socket_t *s, struct us_bun_socket_context_options_t options,
struct us_socket_events_t events, int socket_ext_size) {
struct us_socket_events_t events, int old_socket_ext_size, int socket_ext_size) {
/* Cannot wrap a closed socket */
if (us_socket_is_closed(0, s)) {
return NULL;
@@ -2163,6 +2167,7 @@ us_socket_context_on_socket_connect_error(
struct us_internal_ssl_socket_t *socket =
(struct us_internal_ssl_socket_t *)us_socket_context_adopt_socket(
0, context, s,
old_socket_ext_size,
sizeof(struct us_internal_ssl_socket_t) - sizeof(struct us_socket_t) +
socket_ext_size);
socket->ssl = NULL;

View File

@@ -228,8 +228,8 @@ void us_loop_run(struct us_loop_t *loop) {
// > Instead, the filter will aggregate the events into a single kevent struct
// Note: EV_ERROR only sets the error in data as part of changelist. Not in this call!
int events = 0
| ((filter & EVFILT_READ) ? LIBUS_SOCKET_READABLE : 0)
| ((filter & EVFILT_WRITE) ? LIBUS_SOCKET_WRITABLE : 0);
| ((filter == EVFILT_READ) ? LIBUS_SOCKET_READABLE : 0)
| ((filter == EVFILT_WRITE) ? LIBUS_SOCKET_WRITABLE : 0);
const int error = (flags & (EV_ERROR)) ? ((int)fflags || 1) : 0;
const int eof = (flags & (EV_EOF));
#endif
@@ -325,7 +325,7 @@ void us_internal_loop_update_pending_ready_polls(struct us_loop_t *loop, struct
int num_entries_possibly_remaining = 1;
#else
/* Ready polls may contain same poll twice under kqueue, as one poll may hold two filters */
int num_entries_possibly_remaining = 2;//((old_events & LIBUS_SOCKET_READABLE) ? 1 : 0) + ((old_events & LIBUS_SOCKET_WRITABLE) ? 1 : 0);
int num_entries_possibly_remaining = 2;
#endif
/* Todo: for kqueue if we track things in us_change_poll it is possible to have a fast path with no seeking in cases of:
@@ -360,11 +360,11 @@ int kqueue_change(int kqfd, int fd, int old_events, int new_events, void *user_d
if(!is_readable && !is_writable) {
if(!(old_events & LIBUS_SOCKET_WRITABLE)) {
// if we are not reading or writing, we need to add writable to receive FIN
EV_SET64(&change_list[change_length++], fd, EVFILT_WRITE, EV_ADD, 0, 0, (uint64_t)(void*)user_data, 0, 0);
EV_SET64(&change_list[change_length++], fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, (uint64_t)(void*)user_data, 0, 0);
}
} else if ((new_events & LIBUS_SOCKET_WRITABLE) != (old_events & LIBUS_SOCKET_WRITABLE)) {
/* Do they differ in writable? */
EV_SET64(&change_list[change_length++], fd, EVFILT_WRITE, (new_events & LIBUS_SOCKET_WRITABLE) ? EV_ADD : EV_DELETE, 0, 0, (uint64_t)(void*)user_data, 0, 0);
EV_SET64(&change_list[change_length++], fd, EVFILT_WRITE, (new_events & LIBUS_SOCKET_WRITABLE) ? EV_ADD | EV_ONESHOT : EV_DELETE, 0, 0, (uint64_t)(void*)user_data, 0, 0);
}
int ret;
do {
@@ -377,22 +377,30 @@ int kqueue_change(int kqfd, int fd, int old_events, int new_events, void *user_d
}
#endif
struct us_poll_t *us_poll_resize(struct us_poll_t *p, struct us_loop_t *loop, unsigned int ext_size) {
int events = us_poll_events(p);
struct us_poll_t *us_poll_resize(struct us_poll_t *p, struct us_loop_t *loop, unsigned int old_ext_size, unsigned int ext_size) {
struct us_poll_t *new_p = us_realloc(p, sizeof(struct us_poll_t) + ext_size);
if (p != new_p) {
unsigned int old_size = sizeof(struct us_poll_t) + old_ext_size;
unsigned int new_size = sizeof(struct us_poll_t) + ext_size;
if(new_size <= old_size) return p;
struct us_poll_t *new_p = us_calloc(1, new_size);
memcpy(new_p, p, old_size);
/* Increment poll count for the new poll - the old poll will be freed separately
* which decrements the count, keeping the total correct */
loop->num_polls++;
int events = us_poll_events(p);
#ifdef LIBUS_USE_EPOLL
/* Hack: forcefully update poll by stripping away already set events */
new_p->state.poll_type = us_internal_poll_type(new_p);
us_poll_change(new_p, loop, events);
/* Hack: forcefully update poll by stripping away already set events */
new_p->state.poll_type = us_internal_poll_type(new_p);
us_poll_change(new_p, loop, events);
#else
/* Forcefully update poll by resetting them with new_p as user data */
kqueue_change(loop->fd, new_p->state.fd, 0, LIBUS_SOCKET_WRITABLE | LIBUS_SOCKET_READABLE, new_p);
#endif /* This is needed for epoll also (us_change_poll doesn't update the old poll) */
us_internal_loop_update_pending_ready_polls(loop, p, new_p, events, events);
}
/* Forcefully update poll by resetting them with new_p as user data */
kqueue_change(loop->fd, new_p->state.fd, 0, LIBUS_SOCKET_WRITABLE | LIBUS_SOCKET_READABLE, new_p);
#endif
/* This is needed for epoll also (us_change_poll doesn't update the old poll) */
us_internal_loop_update_pending_ready_polls(loop, p, new_p, events, events);
return new_p;
}
@@ -444,7 +452,7 @@ void us_poll_change(struct us_poll_t *p, struct us_loop_t *loop, int events) {
kqueue_change(loop->fd, p->state.fd, old_events, events, p);
#endif
/* Set all removed events to null-polls in pending ready poll list */
// us_internal_loop_update_pending_ready_polls(loop, p, p, old_events, events);
us_internal_loop_update_pending_ready_polls(loop, p, p, old_events, events);
}
}

View File

@@ -71,6 +71,11 @@ void us_poll_init(struct us_poll_t *p, LIBUS_SOCKET_DESCRIPTOR fd,
}
void us_poll_free(struct us_poll_t *p, struct us_loop_t *loop) {
// poll was resized and dont own uv_poll_t anymore
if(!p->uv_p) {
free(p);
return;
}
/* The idea here is like so; in us_poll_stop we call uv_close after setting
* data of uv-poll to 0. This means that in close_cb_free we call free on 0
* with does nothing, since us_poll_stop should not really free the poll.
@@ -86,6 +91,7 @@ void us_poll_free(struct us_poll_t *p, struct us_loop_t *loop) {
}
void us_poll_start(struct us_poll_t *p, struct us_loop_t *loop, int events) {
if(!p->uv_p) return;
p->poll_type = us_internal_poll_type(p) |
((events & LIBUS_SOCKET_READABLE) ? POLL_TYPE_POLLING_IN : 0) |
((events & LIBUS_SOCKET_WRITABLE) ? POLL_TYPE_POLLING_OUT : 0);
@@ -99,6 +105,7 @@ void us_poll_start(struct us_poll_t *p, struct us_loop_t *loop, int events) {
}
void us_poll_change(struct us_poll_t *p, struct us_loop_t *loop, int events) {
if(!p->uv_p) return;
if (us_poll_events(p) != events) {
p->poll_type =
us_internal_poll_type(p) |
@@ -109,6 +116,7 @@ void us_poll_change(struct us_poll_t *p, struct us_loop_t *loop, int events) {
}
void us_poll_stop(struct us_poll_t *p, struct us_loop_t *loop) {
if(!p->uv_p) return;
uv_poll_stop(p->uv_p);
/* We normally only want to close the poll here, not free it. But if we stop
@@ -217,10 +225,20 @@ struct us_poll_t *us_create_poll(struct us_loop_t *loop, int fallthrough,
/* If we update our block position we have to update the uv_poll data to point
* to us */
struct us_poll_t *us_poll_resize(struct us_poll_t *p, struct us_loop_t *loop,
unsigned int ext_size) {
unsigned int old_ext_size, unsigned int ext_size) {
// cannot resize if we dont own uv_poll_t
if(!p->uv_p) return p;
unsigned int old_size = sizeof(struct us_poll_t) + old_ext_size;
unsigned int new_size = sizeof(struct us_poll_t) + ext_size;
if(new_size <= old_size) return p;
struct us_poll_t *new_p = calloc(1, new_size);
memcpy(new_p, p, old_size);
struct us_poll_t *new_p = realloc(p, sizeof(struct us_poll_t) + ext_size);
new_p->uv_p->data = new_p;
p->uv_p = NULL;
return new_p;
}

View File

@@ -170,6 +170,14 @@ struct us_socket_flags {
unsigned char low_prio_state: 2;
/* If true, the socket should be read using readmsg to support receiving file descriptors */
bool is_ipc: 1;
/* If true, the socket has been closed */
bool is_closed: 1;
/* If true, the socket was reallocated during adoption */
bool adopted: 1;
/* If true, the socket is a TLS socket */
bool is_tls: 1;
/* If true, the last write to this socket failed (would block) */
bool last_write_failed: 1;
} __attribute__((packed));
@@ -435,11 +443,11 @@ void us_internal_ssl_socket_shutdown(us_internal_ssl_socket_r s);
struct us_internal_ssl_socket_t *us_internal_ssl_socket_context_adopt_socket(
us_internal_ssl_socket_context_r context,
us_internal_ssl_socket_r s, int ext_size);
us_internal_ssl_socket_r s, int old_ext_size, int ext_size);
struct us_internal_ssl_socket_t *us_internal_ssl_socket_wrap_with_tls(
us_socket_r s, struct us_bun_socket_context_options_t options,
struct us_socket_events_t events, int socket_ext_size);
struct us_socket_events_t events, int old_socket_ext_size, int socket_ext_size);
struct us_internal_ssl_socket_context_t *
us_internal_create_child_ssl_socket_context(
us_internal_ssl_socket_context_r context, int context_ext_size);

View File

@@ -37,7 +37,6 @@ struct us_internal_loop_data_t {
struct us_timer_t *sweep_timer;
int sweep_timer_count;
struct us_internal_async *wakeup_async;
int last_write_failed;
struct us_socket_context_t *head;
struct us_socket_context_t *iterator;
struct us_socket_context_t *closed_context_head;

View File

@@ -349,7 +349,7 @@ struct us_loop_t *us_socket_context_loop(int ssl, us_socket_context_r context) n
/* Invalidates passed socket, returning a new resized socket which belongs to a different socket context.
* Used mainly for "socket upgrades" such as when transitioning from HTTP to WebSocket. */
struct us_socket_t *us_socket_context_adopt_socket(int ssl, us_socket_context_r context, us_socket_r s, int ext_size);
struct us_socket_t *us_socket_context_adopt_socket(int ssl, us_socket_context_r context, us_socket_r s, int old_ext_size, int ext_size);
struct us_socket_t *us_socket_upgrade_to_tls(us_socket_r s, us_socket_context_r new_context, const char *sni);
@@ -411,7 +411,7 @@ void *us_poll_ext(us_poll_r p) nonnull_fn_decl;
LIBUS_SOCKET_DESCRIPTOR us_poll_fd(us_poll_r p) nonnull_fn_decl;
/* Resize an active poll */
struct us_poll_t *us_poll_resize(us_poll_r p, us_loop_r loop, unsigned int ext_size) nonnull_fn_decl;
struct us_poll_t *us_poll_resize(us_poll_r p, us_loop_r loop, unsigned int old_ext_size, unsigned int ext_size) nonnull_fn_decl;
/* Public interfaces for sockets */
@@ -470,7 +470,7 @@ void us_socket_local_address(int ssl, us_socket_r s, char *nonnull_arg buf, int
/* Bun extras */
struct us_socket_t *us_socket_pair(struct us_socket_context_t *ctx, int socket_ext_size, LIBUS_SOCKET_DESCRIPTOR* fds);
struct us_socket_t *us_socket_from_fd(struct us_socket_context_t *ctx, int socket_ext_size, LIBUS_SOCKET_DESCRIPTOR fd, int ipc);
struct us_socket_t *us_socket_wrap_with_tls(int ssl, us_socket_r s, struct us_bun_socket_context_options_t options, struct us_socket_events_t events, int socket_ext_size);
struct us_socket_t *us_socket_wrap_with_tls(int ssl, us_socket_r s, struct us_bun_socket_context_options_t options, struct us_socket_events_t events, int old_socket_ext_size, int socket_ext_size);
int us_socket_raw_write(int ssl, us_socket_r s, const char *data, int length);
struct us_socket_t* us_socket_open(int ssl, struct us_socket_t * s, int is_client, char* ip, int ip_length);
int us_raw_root_certs(struct us_cert_string_t**out);

View File

@@ -193,9 +193,17 @@ void us_internal_handle_low_priority_sockets(struct us_loop_t *loop) {
loop_data->low_prio_head = s->next;
if (s->next) s->next->prev = 0;
s->next = 0;
int ssl = s->flags.is_tls;
if(us_socket_is_closed(ssl, s)) {
s->flags.low_prio_state = 2;
us_socket_context_unref(ssl, s->context);
continue;
}
us_internal_socket_context_link_socket(0, s->context, s);
us_poll_change(&s->p, us_socket_context(0, s)->loop, us_poll_events(&s->p) | LIBUS_SOCKET_READABLE);
us_internal_socket_context_link_socket(ssl, s->context, s);
us_socket_context_unref(ssl, s->context);
us_poll_change(&s->p, us_socket_context(ssl, s)->loop, us_poll_events(&s->p) | LIBUS_SOCKET_READABLE);
s->flags.low_prio_state = 2;
}
@@ -243,6 +251,7 @@ void us_internal_free_closed_sockets(struct us_loop_t *loop) {
/* Free all closed sockets (maybe it is better to reverse order?) */
for (struct us_socket_t *s = loop->data.closed_head; s; ) {
struct us_socket_t *next = s->next;
s->prev = s->next = 0;
us_poll_free((struct us_poll_t *) s, loop);
s = next;
}
@@ -347,6 +356,9 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
s->flags.allow_half_open = listen_socket->s.flags.allow_half_open;
s->flags.is_paused = 0;
s->flags.is_ipc = 0;
s->flags.is_closed = 0;
s->flags.adopted = 0;
s->flags.is_tls = listen_socket->s.flags.is_tls;
/* We always use nodelay */
bsd_socket_nodelay(client_fd, 1);
@@ -354,7 +366,10 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
us_internal_socket_context_link_socket(0, listen_socket->s.context, s);
listen_socket->s.context->on_open(s, 0, bsd_addr_get_ip(&addr), bsd_addr_get_ip_length(&addr));
/* After socket adoption, track the new socket; the old one becomes invalid */
if(s && s->flags.adopted && s->prev) {
s = s->prev;
}
/* Exit accept loop if listen socket was closed in on_open handler */
if (us_socket_is_closed(0, &listen_socket->s)) {
break;
@@ -369,22 +384,37 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
case POLL_TYPE_SOCKET: {
/* We should only use s, no p after this point */
struct us_socket_t *s = (struct us_socket_t *) p;
/* After socket adoption, track the new socket; the old one becomes invalid */
if(s && s->flags.adopted && s->prev) {
s = s->prev;
}
/* The context can change after calling a callback but the loop is always the same */
struct us_loop_t* loop = s->context->loop;
if (events & LIBUS_SOCKET_WRITABLE && !error) {
/* Note: if we failed a write as a socket of one loop then adopted
* to another loop, this will be wrong. Absurd case though */
loop->data.last_write_failed = 0;
s->flags.last_write_failed = 0;
#ifdef LIBUS_USE_KQUEUE
/* Kqueue is one-shot so is not writable anymore */
p->state.poll_type = us_internal_poll_type(p) | ((events & LIBUS_SOCKET_READABLE) ? POLL_TYPE_POLLING_IN : 0);
#endif
s = s->context->on_writable(s);
/* After socket adoption, track the new socket; the old one becomes invalid */
if(s && s->flags.adopted && s->prev) {
s = s->prev;
}
if (!s || us_socket_is_closed(0, s)) {
return;
}
/* If we have no failed write or if we shut down, then stop polling for more writable */
if (!loop->data.last_write_failed || us_socket_is_shut_down(0, s)) {
if (!s->flags.last_write_failed || us_socket_is_shut_down(0, s)) {
us_poll_change(&s->p, loop, us_poll_events(&s->p) & LIBUS_SOCKET_READABLE);
} else {
#ifdef LIBUS_USE_KQUEUE
/* Kqueue one-shot writable needs to be re-enabled */
us_poll_change(&s->p, loop, us_poll_events(&s->p) | LIBUS_SOCKET_WRITABLE);
#endif
}
}
@@ -468,6 +498,10 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
if (length > 0) {
s = s->context->on_data(s, loop->data.recv_buf + LIBUS_RECV_BUFFER_PADDING, length);
/* After socket adoption, track the new socket; the old one becomes invalid */
if(s && s->flags.adopted && s->prev) {
s = s->prev;
}
// loop->num_ready_polls isn't accessible on Windows.
#ifndef WIN32
// rare case: we're reading a lot of data, there's more to be read, and either:

View File

@@ -125,7 +125,7 @@ int us_socket_is_closed(int ssl, struct us_socket_t *s) {
if(ssl) {
return us_internal_ssl_socket_is_closed((struct us_internal_ssl_socket_t *) s);
}
return s->prev == (struct us_socket_t *) s->context;
return s->flags.is_closed;
}
int us_connecting_socket_is_closed(int ssl, struct us_connecting_socket_t *c) {
@@ -159,8 +159,8 @@ void us_connecting_socket_close(int ssl, struct us_connecting_socket_t *c) {
s->next = s->context->loop->data.closed_head;
s->context->loop->data.closed_head = s;
/* Any socket with prev = context is marked as closed */
s->prev = (struct us_socket_t *) s->context;
/* Mark the socket as closed */
s->flags.is_closed = 1;
}
if(!c->error) {
// if we have no error, we have to set that we were aborted aka we called close
@@ -218,11 +218,10 @@ struct us_socket_t *us_socket_close(int ssl, struct us_socket_t *s, int code, vo
bsd_close_socket(us_poll_fd((struct us_poll_t *) s));
/* Mark the socket as closed */
s->flags.is_closed = 1;
/* Any socket with prev = context is marked as closed */
s->prev = (struct us_socket_t *) s->context;
/* mark it as closed and call the callback */
/* call the callback */
struct us_socket_t *res = s;
if (!(us_internal_poll_type(&s->p) & POLL_TYPE_SEMI_SOCKET)) {
res = s->context->on_close(s, code, reason);
@@ -268,8 +267,8 @@ struct us_socket_t *us_socket_detach(int ssl, struct us_socket_t *s) {
s->next = s->context->loop->data.closed_head;
s->context->loop->data.closed_head = s;
/* Any socket with prev = context is marked as closed */
s->prev = (struct us_socket_t *) s->context;
/* Mark the socket as closed */
s->flags.is_closed = 1;
return s;
}
@@ -321,8 +320,10 @@ struct us_socket_t *us_socket_from_fd(struct us_socket_context_t *ctx, int socke
s->flags.low_prio_state = 0;
s->flags.allow_half_open = 0;
s->flags.is_paused = 0;
s->flags.is_ipc = 0;
s->flags.is_ipc = ipc;
s->flags.is_closed = 0;
s->flags.adopted = 0;
s->flags.is_tls = 0;
s->connect_state = NULL;
/* We always use nodelay */
@@ -369,7 +370,7 @@ int us_socket_write(int ssl, struct us_socket_t *s, const char *data, int length
int written = bsd_send(us_poll_fd(&s->p), data, length);
if (written != length) {
s->context->loop->data.last_write_failed = 1;
s->flags.last_write_failed = 1;
us_poll_change(&s->p, s->context->loop, LIBUS_SOCKET_READABLE | LIBUS_SOCKET_WRITABLE);
}
@@ -406,7 +407,7 @@ int us_socket_ipc_write_fd(struct us_socket_t *s, const char* data, int length,
int sent = bsd_sendmsg(us_poll_fd(&s->p), &msg, 0);
if (sent != length) {
s->context->loop->data.last_write_failed = 1;
s->flags.last_write_failed = 1;
us_poll_change(&s->p, s->context->loop, LIBUS_SOCKET_READABLE | LIBUS_SOCKET_WRITABLE);
}
@@ -476,13 +477,13 @@ int us_connecting_socket_get_error(int ssl, struct us_connecting_socket_t *c) {
Note: this assumes that the socket is non-TLS and will be adopted and wrapped with a new TLS context
context ext will not be copied to the new context, new context will contain us_wrapped_socket_context_t on ext
*/
struct us_socket_t *us_socket_wrap_with_tls(int ssl, struct us_socket_t *s, struct us_bun_socket_context_options_t options, struct us_socket_events_t events, int socket_ext_size) {
struct us_socket_t *us_socket_wrap_with_tls(int ssl, struct us_socket_t *s, struct us_bun_socket_context_options_t options, struct us_socket_events_t events, int old_socket_ext_size, int socket_ext_size) {
// only accepts non-TLS sockets
if (ssl) {
return NULL;
}
return(struct us_socket_t *) us_internal_ssl_socket_wrap_with_tls(s, options, events, socket_ext_size);
return(struct us_socket_t *) us_internal_ssl_socket_wrap_with_tls(s, options, events, old_socket_ext_size, socket_ext_size);
}
// if a TLS socket calls this, it will start SSL call open event and TLS handshake if required

View File

@@ -84,6 +84,7 @@ struct AsyncSocketData {
/* Or empty */
AsyncSocketData() = default;
bool isIdle = false;
bool isAuthorized = false; // per-socket TLS authorization status
};
}

View File

@@ -213,6 +213,16 @@ namespace uWS {
emitSoon = std::string_view(data.data(), chunkSize(state) - 2);
shouldEmit = true;
}
// Validate that the chunk terminator is \r\n to prevent request smuggling
// The last 2 bytes of the chunk must be exactly \r\n
// Note: chunkSize always includes +2 for the terminator (added in consumeHexNumber),
// and chunks with size 0 (chunkSize == 2) are handled earlier at line 190.
// Therefore chunkSize >= 3 here, so no underflow is possible.
size_t terminatorOffset = chunkSize(state) - 2;
if (data[terminatorOffset] != '\r' || data[terminatorOffset + 1] != '\n') {
state = STATE_IS_ERROR;
return std::nullopt;
}
data.remove_prefix(chunkSize(state));
state = STATE_IS_CHUNKED;
if (shouldEmit) {

View File

@@ -124,15 +124,16 @@ private:
// if we are closing or already closed, we don't need to do anything
if (!us_socket_is_closed(SSL, s)) {
HttpContextData<SSL> *httpContextData = getSocketContextDataS(s);
httpContextData->flags.isAuthorized = success;
// Set per-socket authorization status
auto *httpResponseData = reinterpret_cast<HttpResponseData<SSL> *>(us_socket_ext(SSL, s));
if(httpContextData->flags.rejectUnauthorized) {
if(!success || verify_error.error != 0) {
// we failed to handshake, close the socket
us_socket_close(SSL, s, 0, nullptr);
return;
}
httpContextData->flags.isAuthorized = true;
}
httpResponseData->isAuthorized = success;
/* Any connected socket should timeout until it has a request */
((HttpResponse<SSL> *) s)->resetTimeout();

View File

@@ -331,7 +331,7 @@ public:
/* Adopting a socket invalidates it, do not rely on it directly to carry any data */
us_socket_t *usSocket = us_socket_context_adopt_socket(SSL, (us_socket_context_t *) webSocketContext, (us_socket_t *) this, sizeof(WebSocketData) + sizeof(UserData));
us_socket_t *usSocket = us_socket_context_adopt_socket(SSL, (us_socket_context_t *) webSocketContext, (us_socket_t *) this, sizeof(HttpResponseData<SSL>), sizeof(WebSocketData) + sizeof(UserData));
WebSocket<SSL, true, UserData> *webSocket = (WebSocket<SSL, true, UserData> *) usSocket;
/* For whatever reason we were corked, update cork to the new socket */

View File

@@ -4,10 +4,6 @@
"workspaces": {
"": {
"name": "bun-vscode",
"dependencies": {
"glob": "^13.0.0",
"ws": "^8.18.3",
},
"devDependencies": {
"@types/bun": "^1.1.10",
"@types/vscode": "^1.60.0",
@@ -107,10 +103,6 @@
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="],
"@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
"@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="],
@@ -353,7 +345,7 @@
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
"glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="],
"glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
@@ -541,7 +533,7 @@
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
"path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
@@ -699,8 +691,6 @@
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
"@vscode/test-cli/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
"@vscode/vsce/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"@vscode/vsce/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
@@ -715,8 +705,6 @@
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
"htmlparser2/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"istanbul-lib-report/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
@@ -745,7 +733,7 @@
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"path-scurry/lru-cache": ["lru-cache@11.2.2", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="],
"path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
@@ -773,8 +761,6 @@
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
"@vscode/test-cli/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
"@vscode/vsce/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
@@ -805,8 +791,6 @@
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"@vscode/test-cli/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"log-symbols/chalk/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"mocha/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],

View File

@@ -378,9 +378,5 @@
"workspaces": [
"../bun-debug-adapter-protocol",
"../bun-inspector-protocol"
],
"overrides": {
"glob": "^13.0.0",
"ws": "^8.18.3"
}
]
}

View File

@@ -533,15 +533,19 @@ describe("BunTestController (static file parser)", () => {
test.todo("todo test", () => {});
test.only("only test", () => {});
test.failing("failing test", () => {});
test.concurrent("concurrent test", () => {});
test.serial("serial test", () => {});
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(4);
expect(result).toHaveLength(6);
expect(result[0].name).toBe("skipped test");
expect(result[1].name).toBe("todo test");
expect(result[2].name).toBe("only test");
expect(result[3].name).toBe("failing test");
expect(result[4].name).toBe("concurrent test");
expect(result[5].name).toBe("serial test");
});
test("should handle conditional tests", () => {
@@ -549,14 +553,41 @@ describe("BunTestController (static file parser)", () => {
test.if(true)("conditional test", () => {});
test.skipIf(false)("skip if test", () => {});
test.todoIf(true)("todo if test", () => {});
test.failingIf(true)("failing if test", () => {});
test.concurrentIf(true)("concurrent if test", () => {});
test.serialIf(true)("serial if test", () => {});
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(3);
expect(result).toHaveLength(6);
expect(result[0].name).toBe("conditional test");
expect(result[1].name).toBe("skip if test");
expect(result[2].name).toBe("todo if test");
expect(result[3].name).toBe("failing if test");
expect(result[4].name).toBe("concurrent if test");
expect(result[5].name).toBe("serial if test");
});
test("should handle describe modifiers", () => {
const content = `
describe.concurrent("concurrent describe", () => {
test("test in concurrent", () => {});
});
describe.serial("serial describe", () => {
test("test in serial", () => {});
});
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(2);
expect(result[0].name).toBe("concurrent describe");
expect(result[0].type).toBe("describe");
expect(result[0].children).toHaveLength(1);
expect(result[1].name).toBe("serial describe");
expect(result[1].type).toBe("describe");
expect(result[1].children).toHaveLength(1);
});
test("should ignore comments", () => {

View File

@@ -340,7 +340,7 @@ export class BunTestController implements vscode.Disposable {
});
const testRegex =
/\b(describe|test|it)(?:\.(?:skip|todo|failing|only))?(?:\.(?:if|todoIf|skipIf)\s*\([^)]*\))?(?:\.each\s*\([^)]*\))?\s*\(\s*(['"`])((?:\\\2|.)*?)\2\s*(?:,|\))/g;
/\b(describe|test|it)(?:\.(?:skip|todo|failing|only|concurrent|serial))*(?:\.(?:if|todoIf|skipIf|failingIf|concurrentIf|serialIf)\s*\([^)]*\))?(?:\.each\s*\([^)]*\))?\s*\(\s*(['"`])((?:\\\2|.)*?)\2\s*(?:,|\))/g;
const stack: TestNode[] = [];
const root: TestNode[] = [];
@@ -1391,7 +1391,7 @@ export class BunTestController implements vscode.Disposable {
}
const { bunCommand, testArgs } = this.getBunExecutionConfig();
const args = [...testArgs, ...testFiles];
const args = [bunCommand, ...testArgs, ...testFiles];
if (!isIndividualTestRun) {
args.push("--inspect-brk");
@@ -1416,14 +1416,14 @@ export class BunTestController implements vscode.Disposable {
}
const debugConfiguration: vscode.DebugConfiguration = {
args: args.slice(1),
args: args.slice(2),
console: "integratedTerminal",
cwd: "${workspaceFolder}",
internalConsoleOptions: "neverOpen",
name: "Bun Test Debug",
program: args.at(1),
request: "launch",
runtime: bunCommand,
runtime: args.at(0),
type: "bun",
};

2
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly-2025-12-10"

View File

@@ -1,5 +1,5 @@
#!/bin/sh
# Version: 20
# Version: 25
# A script that installs the dependencies needed to build and test Bun.
# This should work on macOS and Linux with a POSIX shell.
@@ -1155,12 +1155,23 @@ llvm_version() {
install_llvm() {
case "$pm" in
apt)
bash="$(require bash)"
llvm_script="$(download_file "https://apt.llvm.org/llvm.sh")"
execute_sudo "$bash" "$llvm_script" "$(llvm_version)" all
# Debian 13 (Trixie) has LLVM 19 natively, and apt.llvm.org doesn't have a trixie repo
if [ "$distro" = "debian" ]; then
install_packages \
"llvm-$(llvm_version)" \
"clang-$(llvm_version)" \
"lld-$(llvm_version)" \
"llvm-$(llvm_version)-dev" \
"llvm-$(llvm_version)-tools" \
"libclang-rt-$(llvm_version)-dev"
else
bash="$(require bash)"
llvm_script="$(download_file "https://apt.llvm.org/llvm.sh")"
execute_sudo "$bash" "$llvm_script" "$(llvm_version)" all
# Install llvm-symbolizer explicitly to ensure it's available for ASAN
install_packages "llvm-$(llvm_version)-tools"
# Install llvm-symbolizer explicitly to ensure it's available for ASAN
install_packages "llvm-$(llvm_version)-tools"
fi
;;
brew)
install_packages "llvm@$(llvm_version)"
@@ -1297,11 +1308,6 @@ install_sccache() {
install_rust() {
case "$distro" in
alpine)
install_packages \
rust \
cargo
;;
freebsd)
install_packages lang/rust
create_directory "$HOME/.cargo/bin"
@@ -1317,6 +1323,9 @@ install_rust() {
rustup_script=$(download_file "https://sh.rustup.rs")
execute "$sh" -lc "$rustup_script -y --no-modify-path"
append_to_path "$rust_home/bin"
# Ensure all rustup files are accessible (for CI builds where different users run builds)
grant_to_user "$rust_home"
;;
esac
@@ -1565,7 +1574,7 @@ install_buildkite() {
return
fi
buildkite_version="3.87.0"
buildkite_version="3.114.0"
case "$arch" in
aarch64)
buildkite_arch="arm64"
@@ -1766,7 +1775,7 @@ ensure_no_tmpfs() {
if ! [ "$os" = "linux" ]; then
return
fi
if ! [ "$distro" = "ubuntu" ]; then
if ! ( [ "$distro" = "ubuntu" ] || [ "$distro" = "debian" ] ); then
return
fi

View File

@@ -87,6 +87,9 @@ async function build(args) {
flag.startsWith("-D") ? [`${flag}=${value}`] : [flag, value],
);
try {
await Bun.file(buildPath + "/CMakeCache.txt").delete();
} catch (e) {}
await startGroup("CMake Configure", () => spawn("cmake", generateArgs, { env }));
const envPath = resolve(buildPath, ".env");

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env bun
/**
* Creates link-metadata.json with link command and build metadata
*
* Usage: bun scripts/create-link-metadata.mjs <build-path> <bun-target>
*/
import { $ } from "bun";
import { dirname, join } from "path";
const [buildPath, bunTarget] = process.argv.slice(2);
if (!buildPath || !bunTarget) {
console.error("Usage: bun scripts/create-link-metadata.mjs <build-path> <bun-target>");
process.exit(1);
}
// Get the repo root (parent of scripts directory)
const repoRoot = dirname(import.meta.dir);
// Extract link command using ninja
console.log("Extracting link command...");
const linkCommandResult = await $`ninja -C ${buildPath} -t commands ${bunTarget}`.quiet();
const linkCommand = linkCommandResult.stdout.toString().trim();
// Read linker-related files from src/
console.log("Reading linker files...");
const linkerLds = await Bun.file(join(repoRoot, "src", "linker.lds")).text();
const symbolsDyn = await Bun.file(join(repoRoot, "src", "symbols.dyn")).text();
const symbolsTxt = await Bun.file(join(repoRoot, "src", "symbols.txt")).text();
// Create metadata JSON with link command included
const metadata = {
bun_version: process.env.BUN_VERSION || "",
webkit_url: process.env.WEBKIT_DOWNLOAD_URL || "",
webkit_version: process.env.WEBKIT_VERSION || "",
zig_commit: process.env.ZIG_COMMIT || "",
target: bunTarget,
timestamp: new Date().toISOString(),
link_command: linkCommand,
linker_lds: linkerLds,
symbols_dyn: symbolsDyn,
symbols_txt: symbolsTxt,
};
const metadataPath = join(buildPath, "link-metadata.json");
await Bun.write(metadataPath, JSON.stringify(metadata, null, 2));
console.log(`Written to ${metadataPath}`);
console.log("Done");

View File

@@ -77,9 +77,8 @@ const testTimeout = 3 * 60_000;
const integrationTimeout = 5 * 60_000;
function getNodeParallelTestTimeout(testPath) {
if (testPath.includes("test-dns")) {
return 90_000;
}
if (testPath.includes("test-dns")) return 60_000;
if (testPath.includes("-docker-")) return 60_000;
if (!isCI) return 60_000; // everything slower in debug mode
if (options["step"]?.includes("-asan-")) return 60_000;
return 20_000;

View File

@@ -2842,7 +2842,7 @@ export function printEnvironment() {
if (isCI) {
startGroup("Environment", () => {
for (const [key, value] of Object.entries(process.env)) {
for (const [key, value] of Object.entries(process.env).toSorted()) {
console.log(`${key}:`, value);
}
});

View File

@@ -119,7 +119,7 @@ pub const StandaloneModuleGraph = struct {
};
const Macho = struct {
pub extern "C" fn Bun__getStandaloneModuleGraphMachoLength() ?*align(1) u32;
pub extern "C" fn Bun__getStandaloneModuleGraphMachoLength() ?*align(1) u64;
pub fn getData() ?[]const u8 {
if (Bun__getStandaloneModuleGraphMachoLength()) |length| {
@@ -127,8 +127,10 @@ pub const StandaloneModuleGraph = struct {
return null;
}
// BlobHeader has 8 bytes size (u64), so data starts at offset 8.
const data_offset = @sizeOf(u64);
const slice_ptr: [*]const u8 = @ptrCast(length);
return slice_ptr[4..][0..length.*];
return slice_ptr[data_offset..][0..length.*];
}
return null;
@@ -136,7 +138,7 @@ pub const StandaloneModuleGraph = struct {
};
const PE = struct {
pub extern "C" fn Bun__getStandaloneModuleGraphPELength() u32;
pub extern "C" fn Bun__getStandaloneModuleGraphPELength() u64;
pub extern "C" fn Bun__getStandaloneModuleGraphPEData() ?[*]u8;
pub fn getData() ?[]const u8 {
@@ -296,7 +298,9 @@ pub const StandaloneModuleGraph = struct {
pub const Flags = packed struct(u32) {
disable_default_env_files: bool = false,
disable_autoload_bunfig: bool = false,
_padding: u30 = 0,
disable_autoload_tsconfig: bool = false,
disable_autoload_package_json: bool = false,
_padding: u28 = 0,
};
const trailer = "\n---- Bun! ----\n";

View File

@@ -132,7 +132,7 @@ pub fn create(bytes: []const u8) bun.sys.Maybe(bun.webcore.Blob.Store.Bytes) {
const label = std.fmt.bufPrintZ(&label_buf, "memfd-num-{d}", .{memfd_counter.fetchAdd(1, .monotonic)}) catch "";
// Using huge pages was slower.
const fd = switch (bun.sys.memfd_create(label, std.os.linux.MFD.CLOEXEC)) {
const fd = switch (bun.sys.memfd_create(label, .non_executable)) {
.err => |err| return .{ .err = bun.sys.Error.fromCode(err.getErrno(), .open) },
.result => |fd| fd,
};

View File

@@ -1655,6 +1655,9 @@ pub const api = struct {
drop: []const []const u8 = &.{},
/// feature_flags for dead-code elimination via `import { feature } from "bun:bundle"`
feature_flags: []const []const u8 = &.{},
/// preserve_symlinks
preserve_symlinks: ?bool = null,

View File

@@ -296,7 +296,7 @@ fn deduplicatedImport(
// Disable this one since an older record is getting used. It isn't
// practical to delete this import record entry since an import or
// require expression can exist.
ir.is_unused = true;
ir.flags.is_unused = true;
const stmt = ctx.stmts.items[gop.value_ptr.stmt_index].data.s_import;
if (items.len > 0) {

View File

@@ -312,8 +312,9 @@ pub fn getObject(expr: *const Expr, name: string) ?Expr {
pub fn getBoolean(expr: *const Expr, name: string) ?bool {
if (expr.asProperty(name)) |query| {
if (query.expr.data == .e_boolean) {
return query.expr.data.e_boolean.value;
switch (query.expr.data) {
.e_boolean, .e_branch_boolean => |b| return b.value,
else => {},
}
}
return null;
@@ -510,9 +511,10 @@ pub inline fn asStringZ(expr: *const Expr, allocator: std.mem.Allocator) OOM!?st
pub fn asBool(
expr: *const Expr,
) ?bool {
if (expr.data != .e_boolean) return null;
return expr.data.e_boolean.value;
return switch (expr.data) {
.e_boolean, .e_branch_boolean => |b| b.value,
else => null,
};
}
pub fn asNumber(expr: *const Expr) ?f64 {
@@ -1490,6 +1492,11 @@ pub const Tag = enum {
e_private_identifier,
e_commonjs_export_identifier,
e_boolean,
/// Like e_boolean, but produced by `feature()` from `bun:bundle`.
/// This tag ensures feature() can only be used directly in conditional
/// contexts (if statements, ternaries). Invalid usage is caught during
/// the visit phase when this expression appears outside a branch condition.
e_branch_boolean,
e_number,
e_big_int,
e_string,
@@ -1513,7 +1520,7 @@ pub const Tag = enum {
// object, regex and array may have had side effects
pub fn isPrimitiveLiteral(tag: Tag) bool {
return switch (tag) {
.e_null, .e_undefined, .e_string, .e_boolean, .e_number, .e_big_int => true,
.e_null, .e_undefined, .e_string, .e_boolean, .e_branch_boolean, .e_number, .e_big_int => true,
else => false,
};
}
@@ -1522,7 +1529,7 @@ pub const Tag = enum {
return switch (tag) {
.e_array, .e_object, .e_null, .e_reg_exp => "object",
.e_undefined => "undefined",
.e_boolean => "boolean",
.e_boolean, .e_branch_boolean => "boolean",
.e_number => "number",
.e_big_int => "bigint",
.e_string => "string",
@@ -1537,7 +1544,7 @@ pub const Tag = enum {
.e_array => writer.writeAll("array"),
.e_unary => writer.writeAll("unary"),
.e_binary => writer.writeAll("binary"),
.e_boolean => writer.writeAll("boolean"),
.e_boolean, .e_branch_boolean => writer.writeAll("boolean"),
.e_super => writer.writeAll("super"),
.e_null => writer.writeAll("null"),
.e_undefined => writer.writeAll("undefined"),
@@ -1627,14 +1634,7 @@ pub const Tag = enum {
}
}
pub fn isBoolean(self: Tag) bool {
switch (self) {
.e_boolean => {
return true;
},
else => {
return false;
},
}
return self == .e_boolean or self == .e_branch_boolean;
}
pub fn isSuper(self: Tag) bool {
switch (self) {
@@ -1921,7 +1921,7 @@ pub const Tag = enum {
pub fn isBoolean(a: *const Expr) bool {
return switch (a.data) {
.e_boolean => true,
.e_boolean, .e_branch_boolean => true,
.e_if => |ex| ex.yes.isBoolean() and ex.no.isBoolean(),
.e_unary => |ex| ex.op == .un_not or ex.op == .un_delete,
.e_binary => |ex| switch (ex.op) {
@@ -1978,8 +1978,8 @@ pub fn maybeSimplifyNot(expr: *const Expr, allocator: std.mem.Allocator) ?Expr {
.e_null, .e_undefined => {
return expr.at(E.Boolean, E.Boolean{ .value = true }, allocator);
},
.e_boolean => |b| {
return expr.at(E.Boolean, E.Boolean{ .value = b.value }, allocator);
.e_boolean, .e_branch_boolean => |b| {
return expr.at(E.Boolean, E.Boolean{ .value = !b.value }, allocator);
},
.e_number => |n| {
return expr.at(E.Boolean, E.Boolean{ .value = (n.value == 0 or std.math.isNan(n.value)) }, allocator);
@@ -2049,7 +2049,7 @@ pub fn toStringExprWithoutSideEffects(expr: *const Expr, allocator: std.mem.Allo
.e_null => "null",
.e_string => return expr.*,
.e_undefined => "undefined",
.e_boolean => |data| if (data.value) "true" else "false",
.e_boolean, .e_branch_boolean => |data| if (data.value) "true" else "false",
.e_big_int => |bigint| bigint.value,
.e_number => |num| if (num.toString(allocator)) |str|
str
@@ -2151,6 +2151,7 @@ pub const Data = union(Tag) {
e_commonjs_export_identifier: E.CommonJSExportIdentifier,
e_boolean: E.Boolean,
e_branch_boolean: E.Boolean,
e_number: E.Number,
e_big_int: *E.BigInt,
e_string: *E.String,
@@ -2589,7 +2590,7 @@ pub const Data = union(Tag) {
const symbol = e.ref.getSymbol(symbol_table);
hasher.update(symbol.original_name);
},
inline .e_boolean, .e_number => |e| {
inline .e_boolean, .e_branch_boolean, .e_number => |e| {
writeAnyToHasher(hasher, e.value);
},
inline .e_big_int, .e_reg_exp => |e| {
@@ -2643,6 +2644,7 @@ pub const Data = union(Tag) {
return switch (this) {
.e_number,
.e_boolean,
.e_branch_boolean,
.e_null,
.e_undefined,
.e_inlined_enum,
@@ -2671,6 +2673,7 @@ pub const Data = union(Tag) {
.e_number,
.e_boolean,
.e_branch_boolean,
.e_null,
.e_undefined,
// .e_reg_exp,
@@ -2696,7 +2699,7 @@ pub const Data = union(Tag) {
// rope strings can throw when toString is called.
.e_string => |str| str.next == null,
.e_number, .e_boolean, .e_undefined, .e_null => true,
.e_number, .e_boolean, .e_branch_boolean, .e_undefined, .e_null => true,
// BigInt is deliberately excluded as a large enough BigInt could throw an out of memory error.
//
@@ -2707,7 +2710,7 @@ pub const Data = union(Tag) {
pub fn knownPrimitive(data: Expr.Data) PrimitiveType {
return switch (data) {
.e_big_int => .bigint,
.e_boolean => .boolean,
.e_boolean, .e_branch_boolean => .boolean,
.e_null => .null,
.e_number => .number,
.e_string => .string,
@@ -2843,7 +2846,7 @@ pub const Data = union(Tag) {
// +'1' => 1
return stringToEquivalentNumberValue(str.slice8());
},
.e_boolean => @as(f64, if (data.e_boolean.value) 1.0 else 0.0),
.e_boolean, .e_branch_boolean => |b| @as(f64, if (b.value) 1.0 else 0.0),
.e_number => data.e_number.value,
.e_inlined_enum => |inlined| switch (inlined.value.data) {
.e_number => |num| num.value,
@@ -2862,7 +2865,7 @@ pub const Data = union(Tag) {
pub fn toFiniteNumber(data: Expr.Data) ?f64 {
return switch (data) {
.e_boolean => @as(f64, if (data.e_boolean.value) 1.0 else 0.0),
.e_boolean, .e_branch_boolean => |b| @as(f64, if (b.value) 1.0 else 0.0),
.e_number => if (std.math.isFinite(data.e_number.value))
data.e_number.value
else
@@ -2953,12 +2956,12 @@ pub const Data = union(Tag) {
.ok = ok,
};
},
.e_boolean => |l| {
.e_boolean, .e_branch_boolean => |l| {
switch (right) {
.e_boolean => {
.e_boolean, .e_branch_boolean => |r| {
return .{
.ok = true,
.equal = l.value == right.e_boolean.value,
.equal = l.value == r.value,
};
},
.e_number => |num| {
@@ -2996,7 +2999,7 @@ pub const Data = union(Tag) {
.equal = l.value == r.value.data.e_number.value,
};
},
.e_boolean => |r| {
.e_boolean, .e_branch_boolean => |r| {
if (comptime kind == .loose) {
return .{
.ok = true,
@@ -3111,7 +3114,7 @@ pub const Data = union(Tag) {
.e_string => |e| e.toJS(allocator, globalObject),
.e_null => jsc.JSValue.null,
.e_undefined => .js_undefined,
.e_boolean => |boolean| if (boolean.value)
.e_boolean, .e_branch_boolean => |boolean| if (boolean.value)
.true
else
.false,

View File

@@ -25,7 +25,7 @@ pub fn scan(
const record: *ImportRecord = &p.import_records.items[st.import_record_index];
if (record.path.isMacro()) {
record.is_unused = true;
record.flags.is_unused = true;
record.path.is_disabled = true;
continue;
}
@@ -190,8 +190,8 @@ pub fn scan(
{
// internal imports are presumed to be always used
// require statements cannot be stripped
if (!record.is_internal and !record.was_originally_require) {
record.is_unused = true;
if (!record.flags.is_internal and !record.flags.was_originally_require) {
record.flags.is_unused = true;
continue;
}
}
@@ -204,7 +204,7 @@ pub fn scan(
st.star_name_loc = null;
}
record.contains_default_alias = record.contains_default_alias or st.default_name != null;
record.flags.contains_default_alias = record.flags.contains_default_alias or st.default_name != null;
const existing_items: ImportItemForNamespaceMap = p.import_items_for_namespace.get(namespace_ref) orelse
ImportItemForNamespaceMap.init(allocator);
@@ -265,7 +265,7 @@ pub fn scan(
) catch |err| bun.handleOom(err);
if (st.star_name_loc) |loc| {
record.contains_import_star = true;
record.flags.contains_import_star = true;
p.named_imports.putAssumeCapacity(
namespace_ref,
js_ast.NamedImport{
@@ -279,7 +279,7 @@ pub fn scan(
}
if (st.default_name) |default| {
record.contains_default_alias = true;
record.flags.contains_default_alias = true;
p.named_imports.putAssumeCapacity(
default.ref.?,
.{
@@ -313,7 +313,7 @@ pub fn scan(
// We do not know at this stage whether or not the import statement is bundled
// This keeps track of the `namespace_alias` incase, at printing time, we determine that we should print it with the namespace
for (st.items) |item| {
record.contains_default_alias = record.contains_default_alias or strings.eqlComptime(item.alias, "default");
record.flags.contains_default_alias = record.flags.contains_default_alias or strings.eqlComptime(item.alias, "default");
const name: LocRef = item.name;
const name_ref = name.ref.?;
@@ -327,7 +327,7 @@ pub fn scan(
// Make sure the printer prints this as a property access
var symbol: *Symbol = &p.symbols.items[name_ref.innerIndex()];
if (record.contains_import_star or st.star_name_loc != null)
if (record.flags.contains_import_star or st.star_name_loc != null)
symbol.namespace_alias = G.NamespaceAlias{
.namespace_ref = namespace_ref,
.alias = item.alias,
@@ -336,7 +336,7 @@ pub fn scan(
};
}
if (record.was_originally_require) {
if (record.flags.was_originally_require) {
var symbol = &p.symbols.items[namespace_ref.innerIndex()];
symbol.namespace_alias = G.NamespaceAlias{
.namespace_ref = namespace_ref,
@@ -349,12 +349,12 @@ pub fn scan(
try p.import_records_for_current_part.append(allocator, st.import_record_index);
record.contains_import_star = record.contains_import_star or st.star_name_loc != null;
record.contains_default_alias = record.contains_default_alias or st.default_name != null;
record.flags.contains_import_star = record.flags.contains_import_star or st.star_name_loc != null;
record.flags.contains_default_alias = record.flags.contains_default_alias or st.default_name != null;
for (st.items) |*item| {
record.contains_default_alias = record.contains_default_alias or strings.eqlComptime(item.alias, "default");
record.contains_es_module_alias = record.contains_es_module_alias or strings.eqlComptime(item.alias, "__esModule");
record.flags.contains_default_alias = record.flags.contains_default_alias or strings.eqlComptime(item.alias, "default");
record.flags.contains_es_module_alias = record.flags.contains_es_module_alias or strings.eqlComptime(item.alias, "__esModule");
}
},
@@ -456,7 +456,7 @@ pub fn scan(
});
try p.recordExport(alias.loc, alias.original_name, st.namespace_ref);
var record = &p.import_records.items[st.import_record_index];
record.contains_import_star = true;
record.flags.contains_import_star = true;
} else {
// "export * from 'path'"
try p.export_star_import_records.append(allocator, st.import_record_index);
@@ -482,9 +482,9 @@ pub fn scan(
var record = &p.import_records.items[st.import_record_index];
if (strings.eqlComptime(item.original_name, "default")) {
record.contains_default_alias = true;
record.flags.contains_default_alias = true;
} else if (strings.eqlComptime(item.original_name, "__esModule")) {
record.contains_es_module_alias = true;
record.flags.contains_es_module_alias = true;
}
}
},

View File

@@ -480,7 +480,7 @@ pub const Runner = struct {
this.macro.vm.waitForPromise(promise);
const promise_result = promise.result(this.macro.vm.jsc_vm);
const rejected = promise.status(this.macro.vm.jsc_vm) == .rejected;
const rejected = promise.status() == .rejected;
if (promise_result.isUndefined() and this.is_top_level) {
this.is_top_level = false;

View File

@@ -185,6 +185,13 @@ pub fn NewParser_(
/// it to the symbol so the code generated `e_import_identifier`'s
bun_app_namespace_ref: Ref = Ref.None,
/// Used to track the `feature` function from `import { feature } from "bun:bundle"`.
/// When visiting e_call, if the target ref matches this, we replace the call with
/// a boolean based on whether the feature flag is enabled.
bundler_feature_flag_ref: Ref = Ref.None,
/// Set to true when visiting an if/ternary condition. feature() calls are only valid in this context.
in_branch_condition: bool = false,
scopes_in_order_visitor_index: usize = 0,
has_classic_runtime_warned: bool = false,
macro_call_count: MacroCallCountType = 0,
@@ -511,7 +518,7 @@ pub fn NewParser_(
p.import_records.items[import_record_index].tag = tag;
}
p.import_records.items[import_record_index].handles_import_errors = (state.is_await_target and p.fn_or_arrow_data_visit.try_body_count != 0) or state.is_then_catch_target;
p.import_records.items[import_record_index].flags.handles_import_errors = (state.is_await_target and p.fn_or_arrow_data_visit.try_body_count != 0) or state.is_then_catch_target;
p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable;
return p.newExpr(E.Import{
@@ -566,7 +573,7 @@ pub fn NewParser_(
}
const import_record_index = p.addImportRecord(.require_resolve, arg.loc, arg.data.e_string.string(p.allocator) catch unreachable);
p.import_records.items[import_record_index].handles_import_errors = p.fn_or_arrow_data_visit.try_body_count != 0;
p.import_records.items[import_record_index].flags.handles_import_errors = p.fn_or_arrow_data_visit.try_body_count != 0;
p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable;
return p.newExpr(
@@ -618,7 +625,7 @@ pub fn NewParser_(
if (should_unwrap_require) {
const import_record_index = p.addImportRecordByRangeAndPath(.stmt, p.source.rangeOfString(arg.loc), path);
p.import_records.items[import_record_index].handles_import_errors = handles_import_errors;
p.import_records.items[import_record_index].flags.handles_import_errors = handles_import_errors;
// Note that this symbol may be completely removed later.
var path_name = fs.PathName.init(path.text);
@@ -651,7 +658,7 @@ pub fn NewParser_(
}
const import_record_index = p.addImportRecordByRangeAndPath(.require, p.source.rangeOfString(arg.loc), path);
p.import_records.items[import_record_index].handles_import_errors = handles_import_errors;
p.import_records.items[import_record_index].flags.handles_import_errors = handles_import_errors;
p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable;
return p.newExpr(E.RequireString{ .import_record_index = import_record_index }, arg.loc);
@@ -1325,7 +1332,7 @@ pub fn NewParser_(
var import_record: *ImportRecord = &p.import_records.items[import_record_i];
if (comptime is_internal)
import_record.path.namespace = "runtime";
import_record.is_internal = is_internal;
import_record.flags.is_internal = is_internal;
const import_path_identifier = try import_record.path.name.nonUniqueNameString(allocator);
var namespace_identifier = try allocator.alloc(u8, import_path_identifier.len + prefix.len);
const clause_items = try allocator.alloc(js_ast.ClauseItem, imports.len);
@@ -2621,7 +2628,7 @@ pub fn NewParser_(
if (is_macro) {
const id = p.addImportRecord(.stmt, path.loc, path.text);
p.import_records.items[id].path.namespace = js_ast.Macro.namespace;
p.import_records.items[id].is_unused = true;
p.import_records.items[id].flags.is_unused = true;
if (stmt.default_name) |name_loc| {
const name = p.loadNameFromRef(name_loc.ref.?);
@@ -2653,13 +2660,41 @@ pub fn NewParser_(
return p.s(S.Empty{}, loc);
}
// Handle `import { feature } from "bun:bundle"` - this is a special import
// that provides static feature flag checking at bundle time.
// We handle it here at parse time (similar to macros) rather than at visit time.
if (strings.eqlComptime(path.text, "bun:bundle")) {
// Look for the "feature" import and validate specifiers
for (stmt.items) |*item| {
// In ClauseItem from parseImportClause:
// - alias is the name from the source module ("feature")
// - original_name is the local binding name
// - name.ref is the ref for the local binding
if (strings.eqlComptime(item.alias, "feature")) {
// Check for duplicate imports of feature
if (p.bundler_feature_flag_ref.isValid()) {
try p.log.addError(p.source, item.alias_loc, "`feature` from \"bun:bundle\" may only be imported once");
continue;
}
// Declare the symbol and store the ref
const name = p.loadNameFromRef(item.name.ref.?);
const ref = try p.declareSymbol(.other, item.name.loc, name);
p.bundler_feature_flag_ref = ref;
} else {
try p.log.addErrorFmt(p.source, item.alias_loc, p.allocator, "\"bun:bundle\" has no export named \"{s}\"", .{item.alias});
}
}
// Return empty statement - the import is completely removed
return p.s(S.Empty{}, loc);
}
const macro_remap = if (comptime allow_macros)
p.options.macro_context.getRemap(path.text)
else
null;
stmt.import_record_index = p.addImportRecord(.stmt, path.loc, path.text);
p.import_records.items[stmt.import_record_index].was_originally_bare_import = was_originally_bare_import;
p.import_records.items[stmt.import_record_index].flags.was_originally_bare_import = was_originally_bare_import;
if (stmt.star_name_loc) |star| {
const name = p.loadNameFromRef(stmt.namespace_ref);
@@ -2720,9 +2755,9 @@ pub fn NewParser_(
});
p.import_records.items[new_import_id].path.namespace = js_ast.Macro.namespace;
p.import_records.items[new_import_id].is_unused = true;
p.import_records.items[new_import_id].flags.is_unused = true;
if (comptime only_scan_imports_and_do_not_visit) {
p.import_records.items[new_import_id].is_internal = true;
p.import_records.items[new_import_id].flags.is_internal = true;
p.import_records.items[new_import_id].path.is_disabled = true;
}
stmt.default_name = null;
@@ -2781,9 +2816,9 @@ pub fn NewParser_(
});
p.import_records.items[new_import_id].path.namespace = js_ast.Macro.namespace;
p.import_records.items[new_import_id].is_unused = true;
p.import_records.items[new_import_id].flags.is_unused = true;
if (comptime only_scan_imports_and_do_not_visit) {
p.import_records.items[new_import_id].is_internal = true;
p.import_records.items[new_import_id].flags.is_internal = true;
p.import_records.items[new_import_id].path.is_disabled = true;
}
remap_count += 1;
@@ -2809,11 +2844,11 @@ pub fn NewParser_(
if (remap_count > 0 and stmt.items.len == 0 and stmt.default_name == null) {
p.import_records.items[stmt.import_record_index].path.namespace = js_ast.Macro.namespace;
p.import_records.items[stmt.import_record_index].is_unused = true;
p.import_records.items[stmt.import_record_index].flags.is_unused = true;
if (comptime only_scan_imports_and_do_not_visit) {
p.import_records.items[stmt.import_record_index].path.is_disabled = true;
p.import_records.items[stmt.import_record_index].is_internal = true;
p.import_records.items[stmt.import_record_index].flags.is_internal = true;
}
return p.s(S.Empty{}, loc);
@@ -3932,6 +3967,7 @@ pub fn NewParser_(
.e_undefined,
.e_missing,
.e_boolean,
.e_branch_boolean,
.e_number,
.e_big_int,
.e_string,

View File

@@ -134,17 +134,17 @@ pub const Parser = struct {
// - import 'foo';
// - import("foo")
// - require("foo")
import_record.is_unused = import_record.is_unused or
import_record.flags.is_unused = import_record.flags.is_unused or
(import_record.kind == .stmt and
!import_record.was_originally_bare_import and
!import_record.calls_runtime_re_export_fn);
!import_record.flags.was_originally_bare_import and
!import_record.flags.calls_runtime_re_export_fn);
}
var iter = scan_pass.used_symbols.iterator();
while (iter.next()) |entry| {
const val = entry.value_ptr;
if (val.used) {
scan_pass.import_records.items[val.import_record_index].is_unused = false;
scan_pass.import_records.items[val.import_record_index].flags.is_unused = false;
}
}
}
@@ -1023,7 +1023,7 @@ pub const Parser = struct {
const import_record: ?*const ImportRecord = brk: {
for (p.import_records.items) |*import_record| {
if (import_record.is_internal or import_record.is_unused) continue;
if (import_record.flags.is_internal or import_record.flags.is_unused) continue;
if (import_record.kind == .stmt) break :brk import_record;
}
@@ -1094,7 +1094,7 @@ pub const Parser = struct {
// If they use an import statement, we say it's ESM because that's not allowed in CommonJS files.
const uses_any_import_statements = brk: {
for (p.import_records.items) |*import_record| {
if (import_record.is_internal or import_record.is_unused) continue;
if (import_record.flags.is_internal or import_record.flags.is_unused) continue;
if (import_record.kind == .stmt) break :brk true;
}

View File

@@ -72,6 +72,7 @@ pub const SideEffects = enum(u1) {
.e_undefined,
.e_string,
.e_boolean,
.e_branch_boolean,
.e_number,
.e_big_int,
.e_inlined_enum,
@@ -88,6 +89,7 @@ pub const SideEffects = enum(u1) {
.e_undefined,
.e_missing,
.e_boolean,
.e_branch_boolean,
.e_number,
.e_big_int,
.e_string,
@@ -277,7 +279,7 @@ pub const SideEffects = enum(u1) {
}
}
properties_slice[end] = prop_;
properties_slice[end] = prop;
end += 1;
}
@@ -545,6 +547,7 @@ pub const SideEffects = enum(u1) {
.e_null,
.e_undefined,
.e_boolean,
.e_branch_boolean,
.e_number,
.e_big_int,
.e_string,
@@ -651,7 +654,7 @@ pub const SideEffects = enum(u1) {
}
switch (exp) {
// Never null or undefined
.e_boolean, .e_number, .e_string, .e_reg_exp, .e_function, .e_arrow, .e_big_int => {
.e_boolean, .e_branch_boolean, .e_number, .e_string, .e_reg_exp, .e_function, .e_arrow, .e_big_int => {
return Result{ .value = false, .side_effects = .no_side_effects, .ok = true };
},
@@ -770,7 +773,7 @@ pub const SideEffects = enum(u1) {
.e_null, .e_undefined => {
return Result{ .ok = true, .value = false, .side_effects = .no_side_effects };
},
.e_boolean => |e| {
.e_boolean, .e_branch_boolean => |e| {
return Result{ .ok = true, .value = e.value, .side_effects = .no_side_effects };
},
.e_number => |e| {

View File

@@ -327,7 +327,7 @@ pub fn ParseStmt(
if (comptime track_symbol_usage_during_parse_pass) {
// In the scan pass, we need _some_ way of knowing *not* to mark as unused
p.import_records.items[import_record_index].calls_runtime_re_export_fn = true;
p.import_records.items[import_record_index].flags.calls_runtime_re_export_fn = true;
}
try p.lexer.expectOrInsertSemicolon();
@@ -381,7 +381,7 @@ pub fn ParseStmt(
if (comptime track_symbol_usage_during_parse_pass) {
// In the scan pass, we need _some_ way of knowing *not* to mark as unused
p.import_records.items[import_record_index].calls_runtime_re_export_fn = true;
p.import_records.items[import_record_index].flags.calls_runtime_re_export_fn = true;
}
p.current_scope.is_after_const_local_prefix = true;
p.has_es_module_syntax = true;

View File

@@ -923,7 +923,10 @@ pub fn VisitExpr(
const e_ = expr.data.e_if;
const is_call_target = @as(Expr.Data, p.call_target) == .e_if and expr.data.e_if == p.call_target.e_if;
const prev_in_branch = p.in_branch_condition;
p.in_branch_condition = true;
e_.test_ = p.visitExpr(e_.test_);
p.in_branch_condition = prev_in_branch;
e_.test_ = SideEffects.simplifyBoolean(p, e_.test_);
@@ -1277,6 +1280,15 @@ pub fn VisitExpr(
}
}
// Handle `feature("FLAG_NAME")` calls from `import { feature } from "bun:bundle"`
// Check if the bundler_feature_flag_ref is set before calling the function
// to avoid stack memory usage from copying values back and forth.
if (p.bundler_feature_flag_ref.isValid()) {
if (maybeReplaceBundlerFeatureCall(p, e_, expr.loc)) |result| {
return result;
}
}
if (e_.target.data == .e_require_call_target) {
e_.can_be_unwrapped_if_unused = .never;
@@ -1631,6 +1643,66 @@ pub fn VisitExpr(
return expr;
}
/// Handles `feature("FLAG_NAME")` calls from `import { feature } from "bun:bundle"`.
/// This enables statically analyzable dead-code elimination through feature gating.
///
/// When a feature flag is enabled via `--feature=FLAG_NAME`, `feature("FLAG_NAME")`
/// is replaced with `true`, otherwise it's replaced with `false`. This allows
/// bundlers to eliminate dead code branches at build time.
///
/// Returns the replacement expression if this is a feature() call, or null otherwise.
/// Note: Caller must check `p.bundler_feature_flag_ref.isValid()` before calling.
fn maybeReplaceBundlerFeatureCall(p: *P, e_: *E.Call, loc: logger.Loc) ?Expr {
// Check if the target is the `feature` function from "bun:bundle"
// It could be e_identifier (for unbound) or e_import_identifier (for imports)
const target_ref: ?Ref = switch (e_.target.data) {
.e_identifier => |ident| ident.ref,
.e_import_identifier => |ident| ident.ref,
else => null,
};
if (target_ref == null or !target_ref.?.eql(p.bundler_feature_flag_ref)) {
return null;
}
// If control flow is dead, just return false without validation errors
if (p.is_control_flow_dead) {
return p.newExpr(E.Boolean{ .value = false }, loc);
}
// Validate: exactly one argument required
if (e_.args.len != 1) {
p.log.addError(p.source, loc, "feature() requires exactly one string argument") catch unreachable;
return p.newExpr(E.Boolean{ .value = false }, loc);
}
const arg = e_.args.slice()[0];
// Validate: argument must be a string literal
if (arg.data != .e_string) {
p.log.addError(p.source, arg.loc, "feature() argument must be a string literal") catch unreachable;
return p.newExpr(E.Boolean{ .value = false }, loc);
}
// Check if the feature flag is enabled
// Use the underlying string data directly without allocation.
// Feature flag names should be ASCII identifiers, so UTF-16 is unexpected.
const flag_string = arg.data.e_string;
if (flag_string.is_utf16) {
p.log.addError(p.source, arg.loc, "feature() flag name must be an ASCII string") catch unreachable;
return p.newExpr(E.Boolean{ .value = false }, loc);
}
// feature() can only be used directly in an if statement or ternary condition
if (!p.in_branch_condition) {
p.log.addError(p.source, loc, "feature() from \"bun:bundle\" can only be used directly in an if statement or ternary condition") catch unreachable;
return p.newExpr(E.Boolean{ .value = false }, loc);
}
const is_enabled = p.options.features.bundler_feature_flags.map.contains(flag_string.data);
return .{ .data = .{ .e_branch_boolean = .{ .value = is_enabled } }, .loc = loc };
}
};
};
}

View File

@@ -1000,7 +1000,10 @@ pub fn VisitStmt(
try stmts.append(stmt.*);
}
pub fn s_if(noalias p: *P, noalias stmts: *ListManaged(Stmt), noalias stmt: *Stmt, noalias data: *S.If) !void {
const prev_in_branch = p.in_branch_condition;
p.in_branch_condition = true;
data.test_ = p.visitExpr(data.test_);
p.in_branch_condition = prev_in_branch;
if (p.options.features.minify_syntax) {
data.test_ = SideEffects.simplifyBoolean(p, data.test_);

View File

@@ -150,6 +150,7 @@ pub const FilePoll = struct {
const StaticPipeWriter = Subprocess.StaticPipeWriter.Poll;
const ShellStaticPipeWriter = bun.shell.ShellSubprocess.StaticPipeWriter.Poll;
const FileSink = jsc.WebCore.FileSink.Poll;
const TerminalPoll = bun.api.Terminal.Poll;
const DNSResolver = bun.api.dns.Resolver;
const GetAddrInfoRequest = bun.api.dns.GetAddrInfoRequest;
const Request = bun.api.dns.internal.Request;
@@ -181,6 +182,7 @@ pub const FilePoll = struct {
// LifecycleScriptSubprocessOutputReader,
Process,
ShellBufferedWriter, // i do not know why, but this has to be here otherwise compiler will complain about dependency loop
TerminalPoll,
});
pub const AllocatorType = enum {
@@ -414,6 +416,12 @@ pub const FilePoll = struct {
Request.MacAsyncDNS.onMachportChange(loader);
},
@field(Owner.Tag, @typeName(TerminalPoll)) => {
log("onUpdate " ++ kqueue_or_epoll ++ " (fd: {f}) Terminal", .{poll.fd});
var handler: *TerminalPoll = ptr.as(TerminalPoll);
handler.onPoll(size_or_offset, poll.flags.contains(.hup));
},
else => {
const possible_name = Owner.typeNameFromTag(@intFromEnum(ptr.tag()));
log("onUpdate " ++ kqueue_or_epoll ++ " (fd: {f}) disconnected? (maybe: {s})", .{ poll.fd, possible_name orelse "<unknown>" });

View File

@@ -36,7 +36,7 @@ bakeModuleLoaderImportModule(JSC::JSGlobalObject* global,
if (!keyString) {
auto promise = JSC::JSInternalPromise::create(vm, global->internalPromiseStructure());
promise->reject(global, JSC::createError(global, "import() requires a string"_s));
promise->reject(vm, global, JSC::createError(global, "import() requires a string"_s));
return promise;
}

View File

@@ -3626,7 +3626,7 @@ pub fn emitMemoryVisualizerMessageTimer(timer: *EventLoopTimer, _: *const bun.ti
assert(dev.magic == .valid);
dev.emitMemoryVisualizerMessage();
timer.state = .FIRED;
dev.vm.timer.update(timer, &bun.timespec.msFromNow(1000));
dev.vm.timer.update(timer, &bun.timespec.msFromNow(.allow_mocked_time, 1000));
}
pub fn emitMemoryVisualizerMessageIfNeeded(dev: *DevServer) void {

View File

@@ -87,7 +87,7 @@ pub fn onMessage(s: *HmrSocket, ws: AnyWebSocket, msg: []const u8, opcode: uws.O
bun.assert(s.dev.memory_visualizer_timer.state != .ACTIVE);
s.dev.vm.timer.update(
&s.dev.memory_visualizer_timer,
&bun.timespec.msFromNow(1000),
&bun.timespec.msFromNow(.allow_mocked_time, 1000),
);
}
},

View File

@@ -925,7 +925,7 @@ pub fn IncrementalGraph(comptime side: bake.Side) type {
// When an import record is duplicated, it gets marked unused.
// This happens in `ConvertESMExportsForHmr.deduplicatedImport`
// There is still a case where deduplication must happen.
if (import_record.is_unused) return .stop;
if (import_record.flags.is_unused) return .stop;
if (import_record.source_index.isRuntime()) return .stop;
const key = import_record.path.keyForIncrementalGraph();
@@ -1044,7 +1044,7 @@ pub fn IncrementalGraph(comptime side: bake.Side) type {
// When an import record is duplicated, it gets marked unused.
// This happens in `ConvertESMExportsForHmr.deduplicatedImport`
// There is still a case where deduplication must happen.
if (import_record.is_unused) continue;
if (import_record.flags.is_unused) continue;
if (!import_record.source_index.isRuntime()) try_index_record: {
// TODO: move this block into a function

Some files were not shown because too many files have changed in this diff Show More