From cdc39bb0501a3e1b8c26cdf5ac0b2806c81bdbf5 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Sat, 25 Apr 2026 10:08:53 -0700 Subject: [PATCH] resolver: add bun:main to HardcodedModule.Alias so it isn't auto-installed `bun:main` was in `HardcodedModule.map` but missing from `Alias.bun_extra_alias_kvs`, so the runtime transpiler's import-record pass (RuntimeTranspilerStore.zig:534) didn't recognise it as a builtin and stripped the `bun:` prefix, leaving a bare `import("main")` in the emitted JS. At runtime that fell through `_resolve` to `resolveAndAutoInstall`, which fetched the npm `main` package over the network. The bun-main-entry-point tests "passed" by loading that npm package and flaked whenever the registry fetch was slow or failed. Adding the alias keeps the specifier intact so it round-trips through the existing `_resolve` / `getHardcodedModule` paths and resolves to the ServerEntryPoint wrapper as intended. Also harden the tests: disable auto-install via an empty package.json, assert the wrapper namespace has no exports (the npm package has four), collapse the assertions into one toEqual so a future failure surfaces stderr/exit/signal, and update the preload test's expected output order now that import("bun:main") actually evaluates the wrapper (entry runs before the preload's await resumes). --- src/bun.js/HardcodedModule.zig | 1 + .../bun/resolve/bun-main-entry-point.test.ts | 32 ++++++++++++++----- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/bun.js/HardcodedModule.zig b/src/bun.js/HardcodedModule.zig index 84f6ff99686..1fb28dabf43 100644 --- a/src/bun.js/HardcodedModule.zig +++ b/src/bun.js/HardcodedModule.zig @@ -365,6 +365,7 @@ pub const HardcodedModule = enum { .{ "bun:app", .{ .path = "bun:app" } }, .{ "bun:ffi", .{ .path = "bun:ffi" } }, .{ "bun:jsc", .{ .path = "bun:jsc" } }, + .{ "bun:main", .{ .path = "bun:main" } }, .{ "bun:sqlite", .{ .path = "bun:sqlite" } }, .{ "bun:wrap", .{ .path = "bun:wrap" } }, .{ "bun:internal-for-testing", .{ .path = "bun:internal-for-testing" } }, diff --git a/test/js/bun/resolve/bun-main-entry-point.test.ts b/test/js/bun/resolve/bun-main-entry-point.test.ts index cccd5dcaf80..9cfea1c7e17 100644 --- a/test/js/bun/resolve/bun-main-entry-point.test.ts +++ b/test/js/bun/resolve/bun-main-entry-point.test.ts @@ -18,9 +18,16 @@ function stripAsanWarning(stderr: string): string[] { test.concurrent("dynamic import('bun:main') returns the wrapper module", async () => { using dir = tempDir("bun-main-dyn", { + // package.json disables auto-install so a regression in the bun:main alias + // cannot silently fall through to fetching the npm `main` package. + "package.json": "{}", "entry.mjs": ` const m = await import("bun:main"); - if (typeof m !== "object" || m === null) throw new Error("expected module namespace"); + if (m[Symbol.toStringTag] !== "Module") throw new Error("expected module namespace, got " + Object.prototype.toString.call(m)); + // The wrapper has no named exports. The npm \`main\` package (what this + // resolved to before the alias fix) exports {default,length,name,prototype}. + const keys = Object.keys(m); + if (keys.length !== 0) throw new Error("expected empty wrapper namespace, got keys: " + keys.join(",")); console.log("OK"); `, }); @@ -32,16 +39,20 @@ test.concurrent("dynamic import('bun:main') returns the wrapper module", async ( stderr: "pipe", }); const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); - expect(stdout).toBe("OK\n"); - expect(stripAsanWarning(stderr)).toEqual([]); - expect(exitCode).toBe(0); + expect({ stdout, stderr: stripAsanWarning(stderr), exitCode, signalCode: proc.signalCode }).toEqual({ + stdout: "OK\n", + stderr: [], + exitCode: 0, + signalCode: null, + }); }); test.concurrent("import('bun:main') from a preload (before the module map is populated)", async () => { using dir = tempDir("bun-main-preload", { + "package.json": "{}", "preload.mjs": ` const m = await import("bun:main"); - if (typeof m !== "object" || m === null) throw new Error("expected module namespace"); + if (m[Symbol.toStringTag] !== "Module") throw new Error("expected module namespace"); console.log("PRELOAD_OK"); `, "entry.mjs": `console.log("ENTRY_OK");`, @@ -54,9 +65,14 @@ test.concurrent("import('bun:main') from a preload (before the module map is pop stderr: "pipe", }); const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); - expect(stdout).toBe("PRELOAD_OK\nENTRY_OK\n"); - expect(stripAsanWarning(stderr)).toEqual([]); - expect(exitCode).toBe(0); + // import("bun:main") evaluates the wrapper, which evaluates entry.mjs, so + // ENTRY_OK prints before the preload's await resumes. + expect({ stdout, stderr: stripAsanWarning(stderr), exitCode, signalCode: proc.signalCode }).toEqual({ + stdout: "ENTRY_OK\nPRELOAD_OK\n", + stderr: [], + exitCode: 0, + signalCode: null, + }); }); test.concurrent(