Skip to content

init: scaffold bunfig.toml with minimumReleaseAge by default#30532

Closed
robobun wants to merge 3 commits into
mainfrom
farm/973519b2/init-minimum-release-age
Closed

init: scaffold bunfig.toml with minimumReleaseAge by default#30532
robobun wants to merge 3 commits into
mainfrom
farm/973519b2/init-minimum-release-age

Conversation

@robobun

@robobun robobun commented May 12, 2026

Copy link
Copy Markdown
Collaborator

What

bun init now writes a bunfig.toml with [install].minimumReleaseAge enabled for all templates:

[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
  • Blank / Library: new bunfig.toml is created (previously none was written)
  • React / Tailwind / Shadcn: existing bunfig.toml templates gain the [install] section above their [serve.static] section
  • --minimal: skipped, consistent with .gitignore / README.md
  • An existing bunfig.toml is never overwritten

The file is written before the scaffolding bun install runs, so the initial install is also protected.

How

  • src/cli/init/bunfig.default.toml — new default asset
  • src/cli/init/react-{app,tailwind,shadcn}/bunfig.toml — added [install] section
  • src/cli/init_command.zig — register asset, add write_bunfig step (gated on !minimal and !existsZ("bunfig.toml"))
  • docs/runtime/templating/init.mdx, docs/quickstart.mdx — list bunfig.toml in generated files

Tests

test/cli/init/init.test.ts — 15 pass, 0 fail. New cases:

  • bun init writes bunfig.toml containing [install] + minimumReleaseAge
  • existing bunfig.toml is not overwritten
  • --minimal does not create bunfig.toml
  • --react / --react=tailwind / --react=shadcn bunfig includes both [install] and [serve.static]
  • existing snapshot tests updated to include bunfig.toml in directory listings

Notes

The default value is 86400 seconds (1 day). Happy to change to 2 days (aligning with #30529) or 3 days (matching the docs examples) — it's a one-line change to the template files.

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.
@robobun

robobun commented May 12, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 9:26 PM PT - May 11th, 2026

@robobun, your commit 381666d has 1 failures in Build #53593 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 30532

That installs a local version of the PR into your bun-30532 executable, so you can run:

bun-30532 --bun

@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The PR adds supply-chain protection to bun init by generating bunfig.toml with a default minimumReleaseAge = 86400 setting. This instructs Bun to reject packages published within the last 24 hours during installation, mitigating supply-chain attack risks across all project templates.

Changes

bunfig.toml supply-chain protection

Layer / File(s) Summary
Config templates with minimumReleaseAge
src/cli/init/bunfig.default.toml, src/cli/init/react-app/bunfig.toml, src/cli/init/react-shadcn/bunfig.toml, src/cli/init/react-tailwind/bunfig.toml
Default and React variant templates define a new [install] section with minimumReleaseAge = 86400 to enforce a minimum 1-day package age before installation.
Init command asset embedding and write logic
src/cli/init_command.zig
Embeds bunfig.toml as a default asset in the Assets struct, adds a write_bunfig flag to the init steps that is disabled for --minimal mode and when the file already exists, and implements conditional file creation during project initialization.
Init test updates and new test scenarios
test/cli/init/init.test.ts
Existing init tests now assert bunfig.toml is created and included in snapshots. New coverage verifies that --minimal mode does not generate the file, that the file contains minimumReleaseAge, that existing files are not overwritten with their contents preserved, and that the setting is applied consistently across --react, --react=tailwind, and --react=shadcn templates.
Documentation updates
docs/quickstart.mdx, docs/runtime/templating/init.mdx
Quickstart and init documentation now list bunfig.toml as a generated file and describe its role in enforcing minimum package release age for supply-chain protection.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: scaffolding bunfig.toml with minimumReleaseAge as a default behavior for bun init.
Description check ✅ Passed The description thoroughly covers both required sections: detailed 'What' explanation with code example and multiple implementation details, plus comprehensive 'How' and 'Tests' sections demonstrating verification.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. feat: set default minimumReleaseAge to 3 days (259200s) #28729 - Both PRs aim to enable minimumReleaseAge by default for new projects; feat: set default minimumReleaseAge to 3 days (259200s) #28729 sets a runtime default in PackageManagerOptions while this PR scaffolds the value into bunfig.toml during bun init

🤖 Generated with Claude Code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/cli/init/init.test.ts`:
- Around line 352-355: The test currently only checks for the presence of the
minimumReleaseAge key in the generated bunfig, which is brittle; update the
assertions in the init test (the block that reads bunfig.toml and the similar
block at the other location) to assert the exact default value instead of just
the key — for example replace expect(bunfig).toContain("minimumReleaseAge") with
an equality or contains check that matches the full setting like
expect(bunfig).toContain("minimumReleaseAge = 86400") or use a regex to assert
the numeric value 86400 so the contract is locked to that default.
- Around line 375-393: Replace the manual for...of loop that creates tests for
each flag with a parameterized describe.each([...]) block so each value in
["--react", "--react=tailwind", "--react=shadcn"] becomes a separate sub-suite;
move the existing test body (which uses tempDirWithFiles, Bun.spawn with cmd
[bunExe(), "init", flag], reading bunfig via fs.readFileSync and assertions for
"[install]", "minimumReleaseAge", and "[serve.static]") into the parameterized
test callback and reference the parameter (flag) instead of the loop variable;
ensure the test timeout (30_000) and env bunEnv/stdio settings remain unchanged
and that the test name interpolates the flag for clarity.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 62cfbec6-b566-4b62-a95d-1215d8bbc8f9

📥 Commits

Reviewing files that changed from the base of the PR and between 37bfbed and 9ebaeac.

📒 Files selected for processing (8)
  • docs/quickstart.mdx
  • docs/runtime/templating/init.mdx
  • src/cli/init/bunfig.default.toml
  • src/cli/init/react-app/bunfig.toml
  • src/cli/init/react-shadcn/bunfig.toml
  • src/cli/init/react-tailwind/bunfig.toml
  • src/cli/init_command.zig
  • test/cli/init/init.test.ts

Comment thread test/cli/init/init.test.ts
Comment thread test/cli/init/init.test.ts

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation looks clean and follows the existing .gitignore/README.md pattern, but this changes the default bun init output for every new project and the 1d vs 2d vs 3d default is still an open question (plus the alternative approach in #28729), so a maintainer should sign off — CI is also currently red on build-zig/build-cpp.

Extended reasoning...

Overview

This PR makes bun init scaffold a bunfig.toml with [install].minimumReleaseAge = 86400 for all non-minimal templates. It touches: a new src/cli/init/bunfig.default.toml asset, three existing React template bunfig.toml files, ~10 lines in src/cli/init_command.zig (new embedded asset + write_bunfig step that mirrors the existing .gitignore/README handling), two doc pages, and test/cli/init/init.test.ts (snapshot updates + 5 new cases).

Security risks

None introduced by the code itself — it writes a static embedded file to a freshly scaffolded project directory and never overwrites an existing bunfig.toml. No injection, auth, or path-handling concerns. The intent of the change is itself a security hardening (supply-chain protection).

Level of scrutiny

Medium-high. The Zig and template changes are mechanical and low-risk in isolation, but this is a user-facing product decision: every bun init will now produce an extra file and every scaffolded project will reject packages published in the last 24h. The author explicitly flags the default value (1d / 2d / 3d) as an open question, and the duplicate-PR bot points to #28729 which solves the same problem via a runtime default rather than a scaffolded file. A maintainer should decide which approach (and which value) is preferred.

Other factors

  • CI is red on commit 898c4fe — build-zig and build-cpp are failing across all targets per robobun. The subsequent autofix.ci commit may or may not resolve this, but it should be green before merge.
  • CodeRabbit left two minor nits (assert exact 86400 value; use describe.each). Neither is blocking.
  • Test coverage for the new behavior is good (writes file, respects existing file, skips on --minimal, present in all three React variants).
  • The bunfig.toml is written before the scaffolding bun install runs in the blank/library path, so the initial install is covered — but note that in the React path (write files and run bun dev), bunfig.toml is written via createNew (O_EXCL) as the first template file, which also precedes the install, so that's consistent.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
test/cli/init/init.test.ts (1)

375-393: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use describe.each() for the React flag parameterization.

The manual for...of loop should be converted to describe.each() to match the test style guide.

♻️ Suggested refactor
-  for (const flag of ["--react", "--react=tailwind", "--react=shadcn"]) {
-    test(`bun init ${flag} bunfig.toml has minimumReleaseAge`, async () => {
+  describe.each(["--react", "--react=tailwind", "--react=shadcn"])("bun init %s", flag => {
+    test("bunfig.toml has minimumReleaseAge", async () => {
       const temp = tempDirWithFiles(`bun-init-bunfig-${flag.replaceAll(/[^a-z]/g, "-")}`, {});
@@
       expect(bunfig).toContain("[serve.static]");
     }, 30_000);
-  }
+  });
As per coding guidelines: "Use describe.each() for parameterized tests".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/cli/init/init.test.ts` around lines 375 - 393, Replace the manual
for...of loop that creates tests for each React flag with a describe.each([...])
block so tests follow the style guide; use the same flag array ["--react",
"--react=tailwind", "--react=shadcn"] in describe.each, move the test body that
calls tempDirWithFiles, Bun.spawn (cmd: [bunExe(), "init", flag]), awaits
exited, reads bunfig via fs.readFileSync(path.join(temp, "bunfig.toml"), "utf8")
and asserts the same expectations (contains "[install]", "minimumReleaseAge =
86400", "[serve.static]") into the parameterized it/test callback, and preserve
the 30_000 timeout and test name template (e.g., `bun init ${flag} bunfig.toml
has minimumReleaseAge`).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/cli/init/init.test.ts`:
- Around line 343-351: Change each Bun.spawn call that currently sets stdio to
["ignore","inherit","inherit"] (the ones creating { exited }) to pipe
stdout/stderr instead (e.g., stdio: ["ignore","pipe","pipe"]), capture the
output streams (read stdout/stderr text from the spawn result) and assert
expected stdout/stderr contents before asserting expect(await exited).toBe(0);
specifically update the spawn invocation and follow it with reading the
spawnResult.stdout/stderr into strings and performing expectations on those
strings (use the same bunExe(), temp, bunEnv variables) prior to the final
exit-code assertion; apply the same pattern to the other spawn blocks that
mirror this one.

---

Duplicate comments:
In `@test/cli/init/init.test.ts`:
- Around line 375-393: Replace the manual for...of loop that creates tests for
each React flag with a describe.each([...]) block so tests follow the style
guide; use the same flag array ["--react", "--react=tailwind", "--react=shadcn"]
in describe.each, move the test body that calls tempDirWithFiles, Bun.spawn
(cmd: [bunExe(), "init", flag]), awaits exited, reads bunfig via
fs.readFileSync(path.join(temp, "bunfig.toml"), "utf8") and asserts the same
expectations (contains "[install]", "minimumReleaseAge = 86400",
"[serve.static]") into the parameterized it/test callback, and preserve the
30_000 timeout and test name template (e.g., `bun init ${flag} bunfig.toml has
minimumReleaseAge`).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8d388635-5e48-4253-8838-cf6830a57e0f

📥 Commits

Reviewing files that changed from the base of the PR and between 9ebaeac and 381666d.

📒 Files selected for processing (1)
  • test/cli/init/init.test.ts

Comment on lines +343 to +351
const { exited } = Bun.spawn({
cmd: [bunExe(), "init", "-y"],
cwd: temp,
stdio: ["ignore", "inherit", "inherit"],
env: bunEnv,
});

expect(await exited).toBe(0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Capture spawned process output before final exit-code assertion.

These new tests assert exited directly with inherited stdio, which weakens failure diagnostics. Prefer piping output and checking expected output before asserting the exit code.

♻️ Suggested pattern (apply to each new spawn block)
-    const { exited } = Bun.spawn({
+    const { exited, stderr } = Bun.spawn({
       cmd: [bunExe(), "init", "-y"],
       cwd: temp,
-      stdio: ["ignore", "inherit", "inherit"],
+      stdio: ["ignore", "inherit", "pipe"],
       env: bunEnv,
     });

-    expect(await exited).toBe(0);
+    const [exitCode, stderrText] = await Promise.all([exited, stderr.text()]);
+    if (exitCode !== 0) {
+      expect(stderrText).toBe("");
+    }
+    expect(exitCode).toBe(0);
As per coding guidelines: "When spawning processes in tests, check stdout expectations before exit code expectations to provide more useful error messages on test failure".

Also applies to: 362-370, 379-387

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/cli/init/init.test.ts` around lines 343 - 351, Change each Bun.spawn
call that currently sets stdio to ["ignore","inherit","inherit"] (the ones
creating { exited }) to pipe stdout/stderr instead (e.g., stdio:
["ignore","pipe","pipe"]), capture the output streams (read stdout/stderr text
from the spawn result) and assert expected stdout/stderr contents before
asserting expect(await exited).toBe(0); specifically update the spawn invocation
and follow it with reading the spawnResult.stdout/stderr into strings and
performing expectations on those strings (use the same bunExe(), temp, bunEnv
variables) prior to the final exit-code assertion; apply the same pattern to the
other spawn blocks that mirror this one.

@robobun

robobun commented May 12, 2026

Copy link
Copy Markdown
Collaborator Author

Closing — enabling minimumReleaseAge by default for every bun init project would force full-packument fetches on every cold-cache resolve (npm's abbreviated manifest still doesn't include time), and the pnpm-style modified-field fast-path is actually worse on cold cache for typical age windows. Not worth the overhead until npm adds time to the corgi doc. Branch left up in case we revisit.

@robobun robobun closed this May 12, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation looks clean and well-tested, but this changes the default bun init output for every new project and the author is explicitly asking for input on the 1d vs 2d vs 3d value (and there's overlap with #28729's approach) — worth a human signing off on the product decision.

Extended reasoning...

Overview

This PR makes bun init scaffold a bunfig.toml containing [install].minimumReleaseAge = 86400 for all templates. Touches: a new embedded asset (src/cli/init/bunfig.default.toml), the three React template bunfig.toml files, ~10 lines in src/cli/init_command.zig to register and conditionally write the asset, two docs pages, and test/cli/init/init.test.ts (snapshot updates + 5 new cases).

Security risks

None. The change writes a static, embedded TOML file into a freshly scaffolded directory. No user input flows into the file path or contents, no auth/crypto/permissions code is touched. The feature itself is a security hardening (supply-chain delay).

Level of scrutiny

The code is low-risk and mechanical — write_bunfig is wired identically to the existing write_gitignore / write_readme steps (gated on !minimal and !existsZ("bunfig.toml")), and test coverage is thorough (creation, no-overwrite, --minimal exclusion, all three React variants, exact value assertion per CodeRabbit's now-resolved comment).

The behavior change, however, is user-facing and global: every bun init will now emit an extra file and constrain the initial install. That's a product/default decision rather than a bug fix or refactor.

Other factors

  • The author explicitly flags the default value as open ("Happy to change to 2 days … or 3 days"), inviting maintainer input.
  • A bot flagged potential overlap with #28729, which sets the same default at the runtime level instead of via scaffolded config — a human should decide which approach (or both) is desired.
  • No CODEOWNERS apply to the touched paths; CodeRabbit's two comments are resolved.

Given the open product question and the overlap with another PR, I'm deferring rather than approving, even though I see nothing wrong with the implementation itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant