Files
bun.sh/test/bundler/cli.test.ts
Jarred Sumner 0b549321e9 Start using test.concurrent in our tests (#22823)
### 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 Bot <claude-bot@bun.sh>
2025-09-22 05:30:34 -07:00

422 lines
12 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, isWindows, tempDir, tmpdirSync } from "harness";
import fs, { mkdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
import path, { join } from "node:path";
describe.concurrent(
"bun build",
() => {
test("warnings dont return exit code 1", async () => {
const { stderr, exited } = Bun.spawn({
cmd: [bunExe(), "build", path.join(import.meta.dir, "./fixtures/jsx-warning/index.jsx")],
env: bunEnv,
stderr: "pipe",
});
expect(await exited).toBe(0);
expect(await stderr.text()).toContain(
'warn: "key" prop after a {...spread} is deprecated in JSX. Falling back to classic runtime.',
);
});
test("generating a standalone binary in nested path, issue #4195", async () => {
async function testCompile(outfile: string) {
const { exited } = Bun.spawn({
cmd: [
bunExe(),
"build",
path.join(import.meta.dir, "./fixtures/trivial/index.js"),
"--compile",
"--outfile",
outfile,
],
env: bunEnv,
stdout: "inherit",
stderr: "inherit",
});
expect(await exited).toBe(0);
}
async function testExec(outfile: string) {
const { exited, stderr } = Bun.spawn({
cmd: [outfile],
env: bunEnv,
stdout: "inherit",
stderr: "pipe",
});
expect(await stderr.text()).toBeEmpty();
expect(await exited).toBe(0);
}
const tmpdir = tmpdirSync();
{
const baseDir = `${tmpdir}/bun-build-outfile-${Date.now()}`;
const outfile = path.join(baseDir, "index.exe");
await testCompile(outfile);
await testExec(outfile);
fs.rmSync(baseDir, { recursive: true, force: true });
}
{
const baseDir = `${tmpdir}/bun-build-outfile2-${Date.now()}`;
const outfile = path.join(baseDir, "b/u/n", "index.exe");
await testCompile(outfile);
await testExec(outfile);
fs.rmSync(baseDir, { recursive: true, force: true });
}
});
test("works with utf8 bom", async () => {
const tmp = tmpdirSync();
const src = path.join(tmp, "index.js");
fs.writeFileSync(src, '\ufeffconsole.log("hello world");', { encoding: "utf8" });
const { exited } = Bun.spawn({
cmd: [bunExe(), "build", src],
env: bunEnv,
stdout: "inherit",
stderr: "inherit",
});
expect(await exited).toBe(0);
});
test("--tsconfig-override works", async () => {
const tmp = tmpdirSync();
const baseDir = path.join(tmp, "tsconfig-override-test");
fs.mkdirSync(baseDir, { recursive: true });
fs.writeFileSync(
path.join(baseDir, "index.ts"),
`import { utils } from "@utils/helper";
console.log(utils());`,
);
fs.writeFileSync(path.join(baseDir, "helper.ts"), `export function utils() { return "Hello from utils"; }`);
fs.writeFileSync(
path.join(baseDir, "tsconfig.json"),
JSON.stringify({
compilerOptions: {
paths: {
"@wrong/*": ["./wrong/*"],
},
},
}),
);
fs.writeFileSync(
path.join(baseDir, "custom-tsconfig.json"),
JSON.stringify({
compilerOptions: {
paths: {
"@utils/*": ["./*"],
},
},
}),
);
const failResult = Bun.spawn({
cmd: [bunExe(), "build", path.join(baseDir, "index.ts"), "--outdir", path.join(baseDir, "out-fail")],
env: bunEnv,
cwd: baseDir,
stderr: "pipe",
});
expect(await failResult.exited).not.toBe(0);
expect(await failResult.stderr?.text()).toContain("Could not resolve");
const successResult = Bun.spawn({
cmd: [
bunExe(),
"build",
path.join(baseDir, "index.ts"),
"--tsconfig-override",
path.join(baseDir, "custom-tsconfig.json"),
"--outdir",
path.join(baseDir, "out-success"),
],
env: bunEnv,
cwd: baseDir,
stderr: "pipe",
});
expect(await successResult.exited).toBe(0);
const outputFile = path.join(baseDir, "out-success", "index.js");
expect(fs.existsSync(outputFile)).toBe(true);
const output = fs.readFileSync(outputFile, "utf8");
expect(output).toContain("Hello from utils");
});
test("--tsconfig-override works from nested directories", async () => {
const tmp = tmpdirSync();
const baseDir = path.join(tmp, "tsconfig-nested-test");
const nestedDir = path.join(baseDir, "nested", "deep");
fs.mkdirSync(nestedDir, { recursive: true });
fs.writeFileSync(
path.join(nestedDir, "index.ts"),
`import { utils } from "@utils/helper";
console.log(utils());`,
);
fs.writeFileSync(path.join(baseDir, "helper.ts"), `export function utils() { return "Hello from nested!"; }`);
fs.writeFileSync(
path.join(baseDir, "custom-tsconfig.json"),
JSON.stringify({
compilerOptions: {
paths: {
"@utils/*": ["./*"],
},
},
}),
);
const result = Bun.spawn({
cmd: [bunExe(), "build", "index.ts", "--tsconfig-override", "../../custom-tsconfig.json", "--outdir", "out"],
env: bunEnv,
cwd: nestedDir,
});
expect(await result.exited).toBe(0);
const outputFile = path.join(nestedDir, "out", "index.js");
expect(fs.existsSync(outputFile)).toBe(true);
const output = fs.readFileSync(outputFile, "utf8");
expect(output).toContain("Hello from nested!");
});
test("__dirname and __filename are printed correctly", async () => {
using baseDirPath = tempDir("bun-build-dirname-filename", {
"我": {
"我.ts": "console.log(__dirname); console.log(__filename);",
},
});
const baseDir = baseDirPath + "";
const { exited } = Bun.spawn({
cmd: [
bunExe(),
"build",
path.join(baseDir, "我/我.ts"),
"--compile",
"--outfile",
path.join(baseDir, "exe.exe"),
],
env: bunEnv,
cwd: baseDir,
stdout: "inherit",
stderr: "inherit",
});
expect(await exited).toBe(0);
const { stdout } = Bun.spawn({
cmd: [path.join(baseDir, "exe.exe")],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const text = await stdout.text();
expect(text).toContain(path.join(baseDir, "我") + "\n");
expect(text).toContain(path.join(baseDir, "我", "我.ts") + "\n");
});
test.skipIf(!isWindows)("should be able to handle pretty path when using pnpm + #14685", async () => {
// this test code follows the same structure as and
// is based on the code for testing issue 4893
let testDir = tmpdirSync();
// Clean up from prior runs if necessary
rmSync(testDir, { recursive: true, force: true });
// Create a directory with our test file
mkdirSync(testDir, { recursive: true });
writeFileSync(
join(testDir, "index.ts"),
"import chalk from \"chalk\"; export function main() { console.log(chalk.red('Hello, World!')); }",
);
writeFileSync(
join(testDir, "package.json"),
`
{
"dependencies": {
"chalk": "^5.3.0"
}
}`,
);
testDir = realpathSync(testDir);
await Bun.spawn({
cmd: [bunExe(), "x", "pnpm@9", "i"],
env: bunEnv,
stderr: "pipe",
cwd: testDir,
}).exited;
// bun build --entrypoints ./index.ts --outdir ./dist --target node
const { stderr, exited } = Bun.spawn({
cmd: [
bunExe(),
"build",
"--entrypoints",
join(testDir, "index.ts"),
"--outdir",
join(testDir, "dist"),
"--target",
"node",
],
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
expect(await stderr.text()).toBe("");
expect(await exited).toBe(0);
});
},
10_000,
);
test.skipIf(!isWindows)("should be able to handle pretty path on windows #13897", async () => {
// this test code follows the same structure as and
// is based on the code for testing issue 4893
let testDir = tmpdirSync();
// Clean up from prior runs if necessary
rmSync(testDir, { recursive: true, force: true });
// Create a directory with our test file
mkdirSync(testDir, { recursive: true });
writeFileSync(
join(testDir, "index.ts"),
"import chalk from \"chalk\"; export function main() { console.log(chalk.red('Hello, World!')); }",
);
writeFileSync(join(testDir, "chalk.ts"), "function red(value){ consol.error(value); } export default { red };");
testDir = realpathSync(testDir);
// bun build --entrypoints ./index.ts --outdir ./dist --target node
const buildOut = await Bun.build({
entrypoints: [join(testDir, "index.ts")],
outdir: join(testDir, "dist"),
minify: true,
sourcemap: "linked",
plugins: [
{
name: "My windows plugin",
async setup(build) {
build.onResolve({ filter: /chalk/ }, () => ({ path: join(testDir, "chalk.ts").replaceAll("/", "\\") }));
},
},
],
});
expect(buildOut?.success).toBe(true);
});
test("you can use --outfile=... and --sourcemap", async () => {
const tmpdir = tmpdirSync();
const inputFile = path.join(tmpdir, "input.js");
const outFile = path.join(tmpdir, "out.js");
writeFileSync(inputFile, 'console.log("Hello, world!");');
const originalContent = fs.readFileSync(inputFile, "utf8");
const { exited, stdout } = Bun.spawn({
cmd: [bunExe(), "build", "--outfile=" + path.relative(tmpdir, outFile), "--sourcemap", inputFile],
env: bunEnv,
cwd: tmpdir,
stdout: "pipe",
stderr: "pipe",
});
expect(await exited).toBe(0);
// Verify that the input file wasn't overwritten
expect(fs.readFileSync(inputFile, "utf8")).toBe(originalContent);
// Verify that the output file was created
expect(fs.existsSync(outFile)).toBe(true);
// Verify that the sourcemap file was created
expect(fs.existsSync(outFile + ".map")).toBe(true);
// Verify that the output file contains sourceMappingURL comment
const outputContent = fs.readFileSync(outFile, "utf8");
expect(outputContent).toContain("//# sourceMappingURL=out.js.map");
expect((await stdout.text()).replace(/\d{1,}ms/, "0.000000001ms")).toMatchInlineSnapshot(`
"Bundled 1 module in 0.000000001ms
out.js 120 bytes (entry point)
out.js.map 213 bytes (source map)
"
`);
});
test("some log cases", async () => {
const tmpdir = tmpdirSync();
const inputFile = path.join(tmpdir, "input.js");
const outFile = path.join(tmpdir, "out.js");
writeFileSync(inputFile, 'console.log("Hello, world!");');
// absolute path
const { exited, stdout } = Bun.spawn({
cmd: [bunExe(), "build", "--outfile=" + outFile, "--sourcemap", inputFile],
env: bunEnv,
cwd: tmpdir,
});
expect(await exited).toBe(0);
expect((await stdout.text()).replace(/in \d+ms/g, "in {time}ms")).toMatchInlineSnapshot(`
"Bundled 1 module in {time}ms
out.js 120 bytes (entry point)
out.js.map 213 bytes (source map)
"
`);
});
test("log case 1", async () => {
const tmpdir = tmpdirSync();
const inputFile = path.join(tmpdir, "input.js");
const inputFile2 = path.join(tmpdir, "input-twooo.js");
writeFileSync(inputFile, 'console.log("Hello, world!");');
writeFileSync(inputFile2, 'console.log("Hello, world!");');
const { exited, stdout } = Bun.spawn({
cmd: [bunExe(), "build", "--outdir=" + tmpdir + "/out", inputFile, inputFile2],
env: bunEnv,
cwd: tmpdir,
});
expect(await exited).toBe(0);
expect((await stdout.text()).replace(/in \d+ms/g, "in {time}ms")).toMatchInlineSnapshot(`
"Bundled 2 modules in {time}ms
input.js 42 bytes (entry point)
input-twooo.js 48 bytes (entry point)
"
`);
});
test("log case 2", async () => {
const tmpdir = tmpdirSync();
const inputFile = path.join(tmpdir, "input.js");
writeFileSync(inputFile, 'console.log("Hello, world!");');
const { exited, stdout } = Bun.spawn({
cmd: [bunExe(), "build", "--outdir=" + tmpdir + "/out", inputFile],
env: bunEnv,
cwd: tmpdir,
});
expect(await exited).toBe(0);
expect((await stdout.text()).replace(/in \d+ms/g, "in {time}ms")).toMatchInlineSnapshot(`
"Bundled 1 module in {time}ms
input.js 42 bytes (entry point)
"
`);
});