Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ecb3f18
Typecheck the built-in modules (src/js) in CI
dylan-conway Sep 20, 2026
a4a1290
Merge remote-tracking branch 'origin/main' into claude/typecheck-buil…
dylan-conway Sep 20, 2026
901fd0d
Fix oxlint errors in the typed built-in modules
dylan-conway Sep 20, 2026
83af22c
Review fixes for the built-in modules typecheck
dylan-conway Sep 21, 2026
9b01970
console.write: keep the Promise a backed-up stdout returns
dylan-conway Sep 21, 2026
7851612
Fix two test failures: spawnSync result, fuzzilli-reprl syntax
dylan-conway Sep 21, 2026
22b660b
node:wasi: fd_pread counts each byte once
dylan-conway Sep 21, 2026
a4854e6
spawnSync: node's result for a process that never ran; console.write …
dylan-conway Sep 21, 2026
398c011
FileSink: a write() that fails while another is pending rejects once
dylan-conway Sep 21, 2026
0569dd8
spawnSync: the lost-output error also carries signalCode
dylan-conway Sep 21, 2026
6909fa9
Typecheck the rest of src and the dev server (bake) in CI
dylan-conway Sep 21, 2026
4b771e9
Merge branch 'main' of github.com:oven-sh/bun into claude/typecheck-b…
dylan-conway Sep 21, 2026
93da97e
build: bake's generated.ts is a declared output, in the types target
dylan-conway Sep 21, 2026
3a2a48d
filesink test: order the failing write by construction
dylan-conway Sep 21, 2026
7dd87e0
Remove the ERR_SSR_RESPONSE_EXPECTED table entry
dylan-conway Sep 21, 2026
75685a6
Leave bun-types' package.json alone; undici-types is a root devDepend…
dylan-conway Sep 21, 2026
edb1625
Merge branch 'main' of github.com:oven-sh/bun into claude/typecheck-b…
dylan-conway Sep 21, 2026
660d94a
Readable::take_read_error: match on the taken value
dylan-conway Sep 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,14 @@ jobs:
run: |
bun x tsc --noEmit -p scripts/build/tsconfig.json
bun x tsc --noEmit -p scripts/tsconfig.json
- name: Typecheck the built-in modules
# The code generators write the types src/js/builtins.d.ts references (build/types) and the enums the dev
# server's sources import (src/runtime/bake/generated.ts).
run: |
bun run build:types
bun x tsc --noEmit -p src/js/tsconfig.json
- name: Typecheck the rest of src
# The code generators, the class definitions and the other build-time TypeScript, then the dev server.
run: |
bun x tsc --noEmit -p src/tsconfig.json
bun x tsc --noEmit -p src/runtime/bake/tsconfig.json
12 changes: 11 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
"devDependencies": {
"@lezer/common": "1.5.2",
"@lezer/cpp": "1.1.6",
"@types/react": "18.3.31",
"@types/react-dom": "18.3.7",
"esbuild": "0.21.5",
"oxlint": "1.70.0",
"prettier": "3.6.2",
"prettier-plugin-organize-imports": "4.3.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"source-map-js": "1.2.1",
"typescript": "6.0.2"
"typescript": "6.0.2",
"undici-types": "8.3.0"
},
"resolutions": {
"bun-types": "workspace:packages/bun-types",
Expand Down Expand Up @@ -46,6 +49,7 @@
"build:logs": "bun scripts/build.ts --profile=release --logs=on --build-dir=build/release-logs",
"build:smol": "bun scripts/build.ts --profile=release --build-type=MinSizeRel --build-dir=build/release-smol",
"build:local": "bun scripts/build.ts --profile=debug-local --build-dir=build/debug-local",
"build:types": "bun scripts/build.ts --mode=codegen --target=generated-types",
"build:release:local": "bun scripts/build.ts --profile=release-local --build-dir=build/release-local",
"run:linux": "docker run --rm -v \"$PWD:/root/bun/\" -w /root/bun ghcr.io/oven-sh/bun-development-docker-image",
"uv-posix-stubs": "bun run src/jsc/bindings/libuv/generate_uv_posix_stubs.ts",
Expand Down
19 changes: 16 additions & 3 deletions packages/bun-types/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1198,10 +1198,19 @@ interface Console {
* console.write("hello world!", "\n"); // "hello world!\n"
* ```
*
* When stdout cannot take the data right away (a pipe whose reader is slow),
* the data is buffered and a Promise is returned instead of a number. Await it
* to wait until the data has been written; it rejects if the write fails, for
* example with `EPIPE` when the reader has closed the pipe.
*
* ```ts
* await console.write(largeOutput);
* ```
*
* @param data - The data to write
* @returns The number of bytes written
* @returns The number of bytes written, or a Promise of it when stdout is backed up
*/
write(...data: Array<string | ArrayBufferView | ArrayBuffer>): number;
write(...data: Array<string | ArrayBufferView | ArrayBuffer>): number | Promise<number>;

/**
* Clear the console
Expand Down Expand Up @@ -1773,7 +1782,11 @@ declare var PerformanceMeasure: Bun.__internal.UseLibDomIfAvailable<
interface PerformanceObserver extends Bun.__internal.LibEmptyOrPerformanceObserver {}
declare var PerformanceObserver: Bun.__internal.UseLibDomIfAvailable<
"PerformanceObserver",
{ prototype: PerformanceObserver; new (): PerformanceObserver }
{
prototype: PerformanceObserver;
new (callback: import("node:perf_hooks").PerformanceObserverCallback): PerformanceObserver;
readonly supportedEntryTypes: readonly string[];
}
>;

interface PerformanceObserverEntryList extends Bun.__internal.LibEmptyOrPerformanceObserverEntryList {}
Expand Down
1 change: 1 addition & 0 deletions packages/bun-types/overrides.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ declare global {
};
binding(m: "uv"): {
errname(code: number): string;
getErrorMap(): Map<number, [name: string, description: string]>;
UV_E2BIG: number;
UV_EACCES: number;
UV_EADDRINUSE: number;
Expand Down
34 changes: 30 additions & 4 deletions scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,16 @@ import {
uploadArtifacts,
} from "./build/ci.ts";
import { formatConfig, formatConfigUnchanged, type Config, type PartialConfig } from "./build/config.ts";
import { configOf, configure, reconfigure, type ConfigureInput } from "./build/configure.ts";
import {
codegenConfigOf,
configOf,
configure,
configureCodegen,
modeOf,
reconfigure,
reconfigureCodegen,
type ConfigureInput,
} from "./build/configure.ts";
import { BuildError } from "./build/error.ts";
import { ninjaIfPresent } from "./build/ninja-release.ts";
import { STREAM_FD } from "./build/stream.ts";
Expand Down Expand Up @@ -77,7 +86,8 @@ async function main(): Promise<void> {
// A ninja tool (`-t query <target>`, `-t deps <object>`, `-t commands`, …) inspects what the last configure and
// build left behind, so it runs on the build directory as it is, with the ninja the build runs.
if (args.ninjaTool !== undefined) {
const { cfg } = configOf({ profile: args.profile, overrides: args.overrides });
const toolInput: ConfigureInput = { profile: args.profile, overrides: args.overrides };
const cfg = modeOf(toolInput) === "codegen" ? codegenConfigOf(toolInput) : configOf(toolInput).cfg;
if (!existsSync(join(cfg.buildDir, "build.ninja"))) {
throw new BuildError(`${cfg.buildDir} has not been configured`, {
hint: "Build it, or configure it with --configure-only, using the same profile flags.",
Expand Down Expand Up @@ -129,10 +139,26 @@ async function main(): Promise<void> {
// ninja's generator rule replaying a previous configure (`regen`, configure.ts): just rewrite build.ninja.
// ninja's own [N/M] line already says "reconfigure"; the CI prelude and the local summary would be noise
// in the middle of a build log.
await reconfigure(input);
await (modeOf(input) === "codegen" ? reconfigureCodegen(input) : reconfigure(input));
return;
}

// mode=codegen: the code generators and nothing else. No toolchain was looked for, and none of what the two
// paths below do around a native build (artifacts, the symbol order file, a binary to run) applies.
if (modeOf(input) === "codegen") {
if (args.execArgs.length > 0) {
throw new BuildError("mode=codegen builds no binary to run", { hint: "Drop the positional args." });
}
const result = await configureCodegen(input);
if (!args.quiet) {
process.stderr.write(`codegen only → ${result.cfg.codegenDir} (configured in ${result.elapsed}ms)\n`);
}
if (args.configureOnly) return;
const ninja = spawnSync(result.ninja, ninjaArgv(result.cfg), { stdio: "inherit" });
if (ninja.error) throw new BuildError("Failed to run ninja", { cause: ninja.error });
process.exit(ninja.status ?? 1);
}

if (isCI) {
// CI: machine/env dump + collapsible groups + annotation-on-failure.
printEnvironment();
Expand Down Expand Up @@ -668,7 +694,7 @@ Options:
vendored dep from a local checkout),
package-manager (bun|npm, installs the
package.json files the build needs),
buildDir, mode (full|cpp-only|link-only),
buildDir, mode (full|cpp-only|link-only|codegen),
unifiedSources, timeTrace, os, arch, abi,
winsysroot (Windows cross-compile SDK root)
--target=<name> Build a specific ninja target (repeatable)
Expand Down
18 changes: 15 additions & 3 deletions scripts/build/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ Tables: `cpuTargetFlags` (`-march`/`-mcpu`/`-mtune` — also forwarded to local

**Iterate on a dependency from a local checkout** — `bun bd --local-deps=mimalloc=~/code/mimalloc …` builds that dep from the clone instead of the pinned tarball (no fetch, no patches; edits rebuild incrementally). Any `github-archive` dep the graph compiles (not lolhtml or rust-argon2 — cargo reads those via `Cargo.toml`); details in `deps/README.md`.

**Add a codegen step** — add a function in `codegen.ts` following the shape of `emitErrorCode` (simple) or `emitCppBind` (needs file-list input). Use the `codegen` rule: it runs the script with `cfg.jsRuntime`, so the script must run under node and bun. Call it from `emitCodegen()` and add outputs to the right `CodegenOutputs` group (`rustInputs` if the Rust build reads it (the `include!`d generated `.rs` files) — `cppSources` if it's a `.cpp` to compile, `cppHeaders` if it's a header. `emitCodegen()` builds `cppAll` from those groups at the end, so do not push to it).
**Add a codegen step** — add a function in `codegen.ts` following the shape of `emitErrorCode` (simple) or `emitCppBind` (needs file-list input). Use the `codegen` rule: it runs the script with `cfg.jsRuntime`, so the script must run under node and bun. Call it from `emitCodegen()` and add outputs to the right `CodegenOutputs` group (`rustInputs` if the Rust build reads it (the `include!`d generated `.rs` files) — `cppSources` if it's a `.cpp` to compile, `cppHeaders` if it's a header. `emitCodegen()` builds `cppAll` from those groups at the end, so do not push to it). A type declaration `src/js/builtins.d.ts` references goes in `cfg.typesDir` and the `generatedTypes` group. The functions take `CodegenFields`: a config field a generator newly reads is added to that `Pick` and must be one `resolveBase()` can decide without a native tool.

**Add a ninja rule** — add its name to `ruleVars` in `ninja.ts` with the `$variables` its text reads, and `n.rule()` it in the module's `registerXxxRules()`. `n.build({ rule, vars })` is typed by the table, and configure fails if the table and the rule's text disagree. Text that needs other variables on some platform is another rule (`pch` / `pch_msvc`). ninja's own bindings (`pool`, `depfile`, `early_output_prefix`) are fields of the build statement, not `vars`.

Expand Down Expand Up @@ -179,6 +179,18 @@ For `mode: "full"` (the normal case):

Split CI modes: `rust-only` (path deps+codegen+rustc units → libbun_runtime.a), `cpp-only` (deps+codegen+compile → archive), `link-only` (download artifacts → link), `rust-and-link` (Rust units + poll build-cpp + download archive → link). The pipeline's `build-bun` step uses `archive-link` (`ci-build` profile): the full graph on one agent, linking from the same archive `cpp-only` produces, with the archive, libbun_runtime.a and dep libs uploaded from ninja edges as soon as each exists.

### `mode: "codegen"` — the code generators alone

`--mode=codegen` configures a graph of the codegen steps only, default target `codegen`, for what needs their outputs and no binary. It has a build directory of its own (`build/debug-codegen`): its manifest and `compile_commands.json` have no native edges in them, and `build/debug` is where clangd reads `compile_commands.json`. `bun run build:types` (`--mode=codegen --target=generated-types`) is what `lint.yml` runs before typechecking `src/js`.

**`build/types/` (`cfg.typesDir`)** holds the type declarations `src/js/builtins.d.ts` references: `generated.d.ts`, `ErrorCode.d.ts`, `ZigGeneratedClasses.d.ts`, `WebCoreJSBuiltins.d.ts`. They are made from source alone (byte-identical between debug and release), so they do not live under a profile's build directory: every profile's graph declares the same files there, the way a dep's `vendor/<name>/.ref` is, and any build keeps them current for the editor. They are declared outputs of the edges that write them (`CodegenOutputs.generatedTypes`, target `generated-types`), so ninja regenerates one that is deleted or whose edge inputs changed. `src/runtime/bake/generated.ts` (the dev server's message enums, which the bake project imports) is in the same target, declared by the bake codegen edge that writes it.

It is configured by `configureCodegen()`, not `configure()`, and resolves a `CodegenConfig`, not a `Config`:

- `resolveJsToolchain()` looks for bun and the root install's esbuild. No compiler, linker, cmake or cargo is looked for, no SDK or sysroot is fetched, and `.cargo/config.toml` is not written. perl is required (the LUT steps).
- `CodegenFields` (`config.ts`) is the `Pick` of `Config` the generators read, and `codegen.ts` takes that type, so a generator that starts reading a native tool does not compile.
- `resolveBase()` decides the build type, the paths, the JavaScript tools and what `build_options.rs` is generated from, for both `resolveConfig()` and `resolveCodegenConfig()`.

### Phase 3 — Execute

- **CI:** collapsible log groups, spawn ninja with `spawnWithAnnotations` (parses compiler errors into Buildkite annotations), upload/download artifacts.
Expand All @@ -191,8 +203,8 @@ Split CI modes: `rust-only` (path deps+codegen+rustc units → libbun_runtime.a)
| File | Owns |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `build.ts` (parent dir) | CLI entry — parse args, call configure, spawn ninja, optionally exec |
| `configure.ts` | `configure()` — toolchain → config → `build.ninja` |
| `config.ts` | `Config`/`PartialConfig`/`Toolchain`/`Host` types, `resolveConfig()` |
| `configure.ts` | `configure()` — toolchain → config → `build.ninja`; `configureCodegen()` for `mode: "codegen"` |
| `config.ts` | `Config`/`PartialConfig`/`Toolchain`/`Host` types, `resolveConfig()`; `CodegenFields`/`CodegenConfig`, `resolveCodegenConfig()` |
| `profiles.ts` | Named `PartialConfig` presets + `getProfile()` |
| `tools.ts` | Tool discovery: `findTool()`, `resolveLlvmToolchain()`, version parsing, `checkImageTools()` |
| `flags.ts` | Flat flag tables, `computeFlags()`, `computeDepFlags()`, `computeCpuTargetFlags()` |
Expand Down
4 changes: 2 additions & 2 deletions scripts/build/buildOptionsRs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

import { mkdirSync } from "node:fs";
import { resolve } from "node:path";
import type { Config } from "./config.ts";
import type { CodegenFields } from "./config.ts";
import { writeIfChanged } from "./fs.ts";

/** Rust string literal for `s`. JSON escaping is a strict subset of Rust's. */
Expand All @@ -36,7 +36,7 @@ const rstr = (s: string): string => JSON.stringify(s);
*/
const rbstr = (s: string): string => `${JSON.stringify(s)}.as_bytes()`;

export function generateBuildOptionsRs(cfg: Config): string {
export function generateBuildOptionsRs(cfg: CodegenFields): string {
const outPath = resolve(cfg.codegenDir, "build_options.rs");
const [major, minor, patch] = cfg.version.split(".");

Expand Down
Loading
Loading