Skip to content

Commit 9f4c8b3

Browse files
authored
Merge branch 'main' into main
2 parents f6f4dc1 + 7afead6 commit 9f4c8b3

32 files changed

+1727
-207
lines changed

bench/snippets/path-parse.mjs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { posix, win32 } from "path";
2+
import { bench, run } from "../runner.mjs";
3+
4+
const paths = ["/home/user/dir/file.txt", "/home/user/dir/", "file.txt", "/root", ""];
5+
6+
paths.forEach(p => {
7+
bench(`posix.parse(${JSON.stringify(p)})`, () => {
8+
globalThis.abc = posix.parse(p);
9+
});
10+
});
11+
12+
paths.forEach(p => {
13+
bench(`win32.parse(${JSON.stringify(p)})`, () => {
14+
globalThis.abc = win32.parse(p);
15+
});
16+
});
17+
18+
await run();

src/CLAUDE.md

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,295 @@ Conventions:
1010
- Prefer `@import` at the **bottom** of the file, but the auto formatter will move them so you don't need to worry about it.
1111
- **Never** use `@import()` inline inside of functions. **Always** put them at the bottom of the file or containing struct. Imports in Zig are free of side-effects, so there's no such thing as a "dynamic" import.
1212
- You must be patient with the build.
13+
14+
## Prefer Bun APIs over `std`
15+
16+
**Always use `bun.*` APIs instead of `std.*`.** The `bun` namespace (`@import("bun")`) provides cross-platform wrappers that preserve OS error info and never use `unreachable`. Using `std.fs`, `std.posix`, or `std.os` directly is wrong in this codebase.
17+
18+
| Instead of | Use |
19+
| ------------------------------------------------------------ | ------------------------------------ |
20+
| `std.fs.File` | `bun.sys.File` |
21+
| `std.fs.cwd()` | `bun.FD.cwd()` |
22+
| `std.posix.open/read/write/stat/mkdir/unlink/rename/symlink` | `bun.sys.*` equivalents |
23+
| `std.fs.path.join/dirname/basename` | `bun.path.join/dirname/basename` |
24+
| `std.mem.eql/indexOf/startsWith` (for strings) | `bun.strings.eql/indexOf/startsWith` |
25+
| `std.posix.O` / `std.posix.mode_t` / `std.posix.fd_t` | `bun.O` / `bun.Mode` / `bun.FD` |
26+
| `std.process.Child` | `bun.spawnSync` |
27+
| `catch bun.outOfMemory()` | `bun.handleOom(...)` |
28+
29+
## `bun.sys` — System Calls (`src/sys.zig`)
30+
31+
All return `Maybe(T)` — a tagged union of `.result: T` or `.err: bun.sys.Error`:
32+
33+
```zig
34+
const fd = switch (bun.sys.open(path, bun.O.RDONLY, 0)) {
35+
.result => |fd| fd,
36+
.err => |err| return .{ .err = err },
37+
};
38+
// Or: const fd = try bun.sys.open(path, bun.O.RDONLY, 0).unwrap();
39+
```
40+
41+
Key functions (all take `bun.FileDescriptor`, not `std.posix.fd_t`):
42+
43+
- `open`, `openat`, `openA` (non-sentinel) → `Maybe(bun.FileDescriptor)`
44+
- `read`, `readAll`, `pread``Maybe(usize)`
45+
- `write`, `pwrite`, `writev``Maybe(usize)`
46+
- `stat`, `fstat`, `lstat``Maybe(bun.Stat)`
47+
- `mkdir`, `unlink`, `rename`, `symlink`, `chmod`, `fchmod`, `fchown``Maybe(void)`
48+
- `readlink`, `getFdPath`, `getcwd``Maybe` of path slice
49+
- `getFileSize`, `dup`, `sendfile`, `mmap`
50+
51+
Use `bun.O.RDONLY`, `bun.O.WRONLY | bun.O.CREAT | bun.O.TRUNC`, etc. for open flags.
52+
53+
### `bun.sys.File` (`src/sys/File.zig`)
54+
55+
Higher-level file handle wrapping `bun.FileDescriptor`:
56+
57+
```zig
58+
// One-shot read: open + read + close
59+
const bytes = switch (bun.sys.File.readFrom(bun.FD.cwd(), path, allocator)) {
60+
.result => |b| b,
61+
.err => |err| return .{ .err = err },
62+
};
63+
64+
// One-shot write: open + write + close
65+
switch (bun.sys.File.writeFile(bun.FD.cwd(), path, data)) {
66+
.result => {},
67+
.err => |err| return .{ .err = err },
68+
}
69+
```
70+
71+
Key methods:
72+
73+
- `File.open/openat/makeOpen``Maybe(File)` (`makeOpen` creates parent dirs)
74+
- `file.read/readAll/write/writeAll` — single or looped I/O
75+
- `file.readToEnd(allocator)` — read entire file into allocated buffer
76+
- `File.readFrom(dir_fd, path, allocator)` — open + read + close
77+
- `File.writeFile(dir_fd, path, data)` — open + write + close
78+
- `file.stat()`, `file.close()`, `file.writer()`, `file.reader()`
79+
80+
### `bun.FD` (`src/fd.zig`)
81+
82+
Cross-platform file descriptor. Use `bun.FD.cwd()` for cwd, `bun.invalid_fd` for sentinel, `fd.close()` to close.
83+
84+
### `bun.sys.Error` (`src/sys/Error.zig`)
85+
86+
Preserves errno, syscall tag, and file path. Convert to JS: `err.toSystemError().toErrorInstance(globalObject)`.
87+
88+
## `bun.strings` — String Utilities (`src/string/immutable.zig`)
89+
90+
SIMD-accelerated string operations. Use instead of `std.mem` for strings.
91+
92+
```zig
93+
// Searching
94+
strings.indexOf(haystack, needle) // ?usize
95+
strings.contains(haystack, needle) // bool
96+
strings.containsChar(haystack, char) // bool
97+
strings.indexOfChar(haystack, char) // ?u32
98+
strings.indexOfAny(str, comptime chars) // ?OptionalUsize (SIMD-accelerated)
99+
100+
// Comparison
101+
strings.eql(a, b) // bool
102+
strings.eqlComptime(str, comptime literal) // bool — optimized
103+
strings.eqlCaseInsensitiveASCII(a, b, comptime true) // 3rd arg = check_len
104+
105+
// Prefix/Suffix
106+
strings.startsWith(str, prefix) // bool
107+
strings.endsWith(str, suffix) // bool
108+
strings.hasPrefixComptime(str, comptime prefix) // bool — optimized
109+
strings.hasSuffixComptime(str, comptime suffix) // bool — optimized
110+
111+
// Trimming
112+
strings.trim(str, comptime chars) // strip from both ends
113+
strings.trimSpaces(str) // strip whitespace
114+
115+
// Encoding conversions
116+
strings.toUTF8Alloc(allocator, utf16) // ![]u8
117+
strings.toUTF16Alloc(allocator, utf8) // !?[]u16
118+
strings.toUTF8FromLatin1(allocator, latin1) // !?Managed(u8)
119+
strings.firstNonASCII(slice) // ?u32
120+
```
121+
122+
Bun handles UTF-8, Latin-1, and UTF-16/WTF-16 because JSC uses Latin-1 and UTF-16 internally. Latin-1 is NOT UTF-8 — bytes 128-255 are single chars in Latin-1 but invalid UTF-8.
123+
124+
### `bun.String` (`src/string.zig`)
125+
126+
Bridges Zig and JavaScriptCore. Prefer over `ZigString` in new code.
127+
128+
```zig
129+
const s = bun.String.cloneUTF8(utf8_slice); // copies into WTFStringImpl
130+
const s = bun.String.borrowUTF8(utf8_slice); // no copy, caller keeps alive
131+
const utf8 = s.toUTF8(allocator); // ZigString.Slice
132+
defer utf8.deinit();
133+
const js_value = s.toJS(globalObject);
134+
135+
// Create a JS string value directly from UTF-8 bytes:
136+
const js_str = try bun.String.createUTF8ForJS(globalObject, utf8_slice);
137+
```
138+
139+
## `bun.path` — Path Manipulation (`src/resolver/resolve_path.zig`)
140+
141+
Use instead of `std.fs.path`. Platform param: `.auto` (current platform), `.posix`, `.windows`, `.loose` (both separators).
142+
143+
```zig
144+
// Join paths — uses threadlocal buffer, result must be copied if it needs to persist
145+
bun.path.join(&.{ dir, filename }, .auto)
146+
bun.path.joinZ(&.{ dir, filename }, .auto) // null-terminated
147+
148+
// Join into a caller-provided buffer
149+
bun.path.joinStringBuf(&buf, &.{ a, b }, .auto)
150+
bun.path.joinStringBufZ(&buf, &.{ a, b }, .auto) // null-terminated
151+
152+
// Resolve against an absolute base (like Node.js path.resolve)
153+
bun.path.joinAbsString(cwd, &.{ relative_path }, .auto)
154+
bun.path.joinAbsStringBufZ(cwd, &buf, &.{ relative_path }, .auto)
155+
156+
// Path components
157+
bun.path.dirname(path, .auto)
158+
bun.path.basename(path)
159+
160+
// Relative path between two absolute paths
161+
bun.path.relative(from, to)
162+
bun.path.relativeAlloc(allocator, from, to)
163+
164+
// Normalize (resolve `.` and `..`)
165+
bun.path.normalizeBuf(path, &buf, .auto)
166+
167+
// Null-terminate a path into a buffer
168+
bun.path.z(path, &buf) // returns [:0]const u8
169+
```
170+
171+
Use `bun.PathBuffer` for path buffers: `var buf: bun.PathBuffer = undefined;`
172+
173+
For pooled path buffers (avoids 64KB stack allocations on Windows):
174+
175+
```zig
176+
const buf = bun.path_buffer_pool.get();
177+
defer bun.path_buffer_pool.put(buf);
178+
```
179+
180+
## URL Parsing
181+
182+
Prefer `bun.jsc.URL` (WHATWG-compliant, backed by WebKit C++) over `bun.URL.parse` (internal, doesn't properly handle errors or invalid URLs).
183+
184+
```zig
185+
// Parse a URL string (returns null if invalid)
186+
const url = bun.jsc.URL.fromUTF8(href_string) orelse return error.InvalidURL;
187+
defer url.deinit();
188+
189+
url.protocol() // bun.String
190+
url.pathname() // bun.String
191+
url.search() // bun.String
192+
url.hash() // bun.String (includes leading '#')
193+
url.port() // u32 (maxInt(u32) if not set, otherwise u16 range)
194+
195+
// NOTE: host/hostname are SWAPPED vs JS:
196+
url.host() // hostname WITHOUT port (opposite of JS!)
197+
url.hostname() // hostname WITH port (opposite of JS!)
198+
199+
// Normalize a URL string (percent-encode, punycode, etc.)
200+
const normalized = bun.jsc.URL.hrefFromString(bun.String.borrowUTF8(input));
201+
if (normalized.tag == .Dead) return error.InvalidURL;
202+
defer normalized.deref();
203+
204+
// Join base + relative URLs
205+
const joined = bun.jsc.URL.join(base_str, relative_str);
206+
defer joined.deref();
207+
208+
// Convert between file paths and file:// URLs
209+
const file_url = bun.jsc.URL.fileURLFromString(path_str); // path → file://
210+
const file_path = bun.jsc.URL.pathFromFileURL(url_str); // file:// → path
211+
```
212+
213+
## MIME Types (`src/http/MimeType.zig`)
214+
215+
```zig
216+
const MimeType = bun.http.MimeType;
217+
218+
// Look up by file extension (without leading dot)
219+
const mime = MimeType.byExtension("html"); // MimeType{ .value = "text/html", .category = .html }
220+
const mime = MimeType.byExtensionNoDefault("xyz"); // ?MimeType (null if unknown)
221+
222+
// Category checks
223+
mime.category // .javascript, .css, .html, .json, .image, .text, .wasm, .font, .video, .audio, ...
224+
mime.category.isCode()
225+
```
226+
227+
Common constants: `MimeType.javascript`, `MimeType.json`, `MimeType.html`, `MimeType.css`, `MimeType.text`, `MimeType.wasm`, `MimeType.ico`, `MimeType.other`.
228+
229+
## Memory & Allocators
230+
231+
**Use `bun.default_allocator` for almost everything.** It's backed by mimalloc.
232+
233+
`bun.handleOom(expr)` converts `error.OutOfMemory` into a crash without swallowing other errors:
234+
235+
```zig
236+
const buf = bun.handleOom(allocator.alloc(u8, size)); // correct
237+
// NOT: allocator.alloc(u8, size) catch bun.outOfMemory() — could swallow non-OOM errors
238+
```
239+
240+
## Environment Variables (`src/env_var.zig`)
241+
242+
Type-safe, cached environment variable accessors via `bun.env_var`:
243+
244+
```zig
245+
bun.env_var.HOME.get() // ?[]const u8
246+
bun.env_var.CI.get() // ?bool
247+
bun.env_var.BUN_CONFIG_DNS_TIME_TO_LIVE_SECONDS.get() // u64 (has default: 30)
248+
```
249+
250+
## Logging (`src/output.zig`)
251+
252+
```zig
253+
const log = bun.Output.scoped(.MY_FEATURE, .visible); // .hidden = opt-in via BUN_DEBUG_MY_FEATURE=1
254+
log("processing {d} items", .{count});
255+
256+
// Color output (convenience wrappers auto-detect TTY):
257+
bun.Output.pretty("<green>success<r>: {s}\n", .{msg});
258+
bun.Output.prettyErrorln("<red>error<r>: {s}", .{msg});
259+
```
260+
261+
## Spawning Subprocesses (`src/bun.js/api/bun/process.zig`)
262+
263+
Use `bun.spawnSync` instead of `std.process.Child`:
264+
265+
```zig
266+
switch (bun.spawnSync(&.{
267+
.argv = argv,
268+
.envp = null, // inherit parent env
269+
.cwd = cwd,
270+
.stdout = .buffer, // capture
271+
.stderr = .inherit, // pass through
272+
.stdin = .ignore,
273+
274+
.windows = if (bun.Environment.isWindows) .{
275+
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(env, null)),
276+
},
277+
}) catch return) {
278+
.err => |err| { /* bun.sys.Error */ },
279+
.result => |result| {
280+
defer result.deinit();
281+
const stdout = result.stdout.items;
282+
if (result.status.isOK()) { ... }
283+
},
284+
}
285+
```
286+
287+
Options: `argv: []const []const u8`, `envp: ?[*:null]?[*:0]const u8` (null = inherit), `argv0: ?[*:0]const u8`. Stdio: `.inherit`, `.ignore`, `.buffer`.
288+
289+
## Common Patterns
290+
291+
```zig
292+
// Read a file
293+
const contents = switch (bun.sys.File.readFrom(bun.FD.cwd(), path, allocator)) {
294+
.result => |bytes| bytes,
295+
.err => |err| { globalObject.throwValue(err.toSystemError().toErrorInstance(globalObject)); return .zero; },
296+
};
297+
298+
// Create directories recursively
299+
bun.makePath(dir.stdDir(), sub_path) catch |err| { ... };
300+
301+
// Hashing
302+
bun.hash(bytes) // u64 — wyhash
303+
bun.hash32(bytes) // u32
304+
```

src/StandaloneModuleGraph.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -935,7 +935,7 @@ pub const StandaloneModuleGraph = struct {
935935

936936
var remain = bytes;
937937
while (remain.len > 0) {
938-
switch (Syscall.write(cloned_executable_fd, bytes)) {
938+
switch (Syscall.write(cloned_executable_fd, remain)) {
939939
.result => |written| remain = remain[written..],
940940
.err => |err| {
941941
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to write to temporary file\n{f}", .{err});

src/bun.js/bindings/BunPlugin.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,7 @@ BUN_DEFINE_HOST_FUNCTION(jsFunctionBunPluginClear, (JSC::JSGlobalObject * global
954954
global->onResolvePlugins.namespaces.clear();
955955

956956
delete global->onLoadPlugins.virtualModules;
957+
global->onLoadPlugins.virtualModules = nullptr;
957958

958959
return JSC::JSValue::encode(JSC::jsUndefined());
959960
}

src/bun.js/bindings/Path.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,25 @@ static JSC::JSObject* createPath(JSGlobalObject* globalThis, bool isWindows)
114114

115115
} // namespace Zig
116116

117+
extern "C" JSC::EncodedJSValue PathParsedObject__create(
118+
JSC::JSGlobalObject* globalObject,
119+
JSC::EncodedJSValue root,
120+
JSC::EncodedJSValue dir,
121+
JSC::EncodedJSValue base,
122+
JSC::EncodedJSValue ext,
123+
JSC::EncodedJSValue name)
124+
{
125+
auto* global = JSC::jsCast<Zig::GlobalObject*>(globalObject);
126+
auto& vm = JSC::getVM(globalObject);
127+
JSC::JSObject* result = JSC::constructEmptyObject(vm, global->pathParsedObjectStructure());
128+
result->putDirectOffset(vm, 0, JSC::JSValue::decode(root));
129+
result->putDirectOffset(vm, 1, JSC::JSValue::decode(dir));
130+
result->putDirectOffset(vm, 2, JSC::JSValue::decode(base));
131+
result->putDirectOffset(vm, 3, JSC::JSValue::decode(ext));
132+
result->putDirectOffset(vm, 4, JSC::JSValue::decode(name));
133+
return JSC::JSValue::encode(result);
134+
}
135+
117136
namespace Bun {
118137

119138
JSC::JSValue createNodePathBinding(Zig::GlobalObject* globalObject)

src/bun.js/bindings/ZigGlobalObject.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2067,6 +2067,30 @@ void GlobalObject::finishCreation(VM& vm)
20672067
init.set(structure);
20682068
});
20692069

2070+
this->m_pathParsedObjectStructure.initLater(
2071+
[](const Initializer<Structure>& init) {
2072+
// { root, dir, base, ext, name } — path.parse() result
2073+
Structure* structure = init.owner->structureCache().emptyObjectStructureForPrototype(
2074+
init.owner, init.owner->objectPrototype(), 5);
2075+
PropertyOffset offset;
2076+
structure = Structure::addPropertyTransition(init.vm, structure,
2077+
Identifier::fromString(init.vm, "root"_s), 0, offset);
2078+
RELEASE_ASSERT(offset == 0);
2079+
structure = Structure::addPropertyTransition(init.vm, structure,
2080+
Identifier::fromString(init.vm, "dir"_s), 0, offset);
2081+
RELEASE_ASSERT(offset == 1);
2082+
structure = Structure::addPropertyTransition(init.vm, structure,
2083+
Identifier::fromString(init.vm, "base"_s), 0, offset);
2084+
RELEASE_ASSERT(offset == 2);
2085+
structure = Structure::addPropertyTransition(init.vm, structure,
2086+
Identifier::fromString(init.vm, "ext"_s), 0, offset);
2087+
RELEASE_ASSERT(offset == 3);
2088+
structure = Structure::addPropertyTransition(init.vm, structure,
2089+
init.vm.propertyNames->name, 0, offset);
2090+
RELEASE_ASSERT(offset == 4);
2091+
init.set(structure);
2092+
});
2093+
20702094
this->m_pendingVirtualModuleResultStructure.initLater(
20712095
[](const Initializer<Structure>& init) {
20722096
init.set(Bun::PendingVirtualModuleResult::createStructure(init.vm, init.owner, init.owner->objectPrototype()));

0 commit comments

Comments
 (0)