Files
bun.sh/test/js/bun/resolve/resolve.test.ts
pfg cb2887feee Fix Bun.resolve() returning a promise throwing a raw exception instead of an Error (#21302)
I haven't checked all uses of tryTakeException but this bug is probably
not the only one.

Caught by running fuzzy-wuzzy with debug logging enabled. It tried to
print the exception. Updates fuzzy-wuzzy to have improved logging that
can tell you what was last executed before a crash.

---------

Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-07-25 12:46:33 -07:00

447 lines
14 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { pathToFileURL } from "bun";
import { describe, expect, it } from "bun:test";
import { mkdirSync, writeFileSync } from "fs";
import { bunEnv, bunExe, bunRun, isWindows, joinP, tempDirWithFiles } from "harness";
import { join, resolve, sep } from "path";
const fixture = (...segs: string[]) => resolve(import.meta.dir, "fixtures", ...segs);
it("spawn test file", () => {
writePackageJSONImportsFixture();
writePackageJSONExportsFixture();
const { exitCode } = Bun.spawnSync({
cmd: [bunExe(), "test", "./resolve-test.js"],
env: bunEnv,
cwd: import.meta.dir,
stdio: ["inherit", "inherit", "inherit"],
});
expect(exitCode).toBe(0);
});
function writePackageJSONExportsFixture() {
try {
mkdirSync(join(import.meta.dir, "./node_modules/package-json-exports/foo"), {
recursive: true,
});
} catch (exception) {}
writeFileSync(join(import.meta.dir, "./node_modules/package-json-exports/foo/bar.js"), "export const bar = 1;");
writeFileSync(
join(import.meta.dir, "./node_modules/package-json-exports/foo/references-baz.js"),
"export {bar} from 'package-json-exports/baz';",
);
writeFileSync(
join(import.meta.dir, "./node_modules/package-json-exports/package.json"),
JSON.stringify(
{
name: "package-json-exports",
exports: {
"./baz": "./foo/bar.js",
"./references-baz": "./foo/references-baz.js",
},
},
null,
2,
),
);
}
function writePackageJSONImportsFixture() {
try {
mkdirSync(join(import.meta.dir, "./node_modules/package-json-imports/foo"), {
recursive: true,
});
} catch (exception) {}
writeFileSync(join(import.meta.dir, "./node_modules/package-json-imports/foo/bar.js"), "export const bar = 1;");
writeFileSync(
join(import.meta.dir, "./node_modules/package-json-imports/foo/wildcard.js"),
"export const wildcard = 1;",
);
writeFileSync(
join(import.meta.dir, "./node_modules/package-json-imports/foo/private-foo.js"),
"export {bar} from 'package-json-imports/#foo';",
);
writeFileSync(
join(import.meta.dir, "./node_modules/package-json-imports/package.json"),
JSON.stringify(
{
name: "package-json-imports",
exports: {
"./baz": "./foo/bar.js",
},
imports: {
"#foo/bar": "./foo/private-foo.js",
"#foo/*.js": "./foo/*.js",
"#foo/extensionless/*": "./foo/*.js",
"#foo": "./foo/private-foo.js",
"#internal-react": "react",
"#to_node_module": "async_hooks",
},
},
null,
2,
),
);
}
it("file url in import resolves", async () => {
const dir = tempDirWithFiles("fileurl", {
"index.js": "export const foo = 1;",
});
writeFileSync(`${dir}/test.js`, `import {foo} from '${pathToFileURL(dir)}/index.js';\nconsole.log(foo);`);
console.log(dir);
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), `${dir}/test.js`],
env: bunEnv,
cwd: import.meta.dir,
});
try {
expect(exitCode).toBe(0);
} catch (e) {
console.log(stdout.toString("utf8"));
throw e;
}
expect(stdout.toString("utf8")).toBe("1\n");
});
it("invalid file url in import throws error", async () => {
const dir = tempDirWithFiles("fileurl", {});
writeFileSync(`${dir}/test.js`, `import {foo} from 'file://\0invalid url';\nconsole.log(foo);`);
const { exitCode, stdout, stderr } = Bun.spawnSync({
cmd: [bunExe(), `${dir}/test.js`],
env: bunEnv,
cwd: import.meta.dir,
});
expect(exitCode).not.toBe(0);
expect(stderr.toString("utf8")).toContain("file://\0invalid url");
});
it("file url in await import resolves", async () => {
const dir = tempDirWithFiles("fileurl", {
"index.js": "export const foo = 1;",
});
writeFileSync(`${dir}/test.js`, `const {foo} = await import('${pathToFileURL(dir)}/index.js');\nconsole.log(foo);`);
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), `${dir}/test.js`],
env: bunEnv,
cwd: import.meta.dir,
});
expect(exitCode).toBe(0);
expect(stdout.toString("utf8")).toBe("1\n");
});
it("file url with special characters in await import resolves", async () => {
const filename = "🅱ndex.js";
const dir = tempDirWithFiles("file url", {
[filename]: "export const foo = 1;",
});
console.log(dir);
writeFileSync(
`${dir}/test.js`,
`const {foo} = await import('${pathToFileURL(dir)}/${encodeURIComponent(filename)}');\nconsole.log(foo);`,
);
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), `${dir}/test.js`],
env: bunEnv,
cwd: import.meta.dir,
});
expect(exitCode).toBe(0);
expect(stdout.toString("utf8")).toBe("1\n");
});
it("file url with special characters not encoded in await import resolves", async () => {
const filename = "🅱ndex.js";
const dir = tempDirWithFiles("file url", {
[filename]: "export const foo = 1;",
});
writeFileSync(
`${dir}/test.js`,
`const {foo} = await import('${pathToFileURL(dir)}/${filename}');\nconsole.log(foo);`,
);
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), `${dir}/test.js`],
env: bunEnv,
cwd: import.meta.dir,
});
expect(exitCode).toBe(0);
expect(stdout.toString("utf8")).toBe("1\n");
});
it("file url with special characters in import statement resolves", async () => {
const filename = "🅱ndex.js";
const dir = tempDirWithFiles("file url", {
[filename]: "export const foo = 1;",
});
writeFileSync(
`${dir}/test.js`,
`import {foo} from '${pathToFileURL(dir)}/${encodeURIComponent(filename)}';\nconsole.log(foo);`,
);
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), `${dir}/test.js`],
env: bunEnv,
cwd: import.meta.dir,
});
expect(exitCode).toBe(0);
expect(stdout.toString("utf8")).toBe("1\n");
});
it("file url with special characters not encoded in import statement resolves", async () => {
const filename = "🅱ndex.js";
const dir = tempDirWithFiles("file url", {
[filename]: "export const foo = 1;",
});
writeFileSync(`${dir}/test.js`, `import {foo} from '${pathToFileURL(dir)}/${filename}';\nconsole.log(foo);`);
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), `${dir}/test.js`],
env: bunEnv,
cwd: import.meta.dir,
});
expect(exitCode).toBe(0);
expect(stdout.toString("utf8")).toBe("1\n");
});
it("file url in require resolves", async () => {
const dir = tempDirWithFiles("fileurl", {
"index.js": "export const foo = 1;",
});
writeFileSync(`${dir}/test.js`, `const {foo} = require('${pathToFileURL(dir)}/index.js');\nconsole.log(foo);`);
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), `${dir}/test.js`],
env: bunEnv,
cwd: import.meta.dir,
});
expect(exitCode).toBe(0);
expect(stdout.toString("utf8")).toBe("1\n");
});
it("file url with special characters in require resolves", async () => {
const filename = "🅱ndex.js";
const dir = tempDirWithFiles("file url", {
[filename]: "export const foo = 1;",
});
writeFileSync(
`${dir}/test.js`,
`const {foo} = require('${pathToFileURL(dir)}/${encodeURIComponent(filename)}');\nconsole.log(foo);`,
);
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), `${dir}/test.js`],
env: bunEnv,
cwd: import.meta.dir,
});
expect(exitCode).toBe(0);
expect(stdout.toString("utf8")).toBe("1\n");
});
it("file url in require.resolve resolves", async () => {
const dir = tempDirWithFiles("fileurl", {
"index.js": "export const foo = 1;",
});
writeFileSync(`${dir}/test.js`, `const to = require.resolve('${pathToFileURL(dir)}/index.js');\nconsole.log(to);`);
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), `${dir}/test.js`],
env: bunEnv,
cwd: import.meta.dir,
});
expect(exitCode).toBe(0);
expect(stdout.toString("utf8")).toBe(`${dir}${sep}index.js\n`);
});
it("file url with special characters in require resolves", async () => {
const filename = "🅱ndex.js";
const dir = tempDirWithFiles("file url", {
[filename]: "export const foo = 1;",
});
writeFileSync(
`${dir}/test.js`,
`const to = require.resolve('${pathToFileURL(dir)}/${encodeURIComponent(filename)}');\nconsole.log(to);`,
);
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), `${dir}/test.js`],
env: bunEnv,
cwd: import.meta.dir,
});
expect(exitCode).toBe(0);
expect(stdout.toString("utf8")).toBe(`${dir}${sep}${filename}\n`);
});
it("import long string should not segfault", async () => {
try {
await import("a".repeat(10000));
} catch {}
});
it("import long string should not segfault", async () => {
try {
import.meta.require("a".repeat(10000));
} catch {}
});
it("import long string should not segfault", async () => {
try {
await import.meta.resolve!("a".repeat(10000));
} catch {}
});
it("import long string should not segfault", async () => {
try {
await import.meta.require.resolve("a".repeat(10000));
} catch {}
});
it("import override to node builtin", async () => {
// @ts-expect-error
expect(await import("#async_hooks")).toBeDefined();
});
it("import override to bun", async () => {
// @ts-expect-error
expect(await import("#bun")).toBeDefined();
});
it.todo("import override to bun:test", async () => {
// @ts-expect-error
expect(await import("#bun_test")).toBeDefined();
});
it.if(isWindows)("directory cache key computation", () => {
expect(import(`${process.cwd()}\\\\doesnotexist.ts`)).rejects.toThrow();
expect(import(`${process.cwd()}\\\\\\doesnotexist.ts`)).rejects.toThrow();
expect(import(`\\\\Test\\\\doesnotexist.ts\\` as any)).rejects.toThrow();
expect(import(`\\\\Test\\\\doesnotexist.ts\\\\` as any)).rejects.toThrow();
expect(import(`\\\\Test\\\\doesnotexist.ts\\\\\\` as any)).rejects.toThrow();
expect(import(`\\\\Test\\\\\\doesnotexist.ts` as any)).rejects.toThrow();
expect(import(`\\\\Test\\\\\\\\doesnotexist.ts` as any)).rejects.toThrow();
expect(import(`\\\\Test\\doesnotexist.ts` as any)).rejects.toThrow();
expect(import(`\\\\\\Test\\doesnotexist.ts` as any)).rejects.toThrow();
expect(import(`\\\\Test\\\\\\doesnotexist.ts\\` as any)).rejects.toThrow();
expect(import(`\\\\Test\\\\\\\\doesnotexist.ts\\` as any)).rejects.toThrow();
expect(import(`\\\\Test\\doesnotexist.ts\\` as any)).rejects.toThrow();
expect(import(`\\\\\\Test\\doesnotexist.ts\\` as any)).rejects.toThrow();
expect(import(`\\\\Test\\\\\\doesnotexist.ts\\\\` as any)).rejects.toThrow();
expect(import(`\\\\Test\\\\\\\\doesnotexist.ts\\\\` as any)).rejects.toThrow();
expect(import(`\\\\Test\\doesnotexist.ts\\\\` as any)).rejects.toThrow();
expect(import(`\\\\\\Test\\doesnotexist.ts\\\\` as any)).rejects.toThrow();
});
describe("NODE_PATH test", () => {
const prepareTest = () => {
const tempDir = tempDirWithFiles("node_path", {
"modules/node_modules/node-path-test/index.js": "exports.testValue = 'NODE_PATH works';",
"modules/node_modules/node-path-test/package.json": JSON.stringify({
name: "node-path-test",
version: "1.0.0",
description: "A node_path test module",
main: "index.js",
}),
"lib/node_modules/node-path-test/index.js": "exports.testValue = 'NODE_PATH from lib works';",
"lib/node_modules/node-path-test/package.json": JSON.stringify({
name: "node-path-test",
version: "1.0.0",
description: "A node_path test module from lib",
main: "index.js",
}),
"test/index.js": "const { testValue } = require('node-path-test');\nconsole.log(testValue);",
});
const nodePath = joinP(tempDir, "modules/node_modules");
const nodePathLib = joinP(tempDir, "lib/node_modules");
const testDir = joinP(tempDir, "test");
const delimiter = isWindows ? ";" : ":";
return {
tempDir,
nodePath,
nodePathLib,
testDir,
delimiter,
};
};
it("should resolve modules from NODE_PATH", () => {
const { nodePath, testDir } = prepareTest();
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), "--no-install", "index.js"],
env: { ...bunEnv, NODE_PATH: nodePath },
cwd: testDir,
});
expect(exitCode).toBe(0);
expect(stdout.toString().trim()).toBe("NODE_PATH works");
});
it("should resolve modules from NODE_PATH entries", () => {
const { nodePath, testDir, delimiter } = prepareTest();
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), "--no-install", "index.js"],
env: { ...bunEnv, NODE_PATH: [nodePath].join(delimiter) },
cwd: testDir,
});
expect(exitCode).toBe(0);
expect(stdout.toString().trim()).toBe("NODE_PATH works");
});
it("should resolve first matched module from NODE_PATH entries", () => {
const { nodePath, nodePathLib, testDir, delimiter } = prepareTest();
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), "--no-install", "index.js"],
env: { ...bunEnv, NODE_PATH: ["/a/path/not/exist", nodePathLib, nodePath].join(delimiter) },
cwd: testDir,
});
expect(exitCode).toBe(0);
expect(stdout.toString().trim()).toBe("NODE_PATH from lib works");
});
});
it("can resolve with source directories that do not exist", () => {
// In Nuxt/Vite, the following call happens:
// `require("module").createRequire("file:///Users/clo/my-nuxt-app/@vue/server-renderer")("vue")`
// This seems to be a bug in their code, not using a concrete file path for
// this virtual module, such as 'node_modules/@vue/server-renderer/index.js',
// but the same exact resolution happens and succeeds in Node.js
const dir = tempDirWithFiles("resolve", {
"node_modules/vue/index.js": "export default 123;",
"test.js": `
const { createRequire } = require('module');
const assert = require('assert');
const req = createRequire(import.meta.url + '/@vue/server-renderer');
assert.strictEqual(req('vue').default, 123);
`,
});
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), "test.js"],
env: bunEnv,
cwd: dir,
stdio: ["ignore", "inherit", "inherit"],
});
expect(exitCode).toBe(0);
});
describe("When CJS and ESM are mixed", () => {
const fixturePath = fixture("tsyringe.ts");
// https://github.com/oven-sh/bun/issues/4677
it("loads reflect-metadata before tsyringe", async () => {
const { stderr } = bunRun(fixturePath);
expect(stderr).toBeEmpty();
});
});