Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down
2 changes: 2 additions & 0 deletions docs/runtime/templating/init.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand All @@ -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

Expand Down
5 changes: 5 additions & 0 deletions src/cli/init/bunfig.default.toml
Original file line number Diff line number Diff line change
@@ -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
8 changes: 7 additions & 1 deletion src/cli/init/react-app/bunfig.toml
Original file line number Diff line number Diff line change
@@ -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_*"
env = "BUN_PUBLIC_*"
6 changes: 6 additions & 0 deletions src/cli/init/react-shadcn/bunfig.toml
Original file line number Diff line number Diff line change
@@ -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_*"
7 changes: 6 additions & 1 deletion src/cli/init/react-tailwind/bunfig.toml
Original file line number Diff line number Diff line change
@@ -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_*"
env = "BUN_PUBLIC_*"
10 changes: 10 additions & 0 deletions src/cli/init_command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -621,17 +622,20 @@ pub const InitCommand = struct {
write_gitignore: bool,
write_package_json: bool,
write_tsconfig: bool,
write_bunfig: bool,
write_readme: bool,
};

var steps = Steps{
.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: {
Expand Down Expand Up @@ -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,
Expand Down
66 changes: 66 additions & 0 deletions test/cli/init/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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 () => {
Expand All @@ -99,6 +101,7 @@ import path from "path";
".gitignore",
"README.md",
"bun.lock",
"bunfig.toml",
"index.ts",
"node_modules",
"package.json",
Expand Down Expand Up @@ -138,6 +141,7 @@ import path from "path";
".gitignore",
"README.md",
"bun.lock",
"bunfig.toml",
"index.ts",
"node_modules",
"package.json",
Expand All @@ -161,6 +165,7 @@ import path from "path";
".gitignore",
"README.md",
"bun.lock",
"bunfig.toml",
"index.ts",
"node_modules",
"package.json",
Expand All @@ -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({
Expand All @@ -196,6 +202,7 @@ import path from "path";
".gitignore",
"README.md",
"bun.lock",
"bunfig.toml",
"index.ts",
"node_modules",
"package.json",
Expand All @@ -205,6 +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/package.json")).json()).toMatchInlineSnapshot(`
{
"devDependencies": {
Expand Down Expand Up @@ -324,5 +334,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);

Comment on lines +343 to +351

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.

const bunfig = fs.readFileSync(path.join(temp, "bunfig.toml"), "utf8");
expect(bunfig).toContain("[install]");
expect(bunfig).toContain("minimumReleaseAge");
}, 30_000);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
Loading