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) " `); });