Commit Graph

1054 Commits

Author SHA1 Message Date
Dylan Conway
dbacff1409 experiment 2026-02-01 15:44:01 -08:00
Ciro Spaciari
a14a89ca95 fix(proxy): respect NO_PROXY for explicit proxy options in fetch and ws (#26608)
### What does this PR do?

Extract NO_PROXY checking logic from getHttpProxyFor into a reusable
isNoProxy method on the env Loader. This allows both fetch() and
WebSocket to check NO_PROXY even when a proxy is explicitly provided via
the proxy option (not just via http_proxy env var).

Changes:
- env_loader.zig: Extract isNoProxy() from getHttpProxyFor()
- FetchTasklet.zig: Check isNoProxy() before using explicit proxy
- WebSocket.cpp: Check Bun__isNoProxy() before using explicit proxy
- virtual_machine_exports.zig: Export Bun__isNoProxy for C++ access
- Add NO_PROXY tests for both fetch and WebSocket proxy paths

### How did you verify your code works?
Tests

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-01-30 16:20:45 -08:00
Alistair Smith
71ce550cfa esm bytecode (#26402)
### What does this PR do?

### How did you verify your code works?

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-01-30 01:38:45 -08:00
robobun
8f61adf494 Harden chunked encoding parser (#26594)
## Summary
- Improve handling of fragmented chunk data in the HTTP parser
- Add test coverage for edge cases

## Test plan
- [x] New tests pass
- [x] Existing tests pass

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

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 01:18:39 -08:00
Jarred Sumner
1bfe5c6b37 feat(md): Zig markdown parser with Bun.markdown API (#26440)
## Summary

- Port md4c (CommonMark-compliant markdown parser) from C to Zig under
`src/md/`
- Three output modes:
  - `Bun.markdown.html(input, options?)` — render to HTML string
- `Bun.markdown.render(input, callbacks?)` — render with custom
callbacks for each element
- `Bun.markdown.react(input, options?)` — render to a React Fragment
element, directly usable as a component return value
- React element creation uses a cached JSC Structure with
`putDirectOffset` for fast allocation
- Component overrides in `react()`: pass tag names as options keys to
replace default HTML elements with custom components
- GFM extensions: tables, strikethrough, task lists, permissive
autolinks, disallowed raw HTML tag filter
- Wire up `.md` as a bundler loader (via explicit `{ type: "md" }`)

## JavaScript API

### `Bun.markdown.html(input, options?)`

Renders markdown to an HTML string:

```js
const html = Bun.markdown.html("# Hello **world**");
// "<h1>Hello <strong>world</strong></h1>\n"

Bun.markdown.html("## Hello", { headingIds: true });
// '<h2 id="hello">Hello</h2>\n'
```

### `Bun.markdown.render(input, callbacks?)`

Renders markdown with custom JavaScript callbacks for each element. Each
callback receives children as a string and optional metadata, and
returns a string:

```js
// Custom HTML with classes
const html = Bun.markdown.render("# Title\n\nHello **world**", {
  heading: (children, { level }) => `<h${level} class="title">${children}</h${level}>`,
  paragraph: (children) => `<p>${children}</p>`,
  strong: (children) => `<b>${children}</b>`,
});

// ANSI terminal output
const ansi = Bun.markdown.render("# Hello\n\n**bold**", {
  heading: (children) => `\x1b[1;4m${children}\x1b[0m\n`,
  paragraph: (children) => children + "\n",
  strong: (children) => `\x1b[1m${children}\x1b[22m`,
});

// Strip all formatting
const text = Bun.markdown.render("# Hello **world**", {
  heading: (children) => children,
  paragraph: (children) => children,
  strong: (children) => children,
});
// "Hello world"

// Return null to omit elements
const result = Bun.markdown.render("# Title\n\n![logo](img.png)\n\nHello", {
  image: () => null,
  heading: (children) => children,
  paragraph: (children) => children + "\n",
});
// "Title\nHello\n"
```

Parser options can be included alongside callbacks:

```js
Bun.markdown.render("Visit www.example.com", {
  link: (children, { href }) => `[${children}](${href})`,
  paragraph: (children) => children,
  permissiveAutolinks: true,
});
```

### `Bun.markdown.react(input, options?)`

Returns a React Fragment element — use it directly as a component return
value:

```tsx
// Use as a component
function Markdown({ text }: { text: string }) {
  return Bun.markdown.react(text);
}

// With custom components
function Heading({ children }: { children: React.ReactNode }) {
  return <h1 className="title">{children}</h1>;
}
const element = Bun.markdown.react("# Hello", { h1: Heading });

// Server-side rendering
import { renderToString } from "react-dom/server";
const html = renderToString(Bun.markdown.react("# Hello **world**"));
// "<h1>Hello <strong>world</strong></h1>"
```

#### React 18 and older

By default, `react()` uses `Symbol.for('react.transitional.element')` as
the `$$typeof` symbol, which is what React 19 expects. For React 18 and
older, pass `reactVersion: 18`:

```tsx
const el = Bun.markdown.react("# Hello", { reactVersion: 18 });
```

### Component Overrides

Tag names can be overridden in `react()`:

```tsx
Bun.markdown.react(input, {
  h1: MyHeading,      // block elements
  p: CustomParagraph,
  a: CustomLink,      // inline elements
  img: CustomImage,
  pre: CodeBlock,
  // ... h1-h6, p, blockquote, ul, ol, li, pre, hr, html,
  //     table, thead, tbody, tr, th, td,
  //     em, strong, a, img, code, del, math, u, br
});
```

Boolean values are ignored (not treated as overrides), so parser options
like `{ strikethrough: true }` don't conflict with component overrides.

### Options

```js
Bun.markdown.html(input, {
  tables: true,              // GFM tables (default: true)
  strikethrough: true,       // ~~deleted~~ (default: true)
  tasklists: true,           // - [x] items (default: true)
  headingIds: true,          // Generate id attributes on headings
  autolinkHeadings: true,    // Wrap heading content in <a> tags
  tagFilter: false,          // GFM disallowed HTML tags
  wikiLinks: false,          // [[wiki]] links
  latexMath: false,          // $inline$ and $$display$$
  underline: false,          // __underline__ (instead of <strong>)
  // ... and more
});
```

## Architecture

### Parser (`src/md/`)

The parser is split into focused modules using Zig's delegation pattern:

| Module | Purpose |
|--------|---------|
| `parser.zig` | Core `Parser` struct, state, and re-exported method
delegation |
| `blocks.zig` | Block-level parsing: document processing, line
analysis, block start/end |
| `containers.zig` | Container management: blockquotes, lists, list
items |
| `inlines.zig` | Inline parsing: emphasis, code spans, HTML tags,
entities |
| `links.zig` | Link/image resolution, reference links, autolink
rendering |
| `autolinks.zig` | Permissive autolink detection (www, url, email) |
| `line_analysis.zig` | Line classification: headings, fences, HTML
blocks, tables |
| `ref_defs.zig` | Reference definition parsing and lookup |
| `render_blocks.zig` | Block rendering dispatch (code, HTML, table
blocks) |
| `html_renderer.zig` | HTML renderer implementing `Renderer` VTable |
| `types.zig` | Shared types: `Renderer` VTable, `BlockType`,
`SpanType`, `TextType`, etc. |

### Renderer Abstraction

Parsing is decoupled from output via a `Renderer` VTable interface:

```zig
pub const Renderer = struct {
    ptr: *anyopaque,
    vtable: *const VTable,

    pub const VTable = struct {
        enterBlock: *const fn (...) void,
        leaveBlock: *const fn (...) void,
        enterSpan:  *const fn (...) void,
        leaveSpan:  *const fn (...) void,
        text:       *const fn (...) void,
    };
};
```

Four renderers are implemented:
- **`HtmlRenderer`** (`src/md/html_renderer.zig`) — produces HTML string
output
- **`JsCallbackRenderer`** (`src/bun.js/api/MarkdownObject.zig`) — calls
JS callbacks for each element, accumulates string output
- **`ParseRenderer`** (`src/bun.js/api/MarkdownObject.zig`) — builds
React element AST with `MarkedArgumentBuffer` for GC safety
- **`JSReactElement`** (`src/bun.js/bindings/JSReactElement.cpp`) — C++
fast path for React element creation using cached JSC Structure +
`putDirectOffset`

## Test plan

- [x] 792 spec tests pass (CommonMark, GFM tables, strikethrough,
tasklists, permissive autolinks, GFM tag filter, wiki links, coverage,
regressions)
- [x] 114 API tests pass (`html()`, `render()`, `react()`,
`renderToString` integration, component overrides)
- [x] 58 GFM compatibility tests pass

```
bun bd test test/js/bun/md/md-spec.test.ts       # 792 pass
bun bd test test/js/bun/md/md-render-api.test.ts  # 114 pass
bun bd test test/js/bun/md/gfm-compat.test.ts     # 58 pass
```

🤖 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>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: SUZUKI Sosuke <sosuke@bun.com>
Co-authored-by: robobun <robobun@oven.sh>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Kirill Markelov <kerusha.chubko@gmail.com>
Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>
Co-authored-by: Alistair Smith <hi@alistair.sh>
2026-01-28 20:24:02 -08:00
Dylan Conway
b59c77a6e7 feat: add native JSON5 parser (Bun.JSON5) (#26439)
## Summary

- Adds `Bun.JSON5.parse()` and `Bun.JSON5.stringify()` as built-in APIs
- Adds `.json5` file support in the module resolver and bundler
- Parser uses a scanner/parser split architecture with a labeled switch
pattern (like the YAML parser) — the scanner produces typed tokens, the
parser never touches source bytes directly
- 430+ tests covering the official JSON5 test suite, escape sequences,
numbers, comments, whitespace (including all Unicode whitespace types),
unquoted/reserved-word keys, unicode identifiers, deeply nested
structures, garbage input, error messages, and stringify behavior

<img width="659" height="610" alt="Screenshot 2026-01-25 at 12 19 57 AM"
src="https://github.com/user-attachments/assets/e300125a-f197-4cad-90ed-e867b6232a01"
/>

## Test plan

- [x] `bun bd test test/js/bun/json5/json5.test.ts` — 317 tests
- [x] `bun bd test test/js/bun/json5/json5-test-suite.test.ts` — 113
tests from the official JSON5 test suite
- [x] `bun bd test test/js/bun/resolve/json5/json5.test.js` — .json5
module resolution

closes #3175

🤖 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>
2026-01-26 10:52:35 -08:00
SUZUKI Sosuke
a595fe1cca test: add JSC JIT stress tests from WebKit (#26380)
## Summary

This PR adds 78 stress tests that exercise JSC's JIT compilation tiers.
The tests are ported from WebKit's `JSTests/stress/` and
`JSTests/wasm/stress/` directories, covering all five JIT tiers:

- **FTL** (41 tests): math intrinsics, string ops, regexp, arguments,
exceptions, try-catch, property access, OSR, tail calls
- **DFG** (14 tests): SSA, type conversion, strength reduction,
arguments, internal functions, try-catch, class constructors
- **Allocation sinking / OSR / LICM** (6 tests): varargs, loop
unrolling, LICM
- **Wasm BBQ** (11 tests): fused-if register alloc, OSR with exceptions,
ipint-bbq OSR, tail calls
- **Wasm OMG** (6 tests): recompile, OSR stack slots, tail call clobber

Each test exercises hot loops that trigger JIT tier-up, verifying
correctness of JIT-compiled code.

## Motivation

The goal is to improve stability on platforms that Bun supports but are
not covered by WebKit's EWS (Early Warning System). By running these JIT
stress tests across all CI platforms, we can catch JIT-related
regressions that would otherwise go unnoticed.

## Licensing

Since the test fixtures are derived from WebKit's `JSTests/`, a
`LICENSE` file is included in the test directory with the BSD 2-Clause
license per WebKit's contribution policy.

## Next Steps

As a follow-up, we are considering running these tests specifically
under CPU emulation (without AVX) on baseline builds, to verify that
JIT-generated code does not emit AVX instructions on platforms that do
not support them.

---------

Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-01-26 00:21:34 -08:00
robobun
2c0721eabe docs(test): update static-initializers.test.ts comments to reflect mimalloc v3 (#26454)
## Summary
- Updates the docstring in static-initializers.test.ts which incorrectly
said "exactly one static initializer" - the test actually expects 2
initializers for both arm64 and x64 since the mimalloc v3 update
- Added a comment explaining that mimalloc v3 adds a static initializer
on arm64

## Background
The test file was updated in c63415c9c9 (Mimalloc v3 update) to expect 2
static initializers on arm64 (changed from 1 to 2), but the comments
were not updated to reflect this change. This PR updates the
documentation to accurately describe the expected behavior.

## Test plan
- [x] No functional changes - documentation only

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

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 00:19:45 -08:00
robobun
232e0df956 fix(http): respect port numbers in NO_PROXY environment variable (#26347)
## Summary
- Fix NO_PROXY environment variable to properly respect port numbers
like Node.js and curl do
- Previously `NO_PROXY=localhost:1234` would bypass proxy for all
requests to localhost regardless of port
- Now entries with ports (e.g., `localhost:8080`) do exact host:port
matching, while entries without ports continue to use suffix matching

## Test plan
- Added tests in `test/js/bun/http/proxy.test.js` covering:
  - [x] Bypass proxy when NO_PROXY matches host:port exactly
  - [x] Use proxy when NO_PROXY has different port  
- [x] Bypass proxy when NO_PROXY has host only (no port) - existing
behavior preserved
  - [x] Handle NO_PROXY with multiple entries including port
- Verified existing proxy tests still pass

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

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-01-23 20:21:57 -08:00
Jarred Sumner
e8b2455f11 feat: add Bun.JSONL.parse() for streaming newline-delimited JSON parsing (#26356)
Adds a built-in JSONL parser implemented in C++ using JavaScriptCore's
optimized JSON parser.

## API

### `Bun.JSONL.parse(input)`
Parse a complete JSONL string or `Uint8Array` and return an array of all
parsed values. Throws on invalid input.

```ts
const results = Bun.JSONL.parse('{"a":1}\n{"b":2}\n');
// [{ a: 1 }, { b: 2 }]
```

### `Bun.JSONL.parseChunk(input, start?, end?)`
Parse as many complete values as possible, returning `{ values, read,
done, error }`. Designed for streaming use cases where input arrives
incrementally.

```ts
const result = Bun.JSONL.parseChunk('{"id":1}\n{"id":2}\n{"id":3');
result.values; // [{ id: 1 }, { id: 2 }]
result.read;   // 17
result.done;   // false
result.error;  // null
```

## Implementation Details

- C++ implementation in `BunObject.cpp` using JSC's `streamingJSONParse`
- ASCII fast path: zero-copy `StringView` for pure ASCII input
- Non-ASCII: uses `fromUTF8ReplacingInvalidSequences` with
`utf16_length_from_utf8` size check to prevent overflow
- UTF-8 BOM automatically skipped for `Uint8Array` input
- Pre-built `Structure` with fixed property offsets for fast result
object creation
- `Symbol.toStringTag = "JSONL"` on the namespace object
- `parseChunk` returns errors in `error` property instead of throwing,
preserving partial results
- Comprehensive boundary checks on start/end parameters

## Tests

234 tests covering:
- Complete and partial/streaming input scenarios
- Error handling and recovery
- UTF-8 multi-byte characters and BOM handling
- start/end boundary security (exhaustive combinations, clamping, OOB
prevention)
- 4 GB input rejection (both ASCII and non-ASCII paths)
- Edge cases (empty input, single values, whitespace, special numbers)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-01-23 00:23:25 -08:00
Dylan Conway
c63415c9c9 Mimalloc v3 update (#26379)
### 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 Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-01-23 00:10:48 -08:00
Jarred Sumner
86d4d87beb feat(unicode): migrate grapheme breaking to uucode with GB9c support (#26376)
## Summary

Replace Bun's outdated grapheme breaking implementation with [Ghostty's
approach](https://github.com/ghostty-org/ghostty/tree/main/src/unicode)
using the [uucode](https://github.com/jacobsandlund/uucode) library.
This adds proper **GB9c (Indic Conjunct Break)** support — Devanagari
and other Indic script conjuncts now correctly form single grapheme
clusters.

## Motivation

The previous implementation used a `GraphemeBoundaryClass` enum with
only 12 values and a 2-bit `BreakState` (just `extended_pictographic`
and `regional_indicator` flags). It had no support for Unicode's GB9c
rule, meaning Indic conjunct sequences (consonant + virama + consonant)
were incorrectly split into multiple grapheme clusters.

## Architecture

### Runtime (zero uucode dependency, two table lookups)

```
codepoint → [3-level LUT] → GraphemeBreakNoControl enum (u5, 17 values)
(state, gb1, gb2) → [8KB precomputed array] → (break_result, new_state)
```

The full grapheme break algorithm (GB6-GB13, GB9c, GB11, GB999) runs
only at **comptime** to populate the 8KB decision array. At runtime it's
pure table lookups.

### File Layout

```
src/deps/uucode/              ← Vendored library (MIT, build-time only)
src/unicode/uucode/           ← Build-time integration
  ├── uucode_config.zig       ← What Unicode properties to generate
  ├── grapheme_gen.zig        ← Generator: queries uucode → writes tables
  ├── lut.zig                 ← 3-level lookup table generator
  └── CLAUDE.md               ← Maintenance docs
src/string/immutable/         ← Runtime (no uucode dependency)
  ├── grapheme.zig            ← Grapheme break API + comptime decisions
  ├── grapheme_tables.zig     ← Pre-generated tables (committed, ~91KB source)
  └── visible.zig             ← Width calculation (2 lines changed)
scripts/update-uucode.sh      ← Update vendored uucode + regenerate
```

### Key Types

| Type | Size | Values |
|------|------|--------|
| `GraphemeBreakNoControl` | u5 | 17 (adds
`indic_conjunct_break_{consonant,linker,extend}`, `emoji_modifier_base`,
`zwnj`, etc.) |
| `BreakState` | u3 | 5 (`default`, `regional_indicator`,
`extended_pictographic`, `indic_conjunct_break_consonant`,
`indic_conjunct_break_linker`) |

### Binary Size

The tables store only the `GraphemeBreakNoControl` enum per codepoint
(not width or emoji properties, which visible.zig handles separately):

- stage1: 8192 × u16 = **16KB** (maps high byte → stage2 offset)
- stage2: 27392 × u8 = **27KB** (maps to stage3 index; max value is 16)
- stage3: 17 × u5 = **~17 bytes** (one per enum value)
- Precomputed decisions: **8KB**
- **Total: ~51KB** (vs previous ~70KB+)

## How to Regenerate Tables

```bash
# After updating src/deps/uucode/:
./scripts/update-uucode.sh

# Or manually:
vendor/zig/zig build generate-grapheme-tables
```

Normal builds never run the generator — they use the committed
`grapheme_tables.zig`.

## Testing

```bash
bun bd test test/js/bun/util/stringWidth.test.ts
```

New test cases verify Devanagari conjuncts (GB9c):
- `क्ष` (Ka+Virama+Ssa) → single cluster, width 2
- `क्‍ष` (Ka+Virama+ZWJ+Ssa) → single cluster, width 2
- `क्क्क` (Ka+Virama+Ka+Virama+Ka) → single cluster, width 3

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-01-23 00:07:06 -08:00
Jarred Sumner
37c41137f8 feat(transpiler): add replMode option for REPL transforms (#26246)
## Summary

Add a new `replMode` option to `Bun.Transpiler` that transforms code for
interactive REPL evaluation. This enables building a Node.js-compatible
REPL using `Bun.Transpiler` with `vm.runInContext` for persistent
variable scope.

## Features

- **Expression result capture**: Wraps the last expression in `{
__proto__: null, value: expr }` for result capture
- **IIFE wrappers**: Uses sync/async IIFE wrappers to avoid extra
parentheses around object literals in output
- **Variable hoisting**: Hoists `var`/`let`/`const` declarations outside
the IIFE for persistence across REPL lines
- **const → let conversion**: Converts `const` to `let` for REPL
mutability (allows re-declaration)
- **Function hoisting**: Hoists function declarations with
`this.funcName = funcName` assignment for vm context persistence
- **Class hoisting**: Hoists class declarations with `var` for vm
context persistence
- **Object literal detection**: Auto-detects object literals (code
starting with `{` without trailing `;`) and wraps them in parentheses

## Usage

```typescript
import vm from "node:vm";

const transpiler = new Bun.Transpiler({
  loader: "tsx",
  replMode: true,
});

const context = vm.createContext({ console, Promise });

async function repl(code: string) {
  const transformed = transpiler.transformSync(code);
  const result = await vm.runInContext(transformed, context);
  return result.value;
}

// Example REPL session
await repl("var x = 10");        // 10
await repl("x + 5");             // 15
await repl("class Counter {}"); // [class Counter]
await repl("new Counter()");    // Counter {}
await repl("{a: 1, b: 2}");     // {a: 1, b: 2} (auto-detected object literal)
await repl("await Promise.resolve(42)"); // 42
```

## Transform Examples

| Input | Output |
|-------|--------|
| `42` | `(() => { return { __proto__: null, value: 42 }; })()` |
| `var x = 10` | `var x; (() => { return { __proto__: null, value: x =
10 }; })()` |
| `await fetch()` | `(async () => { return { __proto__: null, value:
await fetch() }; })()` |
| `{a: 1}` | `(() => { return { __proto__: null, value: ({a: 1}) };
})()` |
| `class Foo {}` | `var Foo; (() => { return { __proto__: null, value:
Foo = class Foo {} }; })()` |

## Files Changed

- `src/ast/repl_transforms.zig`: New module containing REPL transform
logic
- `src/ast/P.zig`: Calls REPL transforms after parsing in REPL mode
- `src/bun.js/api/JSTranspiler.zig`: Adds `replMode` config option and
object literal detection
- `src/options.zig`, `src/runtime.zig`, `src/transpiler.zig`: Propagate
`repl_mode` flag
- `packages/bun-types/bun.d.ts`: TypeScript type definitions
- `test/js/bun/transpiler/repl-transform.test.ts`: Test cases

## Testing

```bash
bun bd test test/js/bun/transpiler/repl-transform.test.ts
```

34 tests covering:
- Basic transform output
- REPL session with node:vm
- Variable persistence across lines
- Object literal detection
- Edge cases (empty input, comments, TypeScript, etc.)
- No-transform cases (await inside async functions)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-01-21 13:39:25 -08:00
Jarred Sumner
b6b3626c14 fix(bindings): handle errors from String.toJS() for oversized strings (#26213)
## Summary

- When a string exceeds `WTF::String::MaxLength` (~4GB),
`bun.String.createUninitialized()` returns a `.Dead` tag
- The C++ layer now properly throws `ERR_STRING_TOO_LONG` when this
happens
- Updated `String.toJS()` in Zig to return `bun.JSError!jsc.JSValue`
instead of just `jsc.JSValue`
- Updated ~40 Zig caller files to handle the error with `try`
- C++ callers updated with `RETURN_IF_EXCEPTION` checks

## Test plan

- [x] `bun bd test test/js/node/buffer.test.js` - 449 tests pass
- [x] `bun bd
test/js/node/test/parallel/test-buffer-tostring-rangeerror.js` - passes

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
2026-01-21 13:01:25 -08:00
robobun
039c89442f chore: bump TinyCC to latest upstream (Jan 2026) (#26210)
## What does this PR do?

Updates the oven-sh/tinycc fork to the latest upstream TinyCC,
incorporating 30+ upstream commits while preserving all Bun-specific
patches.

### Upstream changes incorporated
- Build system improvements (c2str.exe handling, cross-compilation)
- macOS 15 compatibility fixes
- libtcc debugging support
- pic/pie support for i386
- arm64 alignment and symbol offset fixes
- RISC-V 64 improvements (pointer difference, assembly, Zicsr extension)
- Relocation updates
- Preprocessor improvements (integer literal overflow handling)
- x86-64 cvts*2si fix
- Various bug fixes

### Bun-specific patches preserved
- Fix crash on macOS x64 (libxcselect.dylib memory handling)
- Implement `-framework FrameworkName` on macOS (for framework header
parsing)
- Add missing #ifdef guards for TCC_IS_NATIVE
- Make `__attribute__(deprecated)` a no-op
- Fix `__has_include` with framework paths
- Support attributes after identifiers in enums
- Fix dlsym behavior on macOS (RTLD_SELF first, then RTLD_DEFAULT)
- Various tccmacho.c improvements

### Related PRs
- TinyCC fork CI is passing:
https://github.com/oven-sh/tinycc/actions/runs/21105489093

## How did you verify your code works?

- [x] TinyCC fork CI passes on all platforms (Linux
x86_64/arm64/armv7/riscv64, macOS x86_64/arm64, Windows i386/x86_64)
- [ ] Bun CI passes

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

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 13:31:21 -08:00
robobun
1344151576 fix(json): prevent stack overflow in JSONC parser on deeply nested input (#26174)
## Summary
- Add stack overflow protection to JSON/JSONC parser to prevent
segmentation faults
- Parser now throws `RangeError: Maximum call stack size exceeded`
instead of crashing
- Fixes DoS vulnerability when parsing deeply nested JSON structures
(~150k+ depth)

## Test plan
- [x] Added regression tests for deeply nested arrays and objects (25k
depth)
- [x] Verified system Bun v1.3.6 crashes with segfault at 150k depth
- [x] Verified fix throws proper error instead of crashing
- [x] All existing JSONC tests pass

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

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 16:23:01 -08:00
SUZUKI Sosuke
44df912d37 Add Bun.wrapAnsi() for text wrapping with ANSI escape code preservation (#26061)
## Summary

Adds `Bun.wrapAnsi()`, a native implementation of the popular
[wrap-ansi](https://www.npmjs.com/package/wrap-ansi) npm package for
wrapping text with ANSI escape codes.

## API

```typescript
Bun.wrapAnsi(string: string, columns: number, options?: WrapAnsiOptions): string

interface WrapAnsiOptions {
  hard?: boolean;              // default: false - Break words longer than columns
  wordWrap?: boolean;          // default: true - Wrap at word boundaries
  trim?: boolean;              // default: true - Trim leading/trailing whitespace
  ambiguousIsNarrow?: boolean; // default: true - Treat ambiguous-width chars as narrow
}
```

## Features

- Wraps text to fit within specified column width
- Preserves ANSI escape codes (SGR colors/styles)
- Supports OSC 8 hyperlinks
- Respects Unicode display widths (full-width characters, emoji)
- Normalizes `\r\n` to `\n`

## Implementation Details

The implementation closes and reopens ANSI codes around line breaks for
robust terminal compatibility. This differs slightly from the npm
package in edge cases but produces visually equivalent output.

### Behavioral Differences from npm wrap-ansi

1. **ANSI code preservation**: Bun always maintains complete ANSI escape
sequences. The npm version can output malformed codes (missing ESC
character) in certain edge cases with `wordWrap: false, trim: false`.

2. **Newline ANSI handling**: Bun closes and reopens ANSI codes around
newlines for robustness. The npm version sometimes keeps them spanning
across newlines. The visual output is equivalent.

## Tests

- 27 custom tests covering basic functionality, ANSI codes, Unicode, and
options
- 23 tests ported from the npm package (MIT licensed, credited in file
header)
- All 50 tests pass

## Benchmark

<!-- Benchmark results will be added -->
```
$ cd /Users/sosuke/code/bun/bench && ../build/release/bun snippets/wrap-ansi.js
clk: ~3.82 GHz
cpu: Apple M4 Max
runtime: bun 1.3.7 (arm64-darwin)

benchmark                    avg (min … max) p75   p99    (min … top 1%)
-------------------------------------------- -------------------------------
Short text (45 chars) - npm    25.81 µs/iter  21.71 µs  █
                      (16.79 µs … 447.38 µs) 110.96 µs ▆█▃▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
Short text (45 chars) - Bun   685.55 ns/iter 667.00 ns    █
                       (459.00 ns … 2.16 ms)   1.42 µs ▁▁▁█▃▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁

summary
  Short text (45 chars) - Bun
   37.65x faster than Short text (45 chars) - npm

-------------------------------------------- -------------------------------
Medium text (810 chars) - npm 568.12 µs/iter 578.00 µs  ▄▅█▆▆▃
                     (525.25 µs … 944.71 µs) 700.75 µs ▄██████▆▅▄▃▃▂▂▂▁▁▁▁▁▁
Medium text (810 chars) - Bun  11.22 µs/iter  11.28 µs                     █
                       (11.04 µs … 11.46 µs)  11.33 µs █▁▁▁██▁█▁▁▁▁█▁█▁▁█▁▁█

summary
  Medium text (810 chars) - Bun
   50.62x faster than Medium text (810 chars) - npm

-------------------------------------------- -------------------------------
Long text (8100 chars) - npm    7.66 ms/iter   7.76 ms     ▂▂▅█   ▅
                         (7.31 ms … 8.10 ms)   8.06 ms ▃▃▄▃█████▇▇███▃▆▆▆▄▁▃
Long text (8100 chars) - Bun  112.14 µs/iter 113.50 µs        █
                     (102.50 µs … 146.04 µs) 124.92 µs ▁▁▁▁▁▁██▇▅█▃▂▂▂▂▁▁▁▁▁

summary
  Long text (8100 chars) - Bun
   68.27x faster than Long text (8100 chars) - npm

-------------------------------------------- -------------------------------
Colored short - npm            28.46 µs/iter  28.56 µs              █
                       (27.90 µs … 29.34 µs)  28.93 µs ▆▁▆▁▁▆▁▁▆▆▆▁▆█▁▁▁▁▁▁▆
Colored short - Bun           861.64 ns/iter 867.54 ns         ▂  ▇█▄▂
                     (839.68 ns … 891.12 ns) 882.04 ns ▃▅▄▅▆▆▇▆██▇████▆▃▅▅▅▂

summary
  Colored short - Bun
   33.03x faster than Colored short - npm

-------------------------------------------- -------------------------------
Colored medium - npm          557.84 µs/iter 562.63 µs      ▂▃█▄
                     (508.08 µs … 911.92 µs) 637.96 µs ▁▁▁▂▄█████▅▂▂▁▁▁▁▁▁▁▁
Colored medium - Bun           14.91 µs/iter  14.94 µs ██  ████ ██ █      ██
                       (14.77 µs … 15.17 µs)  15.06 µs ██▁▁████▁██▁█▁▁▁▁▁▁██

summary
  Colored medium - Bun
   37.41x faster than Colored medium - npm

-------------------------------------------- -------------------------------
Colored long - npm              7.84 ms/iter   7.90 ms       █  ▅
                         (7.53 ms … 8.38 ms)   8.19 ms ▂▂▂▄▃▆██▇██▇▃▂▃▃▃▄▆▂▂
Colored long - Bun            176.73 µs/iter 175.42 µs       █
                       (162.50 µs … 1.37 ms) 204.46 µs ▁▁▂▄▇██▅▂▂▂▁▁▁▁▁▁▁▁▁▁

summary
  Colored long - Bun
   44.37x faster than Colored long - npm

-------------------------------------------- -------------------------------
Hard wrap long - npm            8.05 ms/iter   8.12 ms       ▃ ▇█
                         (7.67 ms … 8.53 ms)   8.50 ms ▄▁▁▁▃▄█████▄▃▂▆▄▃▂▂▂▂
Hard wrap long - Bun          111.85 µs/iter 112.33 µs         ▇█
                     (101.42 µs … 145.42 µs) 123.88 µs ▁▁▁▁▁▁▁████▄▃▂▂▂▁▁▁▁▁

summary
  Hard wrap long - Bun
   72.01x faster than Hard wrap long - npm

-------------------------------------------- -------------------------------
Hard wrap colored - npm         8.82 ms/iter   8.92 ms   ▆ ██
                         (8.55 ms … 9.47 ms)   9.32 ms ▆▆████▆▆▄▆█▄▆▄▄▁▃▁▃▄▃
Hard wrap colored - Bun       174.38 µs/iter 175.54 µs   █ ▂
                     (165.75 µs … 210.25 µs) 199.50 µs ▁▃█▆███▃▂▃▂▂▂▂▂▁▁▁▁▁▁

summary
  Hard wrap colored - Bun
   50.56x faster than Hard wrap colored - npm

-------------------------------------------- -------------------------------
Japanese (full-width) - npm    51.00 µs/iter  52.67 µs    █▂   █▄
                      (40.71 µs … 344.88 µs)  66.13 µs ▁▁▃██▄▃▅██▇▄▃▄▃▂▂▁▁▁▁
Japanese (full-width) - Bun     7.46 µs/iter   7.46 µs       █
                        (6.50 µs … 34.92 µs)   9.38 µs ▁▁▁▁▁██▆▂▁▂▁▁▁▁▁▁▁▁▁▁

summary
  Japanese (full-width) - Bun
   6.84x faster than Japanese (full-width) - npm

-------------------------------------------- -------------------------------
Emoji text - npm              173.63 µs/iter 222.17 µs   █
                     (129.42 µs … 527.25 µs) 249.58 µs ▁▃█▆▃▃▃▁▁▁▁▁▁▁▂▄▆▄▂▂▁
Emoji text - Bun                9.42 µs/iter   9.47 µs           ██
                         (9.32 µs … 9.52 µs)   9.50 µs █▁▁███▁▁█▁██▁▁▁▁██▁▁█

summary
  Emoji text - Bun
   18.44x faster than Emoji text - npm

-------------------------------------------- -------------------------------
Hyperlink (OSC 8) - npm       208.00 µs/iter 254.25 µs   █
                     (169.58 µs … 542.17 µs) 281.00 µs ▁▇█▃▃▂▂▂▁▁▁▁▁▁▁▃▃▅▃▂▁
Hyperlink (OSC 8) - Bun         6.00 µs/iter   6.06 µs      █           ▄
                         (5.88 µs … 6.11 µs)   6.10 µs ▅▅▅▁▅█▅▁▅▁█▁▁▅▅▅▅█▅▁█

summary
  Hyperlink (OSC 8) - Bun
   34.69x faster than Hyperlink (OSC 8) - npm

-------------------------------------------- -------------------------------
No trim long - npm              8.32 ms/iter   8.38 ms  █▇
                        (7.61 ms … 13.67 ms)  11.74 ms ▃████▄▂▃▂▂▃▁▁▁▁▁▁▁▁▁▂
No trim long - Bun             93.92 µs/iter  94.42 µs           █▂
                      (82.75 µs … 162.38 µs) 103.83 µs ▁▁▁▁▁▁▁▁▄███▄▃▂▂▁▁▁▁▁

summary
  No trim long - Bun
   88.62x faster than No trim long - npm
```

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-01-16 16:12:23 -08:00
vadim-anthropic
8da29af1ae feat(node:inspector): implement Profiler API (#25939) 2026-01-16 10:12:28 -08:00
Ciro Spaciari
5d3f37d7ae feat(s3): add Content-Encoding header support for S3 uploads (#26149)
## Summary

Add support for setting the `Content-Encoding` header in S3 `.write()`
and `.writer()` calls, following the same pattern as
`Content-Disposition`.

This allows users to specify the encoding of uploaded content:

```typescript
// With .write()
await s3file.write("compressed data", { contentEncoding: "gzip" });

// With .writer()
const writer = s3file.writer({ contentEncoding: "gzip" });
writer.write("compressed data");
await writer.end();

// With bucket.write()
await bucket.write("key", data, { contentEncoding: "br" });
```

## Implementation

- Extended `SignedHeaders.Key` from 6 bits to 7 bits (64→128
combinations) to accommodate the new header
- Added `content_encoding` to `S3CredentialsWithOptions`, `SignOptions`,
and `SignResult` structs
- Updated `CanonicalRequest` format strings to include
`content-encoding` in AWS SigV4 signing
- Added `getContentEncoding()` method to `Headers` for fetch-based S3
uploads
- Expanded `_headers` array from 9 to 10 elements
- Pass `content_encoding` through all S3 upload paths (upload,
uploadStream, writableStream)

## Test plan

- Added tests for "should be able to set content-encoding" 
- Added tests for "should be able to set content-encoding in writer"
- Tests verify the Content-Encoding header is properly set on uploaded
objects via presigned URL fetch
- All 4 new tests pass with `bun bd test` and fail with
`USE_SYSTEM_BUN=1` (confirming the feature is new)

## Changelog

> Describe your changes in 1-2 sentences. These will be featured on
[bun.sh/blog](https://bun.sh/blog) and Bun's release notes.

Added `contentEncoding` option to S3 `.write()` and `.writer()` methods,
allowing users to set the `Content-Encoding` header when uploading
objects.

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 18:09:33 -08:00
Dylan Conway
959169dfaf feat(archive): change API to constructor-based with S3 support (#25940)
## Summary
- Change Archive API from `Bun.Archive.from(data)` to `new
Bun.Archive(data, options?)`
- Change compression options from `{ gzip: true }` to `{ compress:
"gzip", level?: number }`
- Default to no compression when no options provided
- Use `{ compress: "gzip" }` to enable gzip compression (level 6 by
default)
- Add Archive support for S3 and local file writes via `Bun.write()`

## New API

```typescript
// Create archive - defaults to uncompressed tar
const archive = new Bun.Archive({
  "hello.txt": "Hello, World!",
  "data.json": JSON.stringify({ foo: "bar" }),
});

// Enable gzip compression
const compressed = new Bun.Archive(files, { compress: "gzip" });

// Gzip with custom level (1-12)
const maxCompression = new Bun.Archive(files, { compress: "gzip", level: 12 });

// Write to local file
await Bun.write("archive.tar", archive);           // uncompressed by default
await Bun.write("archive.tar.gz", compressed);     // gzipped

// Write to S3
await client.write("archive.tar.gz", compressed);          // S3Client.write()
await Bun.write("s3://bucket/archive.tar.gz", compressed); // S3 URL
await s3File.write(compressed);                            // s3File.write()

// Get bytes/blob (uses compression setting from constructor)
const bytes = await archive.bytes();
const blob = await archive.blob();
```

## TypeScript Types

```typescript
type ArchiveCompression = "gzip";

type ArchiveOptions = {
  compress?: "gzip";
  level?: number;  // 1-12, default 6 when gzip enabled
};
```

## Test plan
- [x] 98 archive tests pass
- [x] S3 integration tests updated to new API
- [x] TypeScript types updated
- [x] Documentation updated with new examples

🤖 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>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-01-12 14:54:21 -08:00
robobun
7076a49bb1 feat(archive): add TypeScript types, docs, and files() benchmark (#25922)
## Summary

- Add comprehensive TypeScript type definitions for `Bun.Archive` in
`bun.d.ts`
  - `ArchiveInput` and `ArchiveCompression` types
- Full JSDoc documentation with examples for all methods (`from`,
`write`, `extract`, `blob`, `bytes`, `files`)
- Add documentation page at `docs/runtime/archive.mdx`
  - Quickstart examples
  - Creating and extracting archives
  - `files()` method with glob filtering
  - Compression support
  - Full API reference section
- Add Archive to docs sidebar under "Data & Storage"
- Add `files()` benchmark comparing `Bun.Archive.files()` vs node-tar
- Shows ~7x speedup for reading archive contents into memory (59µs vs
434µs)

## Test plan

- [x] TypeScript types compile correctly
- [x] Documentation renders properly in Mintlify format
- [x] Benchmark runs successfully and shows performance comparison
- [x] Verified `files()` method works correctly with both Bun.Archive
and node-tar

🤖 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>
2026-01-09 19:00:19 -08:00
Jarred Sumner
1e0f51ddcc Revert "feat(shell): add $.trace for analyzing shell commands without execution (#25667)"
This reverts commit 6b5de25d8a.
2026-01-09 16:20:18 -08:00
robobun
70fa6af355 feat: add Bun.Archive API for creating and extracting tarballs (#25665)
## Summary

- Adds new `Bun.Archive` API for working with tar archives
- `Bun.Archive.from(data)` - Create archive from object, Blob,
TypedArray, or ArrayBuffer
- `Bun.Archive.write(path, data, compress?)` - Write archive to disk
(async)
- `archive.extract(path)` - Extract to directory, returns
`Promise<number>` (file count)
- `archive.blob(compress?)` - Get archive as Blob (async)
- `archive.bytes(compress?)` - Get archive as Uint8Array (async)

Key implementation details:
- Uses existing libarchive bindings for tarball creation/extraction via
`extractToDisk`
- Uses libdeflate for gzip compression
- Immediate byte copying for GC safety (no JSValue protection, no
`hasPendingActivity`)
- Async operations run on worker pool threads with proper VM reference
handling
- Growing memory buffer via `archive_write_open2` callbacks for
efficient tarball creation

## Test plan

- [x] 65 comprehensive tests covering:
  - Normal operations (create, extract, blob, bytes, write)
  - GC safety (unreferenced archives, mutation isolation)  
  - Error handling (invalid args, corrupted data, I/O errors)
- Edge cases (large files, many files, special characters, path
normalization)
  - Concurrent operations

🤖 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: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-01-09 00:33:35 -08:00
Dylan Conway
596e83c918 fix: correct logic bugs in libarchive, s3 credentials, and postgres bindings (#25905)
## Summary

- **libarchive.zig:110**: Fix self-assignment bug where `this.pos` was
assigned to itself instead of `new_pos`
- **s3/credentials.zig:165,176,199**: Fix impossible range checks -
`and` should be `or` for pageSize, partSize, and retry validation (a
value cannot be both less than MIN and greater than MAX simultaneously)
- **postgres.zig:14**: Fix copy-paste error where createConnection
function was internally named "createQuery"

## Test plan

- [ ] Verify S3 credential validation now properly rejects out-of-range
values for pageSize, partSize, and retry
- [ ] Verify libarchive seek operations work correctly
- [ ] Verify postgres createConnection function has correct internal
name

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
2026-01-08 19:46:06 -08:00
robobun
eeef013365 Add Bun.JSONC API for parsing JSON with comments and trailing commas (#22115)
## Summary

This PR implements a new `Bun.JSONC.parse()` API that allows parsing
JSONC (JSON with Comments) files. It addresses the feature request from
issue #16257 by providing a native API for parsing JSON with comments
and trailing commas.

The implementation follows the same pattern as `Bun.YAML` and
`Bun.TOML`, leveraging the existing `TSConfigParser` which already
handles JSONC parsing internally.

## Features

- **Parse JSON with comments**: Supports both `//` single-line and `/*
*/` block comments
- **Handle trailing commas**: Works with trailing commas in objects and
arrays
- **Full JavaScript object conversion**: Returns native JavaScript
objects/arrays
- **Error handling**: Proper error throwing for invalid JSON
- **TypeScript compatibility**: Works with TypeScript config files and
other JSONC formats

## Usage Example

```javascript
const result = Bun.JSONC.parse(`{
  // This is a comment
  "name": "my-app",
  "version": "1.0.0", // trailing comma is allowed
  "dependencies": {
    "react": "^18.0.0",
  },
}`);
// Returns native JavaScript object
```

## Implementation Details

- Created `JSONCObject.zig` following the same pattern as
`YAMLObject.zig` and `TOMLObject.zig`
- Uses the existing `TSConfigParser` from `json.zig` which already
handles comments and trailing commas
- Added proper C++ bindings and exports following Bun's established
patterns
- Comprehensive test suite covering various JSONC features

## Test Plan

- [x] Basic JSON parsing works
- [x] Single-line comments (`//`) are handled correctly
- [x] Block comments (`/* */`) are handled correctly  
- [x] Trailing commas in objects and arrays work
- [x] Complex nested structures parse correctly
- [x] Error handling for invalid JSON
- [x] Empty objects and arrays work
- [x] Boolean and null values work correctly

🤖 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>
Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2026-01-08 13:27:47 -08:00
Jarred Sumner
bdc95c2dc5 Speed up shell leak test (#25880)
### What does this PR do?
18s -> 3s

### How did you verify your code works?
2026-01-07 21:31:33 -08:00
Jarred Sumner
39e2c22e1a fix(http): disable keep-alive on proxy authentication failure (407) (#25884)
## Summary

- Disable HTTP keep-alive when a proxy returns a 407 (Proxy
Authentication Required) status code
- This prevents subsequent requests from trying to reuse a connection
that the proxy server has closed
- Refactored proxy tests to use `describe.concurrent` and async
`Bun.spawn` patterns

## Test plan

- [x] Added test `simultaneous proxy auth failures should not hang` that
verifies multiple concurrent requests with invalid proxy credentials
complete without hanging
- [x] Existing proxy tests pass

🤖 Generated with [Claude Code](https://claude.ai/code)
2026-01-07 20:49:30 -08:00
robobun
5617b92a5a test: refactor spawnSync to spawn with describe.concurrent (#25849)
## Summary

- Refactor 16 test files to use async `Bun.spawn` instead of
`Bun.spawnSync`
- Wrap tests in `describe.concurrent` blocks for parallel execution
- Use `await using` for automatic resource cleanup

## Performance Improvement

| Test File | Before | After | Improvement |
|-----------|--------|-------|-------------|
| `node-module-module.test.js` (28 tests) | ~325ms | ~185ms | **43%
faster** |
| `non-english-import.test.js` (3 tests) | ~238ms | ~157ms | **34%
faster** |

## Files Changed

- `test/cli/run/commonjs-invalid.test.ts`
- `test/cli/run/commonjs-no-export.test.ts`
- `test/cli/run/empty-file.test.ts`
- `test/cli/run/jsx-symbol-collision.test.ts`
- `test/cli/run/run-cjs.test.ts`
- `test/cli/run/run-extensionless.test.ts`
- `test/cli/run/run-shell.test.ts`
- `test/cli/run/run-unicode.test.ts`
- `test/js/bun/resolve/non-english-import.test.js`
- `test/js/node/module/node-module-module.test.js`
- `test/regression/issue/00631.test.ts`
- `test/regression/issue/03216.test.ts`
- `test/regression/issue/03830.test.ts`
- `test/regression/issue/04011.test.ts`
- `test/regression/issue/04893.test.ts`
- `test/regression/issue/hashbang-still-works.test.ts`

## Test plan

- [x] All refactored tests pass with `USE_SYSTEM_BUN=1 bun test <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>
2026-01-06 15:37:56 +00:00
Prithvish Baidya
9ab6365a13 Add support for Requester Pays in S3 operations (#25514)
- Introduced `requestPayer` option in S3-related functions and
structures to handle Requester Pays buckets.
- Updated S3 client methods to accept and propagate the `requestPayer`
flag.
- Enhanced documentation for the `requestPayer` option in the S3 type
definitions.
- Adjusted existing S3 operations to utilize the `requestPayer`
parameter where applicable, ensuring compatibility with AWS S3's
Requester Pays feature.
- Ensured that the new functionality is integrated into multipart
uploads and simple requests.

### What does this PR do?

This change allows users to specify whether they are willing to pay for
data transfer costs when accessing objects in Requester Pays buckets,
improving flexibility and compliance with AWS S3's billing model.

This closes #25499

### How did you verify your code works?

I have added a new test file to verify this functionality, and all my
tests pass.
I also tested this against an actual S3 bucket which can only be
accessed if requester pays. I can confirm that it's accessible with
`requestPayer` is `true`, and the default of `false` does not allow
access.

An example bucket is here: s3://hl-mainnet-evm-blocks/0/0/1.rmp.lz4
(my usecase is indexing [hyperliquid block
data](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/raw-hyperevm-block-data)
which is stored in s3, and I want to use bun to index faster)

---------

Co-authored-by: Alistair Smith <hi@alistair.sh>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>
2026-01-05 15:04:20 -08:00
robobun
6b5de25d8a feat(shell): add $.trace for analyzing shell commands without execution (#25667)
## Summary

Adds `Bun.$.trace` for tracing shell commands without executing them.

```js
const result = $.trace`cat /tmp/file.txt > output.txt`;
// { operations: [...], cwd: "...", success: true, error: null }
```

## Test plan

- [x] `bun bd test test/js/bun/shell/trace.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>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2025-12-27 17:25:52 -08:00
robobun
b51e993bc2 fix: reject null bytes in spawn args, env, and shell arguments (#25698)
## Summary

- Reject null bytes in command-line arguments passed to `Bun.spawn` and
`Bun.spawnSync`
- Reject null bytes in environment variable keys and values
- Reject null bytes in shell (`$`) template literal arguments

This prevents null byte injection attacks (CWE-158) where null bytes in
strings could cause unintended truncation when passed to the OS,
potentially allowing attackers to bypass file extension validation or
create files with unexpected names.

## Test plan

- [x] Added tests in `test/js/bun/spawn/null-byte-injection.test.ts`
- [x] Tests pass with debug build: `bun bd test
test/js/bun/spawn/null-byte-injection.test.ts`
- [x] Tests fail with system Bun (confirming the fix works)

🤖 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-26 23:39:37 -08:00
Jarred Sumner
28fd495b39 Deflake test/js/bun/resolve/load-same-js-file-a-lot.test.ts 2025-12-25 17:43:43 -08: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
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
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
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
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
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
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
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
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
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
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
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
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
eroderust
0d5a7c36ed chore: remove duplicate words in comment (#25347) 2025-12-05 11:19:47 -08:00