Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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 = 86400");
}, 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 = 86400");
expect(bunfig).toContain("[serve.static]");
}, 30_000);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
Loading