From 898c4fefe86ecfa7017d337ebfab851840abf009 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 12 May 2026 03:43:15 +0000 Subject: [PATCH 1/3] init: scaffold bunfig.toml with minimumReleaseAge by default bun init now writes a bunfig.toml with [install].minimumReleaseAge set to 86400 (1 day) for the blank, library, and react templates. An existing bunfig.toml is never overwritten, and --minimal skips it. --- docs/quickstart.mdx | 1 + docs/runtime/templating/init.mdx | 2 + src/cli/init/bunfig.default.toml | 5 ++ src/cli/init/react-app/bunfig.toml | 8 +++- src/cli/init/react-shadcn/bunfig.toml | 6 +++ src/cli/init/react-tailwind/bunfig.toml | 7 ++- src/cli/init_command.zig | 10 ++++ test/cli/init/init.test.ts | 64 +++++++++++++++++++++++++ 8 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 src/cli/init/bunfig.default.toml diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index 451b588feaa..3a28a2acba9 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -33,6 +33,7 @@ Build a minimal HTTP server with `Bun.serve`, run it locally, then evolve it by - .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc -> CLAUDE.md - index.ts - tsconfig.json (for editor autocomplete) + - bunfig.toml - README.md ``` diff --git a/docs/runtime/templating/init.mdx b/docs/runtime/templating/init.mdx index 602fb911a6b..bec244f6f12 100644 --- a/docs/runtime/templating/init.mdx +++ b/docs/runtime/templating/init.mdx @@ -24,6 +24,7 @@ bun init my-app + .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc -> CLAUDE.md + index.ts + tsconfig.json (for editor autocomplete) + + bunfig.toml + README.md ``` @@ -41,6 +42,7 @@ It creates: - a `package.json` file with a name that defaults to the current directory name - a `tsconfig.json` file or a `jsconfig.json` file, depending if the entry point is a TypeScript file or not +- a `bunfig.toml` file with [`minimumReleaseAge`](/pm/cli/install#minimum-release-age) enabled to protect against supply-chain attacks - an entry point which defaults to `index.ts` unless any of `index.{tsx, jsx, js, mts, mjs}` exist or the `package.json` specifies a `module` or `main` field - a `README.md` file diff --git a/src/cli/init/bunfig.default.toml b/src/cli/init/bunfig.default.toml new file mode 100644 index 00000000000..4cf82aa5c41 --- /dev/null +++ b/src/cli/init/bunfig.default.toml @@ -0,0 +1,5 @@ +[install] +# Protect against supply-chain attacks by rejecting package +# versions published in the last 86400 seconds (1 day). +# https://bun.com/docs/pm/cli/install#minimum-release-age +minimumReleaseAge = 86400 diff --git a/src/cli/init/react-app/bunfig.toml b/src/cli/init/react-app/bunfig.toml index 9819bf6de16..de707c72240 100644 --- a/src/cli/init/react-app/bunfig.toml +++ b/src/cli/init/react-app/bunfig.toml @@ -1,2 +1,8 @@ +[install] +# Protect against supply-chain attacks by rejecting package +# versions published in the last 86400 seconds (1 day). +# https://bun.com/docs/pm/cli/install#minimum-release-age +minimumReleaseAge = 86400 + [serve.static] -env = "BUN_PUBLIC_*" \ No newline at end of file +env = "BUN_PUBLIC_*" diff --git a/src/cli/init/react-shadcn/bunfig.toml b/src/cli/init/react-shadcn/bunfig.toml index 0175d44e1dc..bb21d331929 100644 --- a/src/cli/init/react-shadcn/bunfig.toml +++ b/src/cli/init/react-shadcn/bunfig.toml @@ -1,3 +1,9 @@ +[install] +# Protect against supply-chain attacks by rejecting package +# versions published in the last 86400 seconds (1 day). +# https://bun.com/docs/pm/cli/install#minimum-release-age +minimumReleaseAge = 86400 + [serve.static] plugins = ["bun-plugin-tailwind"] env = "BUN_PUBLIC_*" diff --git a/src/cli/init/react-tailwind/bunfig.toml b/src/cli/init/react-tailwind/bunfig.toml index 8877354c1f7..bb21d331929 100644 --- a/src/cli/init/react-tailwind/bunfig.toml +++ b/src/cli/init/react-tailwind/bunfig.toml @@ -1,4 +1,9 @@ +[install] +# Protect against supply-chain attacks by rejecting package +# versions published in the last 86400 seconds (1 day). +# https://bun.com/docs/pm/cli/install#minimum-release-age +minimumReleaseAge = 86400 [serve.static] plugins = ["bun-plugin-tailwind"] -env = "BUN_PUBLIC_*" \ No newline at end of file +env = "BUN_PUBLIC_*" diff --git a/src/cli/init_command.zig b/src/cli/init_command.zig index 61480d539dc..e2d5e00c212 100644 --- a/src/cli/init_command.zig +++ b/src/cli/init_command.zig @@ -223,6 +223,7 @@ pub const InitCommand = struct { // "known" assets const @".gitignore" = @embedFile("init/gitignore.default"); const @"tsconfig.json" = @embedFile("init/tsconfig.default.json"); + const @"bunfig.toml" = @embedFile("init/bunfig.default.toml"); const @"README.md" = @embedFile("init/README.default.md"); const @"README2.md" = @embedFile("init/README2.default.md"); @@ -621,6 +622,7 @@ pub const InitCommand = struct { write_gitignore: bool, write_package_json: bool, write_tsconfig: bool, + write_bunfig: bool, write_readme: bool, }; @@ -628,10 +630,12 @@ pub const InitCommand = struct { .write_package_json = true, .write_tsconfig = true, .write_gitignore = !minimal, + .write_bunfig = !minimal, .write_readme = !minimal, }; steps.write_gitignore = steps.write_gitignore and !existsZ(".gitignore"); + steps.write_bunfig = steps.write_bunfig and !existsZ("bunfig.toml"); steps.write_readme = steps.write_readme and !existsZ("README.md") and !existsZ("README") and !existsZ("README.txt") and !existsZ("README.mdx"); steps.write_tsconfig = brk: { @@ -822,6 +826,12 @@ pub const InitCommand = struct { } } + if (steps.write_bunfig) { + Assets.create("bunfig.toml", .{}) catch { + // suppressed + }; + } + if (steps.write_readme) { Assets.create("README.md", .{ .name = fields.name, diff --git a/test/cli/init/init.test.ts b/test/cli/init/init.test.ts index 06c3dce20af..eb32662bf20 100644 --- a/test/cli/init/init.test.ts +++ b/test/cli/init/init.test.ts @@ -38,6 +38,7 @@ import path from "path"; expect(fs.existsSync(path.join(temp, ".gitignore"))).toBe(true); expect(fs.existsSync(path.join(temp, "node_modules"))).toBe(true); expect(fs.existsSync(path.join(temp, "tsconfig.json"))).toBe(true); + expect(fs.existsSync(path.join(temp, "bunfig.toml"))).toBe(true); }, 30_000); test("bun init with piped cli", async () => { @@ -74,6 +75,7 @@ import path from "path"; expect(fs.existsSync(path.join(temp, ".gitignore"))).toBe(true); expect(fs.existsSync(path.join(temp, "node_modules"))).toBe(true); expect(fs.existsSync(path.join(temp, "tsconfig.json"))).toBe(true); + expect(fs.existsSync(path.join(temp, "bunfig.toml"))).toBe(true); }, 30_000); test("bun init in folder", async () => { @@ -99,6 +101,7 @@ import path from "path"; ".gitignore", "README.md", "bun.lock", + "bunfig.toml", "index.ts", "node_modules", "package.json", @@ -138,6 +141,7 @@ import path from "path"; ".gitignore", "README.md", "bun.lock", + "bunfig.toml", "index.ts", "node_modules", "package.json", @@ -161,6 +165,7 @@ import path from "path"; ".gitignore", "README.md", "bun.lock", + "bunfig.toml", "index.ts", "node_modules", "package.json", @@ -170,6 +175,7 @@ import path from "path"; await Bun.write(path.join(temp, "mydir/index.ts"), "my edited index.ts"); await Bun.write(path.join(temp, "mydir/README.md"), "my edited README.md"); await Bun.write(path.join(temp, "mydir/.gitignore"), "my edited .gitignore"); + await Bun.write(path.join(temp, "mydir/bunfig.toml"), "# my edited bunfig.toml"); await Bun.write( path.join(temp, "mydir/package.json"), JSON.stringify({ @@ -196,6 +202,7 @@ import path from "path"; ".gitignore", "README.md", "bun.lock", + "bunfig.toml", "index.ts", "node_modules", "package.json", @@ -205,6 +212,7 @@ import path from "path"; expect(await Bun.file(path.join(temp, "mydir/index.ts")).text()).toMatchInlineSnapshot(`"my edited index.ts"`); expect(await Bun.file(path.join(temp, "mydir/README.md")).text()).toMatchInlineSnapshot(`"my edited README.md"`); expect(await Bun.file(path.join(temp, "mydir/.gitignore")).text()).toMatchInlineSnapshot(`"my edited .gitignore"`); + expect(await Bun.file(path.join(temp, "mydir/bunfig.toml")).text()).toMatchInlineSnapshot(`"# my edited bunfig.toml"`); expect(await Bun.file(path.join(temp, "mydir/package.json")).json()).toMatchInlineSnapshot(` { "devDependencies": { @@ -324,5 +332,61 @@ import path from "path"; expect(fs.existsSync(path.join(temp, "README.md"))).toBe(false); expect(fs.existsSync(path.join(temp, "CLAUDE.md"))).toBe(false); expect(fs.existsSync(path.join(temp, ".cursor"))).toBe(false); + expect(fs.existsSync(path.join(temp, "bunfig.toml"))).toBe(false); }); + + test("bun init writes bunfig.toml with minimumReleaseAge", async () => { + const temp = tempDirWithFiles("bun-init-bunfig", {}); + + const { exited } = Bun.spawn({ + cmd: [bunExe(), "init", "-y"], + cwd: temp, + stdio: ["ignore", "inherit", "inherit"], + env: bunEnv, + }); + + expect(await exited).toBe(0); + + const bunfig = fs.readFileSync(path.join(temp, "bunfig.toml"), "utf8"); + expect(bunfig).toContain("[install]"); + expect(bunfig).toContain("minimumReleaseAge"); + }, 30_000); + + test("bun init does not overwrite existing bunfig.toml", async () => { + const temp = tempDirWithFiles("bun-init-bunfig-exists", { + "bunfig.toml": "# existing\n", + }); + + const { exited } = Bun.spawn({ + cmd: [bunExe(), "init", "-y"], + cwd: temp, + stdio: ["ignore", "inherit", "inherit"], + env: bunEnv, + }); + + expect(await exited).toBe(0); + + const bunfig = fs.readFileSync(path.join(temp, "bunfig.toml"), "utf8"); + expect(bunfig).toBe("# existing\n"); + }, 30_000); + + for (const flag of ["--react", "--react=tailwind", "--react=shadcn"]) { + test(`bun init ${flag} bunfig.toml has minimumReleaseAge`, async () => { + const temp = tempDirWithFiles(`bun-init-bunfig-${flag.replaceAll(/[^a-z]/g, "-")}`, {}); + + const { exited } = Bun.spawn({ + cmd: [bunExe(), "init", flag], + cwd: temp, + stdio: ["ignore", "inherit", "inherit"], + env: bunEnv, + }); + + expect(await exited).toBe(0); + + const bunfig = fs.readFileSync(path.join(temp, "bunfig.toml"), "utf8"); + expect(bunfig).toContain("[install]"); + expect(bunfig).toContain("minimumReleaseAge"); + expect(bunfig).toContain("[serve.static]"); + }, 30_000); + } }); From 9ebaeac2b9cbbc37fd5754885404fa4cd9d40569 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 03:45:15 +0000 Subject: [PATCH 2/3] [autofix.ci] apply automated fixes --- test/cli/init/init.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/cli/init/init.test.ts b/test/cli/init/init.test.ts index eb32662bf20..8d4539bd29f 100644 --- a/test/cli/init/init.test.ts +++ b/test/cli/init/init.test.ts @@ -212,7 +212,9 @@ import path from "path"; expect(await Bun.file(path.join(temp, "mydir/index.ts")).text()).toMatchInlineSnapshot(`"my edited index.ts"`); expect(await Bun.file(path.join(temp, "mydir/README.md")).text()).toMatchInlineSnapshot(`"my edited README.md"`); expect(await Bun.file(path.join(temp, "mydir/.gitignore")).text()).toMatchInlineSnapshot(`"my edited .gitignore"`); - expect(await Bun.file(path.join(temp, "mydir/bunfig.toml")).text()).toMatchInlineSnapshot(`"# my edited bunfig.toml"`); + expect(await Bun.file(path.join(temp, "mydir/bunfig.toml")).text()).toMatchInlineSnapshot( + `"# my edited bunfig.toml"`, + ); expect(await Bun.file(path.join(temp, "mydir/package.json")).json()).toMatchInlineSnapshot(` { "devDependencies": { From 381666dbf0ebc543153b80bf8eb9641c199e3bc3 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 12 May 2026 04:20:18 +0000 Subject: [PATCH 3/3] test: assert exact minimumReleaseAge value in init.test.ts --- test/cli/init/init.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cli/init/init.test.ts b/test/cli/init/init.test.ts index 8d4539bd29f..e28d22fdec2 100644 --- a/test/cli/init/init.test.ts +++ b/test/cli/init/init.test.ts @@ -351,7 +351,7 @@ import path from "path"; const bunfig = fs.readFileSync(path.join(temp, "bunfig.toml"), "utf8"); expect(bunfig).toContain("[install]"); - expect(bunfig).toContain("minimumReleaseAge"); + expect(bunfig).toContain("minimumReleaseAge = 86400"); }, 30_000); test("bun init does not overwrite existing bunfig.toml", async () => { @@ -387,7 +387,7 @@ import path from "path"; const bunfig = fs.readFileSync(path.join(temp, "bunfig.toml"), "utf8"); expect(bunfig).toContain("[install]"); - expect(bunfig).toContain("minimumReleaseAge"); + expect(bunfig).toContain("minimumReleaseAge = 86400"); expect(bunfig).toContain("[serve.static]"); }, 30_000); }