Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 4 additions & 2 deletions docs/pm/cli/install.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,15 @@ cowsay "Bun!"

## Production mode

To install in production mode (without `devDependencies` or `optionalDependencies`):
To install in production mode (without `devDependencies`):

```bash terminal icon="terminal"
bun install --production
```

For reproducible installs, use `--frozen-lockfile`. Bun installs the exact versions specified in the lockfile and does not update it. If your `package.json` disagrees with `bun.lock`, Bun exits with an error.
Passing `--production` also implies `--frozen-lockfile`, so the install fails if `package.json` is out of sync with the lockfile. If you only want to skip `devDependencies` without freezing the lockfile, use `--omit=dev` instead.

For reproducible installs without skipping `devDependencies`, use `--frozen-lockfile` on its own. Bun installs the exact versions specified in the lockfile and does not update it. If your `package.json` disagrees with `bun.lock`, Bun exits with an error.

```bash terminal icon="terminal"
bun install --frozen-lockfile
Expand Down
2 changes: 1 addition & 1 deletion docs/runtime/bunfig.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ peer = true

Whether `bun install` runs in "production mode". Default `false`.

In production mode, `"devDependencies"` are not installed. The `--production` CLI flag overrides this setting.
In production mode, `"devDependencies"` are not installed and the lockfile is frozen (as if `--frozen-lockfile` was passed), so installs fail if `package.json` disagrees with `bun.lock`. The `--production` CLI flag overrides this setting.

```toml title="bunfig.toml" icon="settings"
[install]
Expand Down
2 changes: 1 addition & 1 deletion docs/snippets/cli/install.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@

### Dependency Scope & Management

<ParamField path="--production" type="boolean">
Don't install devDependencies
Don't install devDependencies. Implies --frozen-lockfile
</ParamField>

Check warning on line 21 in docs/snippets/cli/install.mdx

View check run for this annotation

Claude / Claude Code Review

Sibling CLI doc snippets not updated to match new --production help text

Since `--production` lives in `SHARED_PARAMS`, the help text for `bun add`/`update`/`remove`/`link`/`patch`/`outdated`/`publish` now also says "Implies --frozen-lockfile" — but only `docs/snippets/cli/install.mdx` was updated. The sibling snippets (`docs/snippets/cli/{add,update,remove,link,patch,outdated,publish}.mdx`) and the completions files (`completions/{bun-cli.json,spec.yaml,bun.fish}`) still carry the old description. Not blocking, but worth a quick sweep so the docs stay in sync with t
Comment thread
robobun marked this conversation as resolved.
Resolved

<ParamField path="--no-save" type="boolean">
Don't update package.json or save a lockfile
Expand Down
4 changes: 3 additions & 1 deletion src/install/PackageManager/CommandLineArguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ const BACKEND_PARAM: ParamType = clap::param!(
const SHARED_PARAMS: &[ParamType] = &[
clap::param!("-c, --config <STR>? Specify path to config file (bunfig.toml)"),
clap::param!("-y, --yarn Write a yarn.lock file (yarn v1)"),
clap::param!("-p, --production Don't install devDependencies"),
clap::param!(
"-p, --production Don't install devDependencies. Implies --frozen-lockfile"
),
clap::param!("-P, --prod"),
clap::param!(
"--no-save Don't update package.json or save a lockfile"
Expand Down
2 changes: 1 addition & 1 deletion src/install/PackageManager/install_with_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -735,9 +735,9 @@
bun_core::pretty_errorln!(
"<r><red>error<r><d>:<r> lockfile had changes, but lockfile is frozen"
);
bun_core::note!(
"try re-running without <d>--frozen-lockfile<r> and commit the updated lockfile"
"try re-running without <d>--frozen-lockfile<r> or <d>--production<r> and commit the updated lockfile"
);

Check warning on line 740 in src/install/PackageManager/install_with_manager.rs

View check run for this annotation

Claude / Claude Code Review

Sibling frozen-lockfile error site lacks the --production hint

There's a second site emitting `lockfile had changes, but lockfile is frozen` at [install_with_manager.rs:1522](src/install/PackageManager/install_with_manager.rs#L1522) (inside `create_new_lockfile_and_enqueue`, reached when `needs_new_lockfile` is true) that has no `note!` at all — a user hitting that path via `--production` still gets no hint. Consider adding the same note there for completeness. Nit: that site never had a note and it's a rarer edge case, so not blocking.
Comment thread
robobun marked this conversation as resolved.
Resolved
}
Global::crash();
}
Expand Down
62 changes: 62 additions & 0 deletions test/cli/install/bun-install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6764,6 +6764,68 @@
});
});

// https://github.com/oven-sh/bun/issues/10949
it("documents that --production implies --frozen-lockfile", async () => {
await withContext(defaultOpts, async ctx => {
let urls: string[] = [];
setContextHandler(
ctx,
dummyRegistryForContext(ctx, urls, { "0.0.3": { as: "0.0.3" }, "0.0.5": { as: "0.0.5" } }),
);

// --help should say that --production implies --frozen-lockfile
const help = spawn({
cmd: [bunExe(), "install", "--help"],
cwd: ctx.package_dir,
stdout: "pipe",
stderr: "pipe",
env,
});
const [helpOut, helpErr] = await Promise.all([help.stdout.text(), help.stderr.text()]);
const helpText = helpOut + helpErr;
const productionLine = helpText.split("\n").find(line => line.includes("--production"));
expect(productionLine).toMatch(/--production.*Implies --frozen-lockfile/);
expect(await help.exited).toBe(0);

await writeFile(
join(ctx.package_dir, "package.json"),
JSON.stringify({ name: "foo", version: "0.0.1", dependencies: { baz: "0.0.3" } }),
);

expect(
await spawn({
cmd: [bunExe(), "install"],
cwd: ctx.package_dir,
stdout: "ignore",
stdin: "ignore",
stderr: "ignore",
env,
}).exited,
).toBe(0);

// change version of baz in package.json so it disagrees with the lockfile
await writeFile(
join(ctx.package_dir, "package.json"),
JSON.stringify({ name: "foo", version: "0.0.1", dependencies: { baz: "0.0.5" } }),
);

const { stderr, exited } = spawn({
cmd: [bunExe(), "install", "--production"],
cwd: ctx.package_dir,
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
});

// the note should mention --production, not just --frozen-lockfile
const err = await stderr.text();
expect(err).toContain("error: lockfile had changes, but lockfile is frozen");

Check warning on line 6823 in test/cli/install/bun-install.test.ts

View check run for this annotation

Claude / Claude Code Review

Test pipes stdout but never drains it

The third `spawn` call sets `stdout: "pipe"` (and `stdin: "pipe"`) but only destructures `{ stderr, exited }`, so stdout is never drained. Since you don't use it, consider `stdout: "ignore"` to match the second spawn in this test — CLAUDE.md's harness convention is to either drain piped streams in the same `Promise.all` or not pipe them at all. No real deadlock risk here (the frozen-lockfile error path writes ~nothing to stdout before crashing), so this is just a style nit; the pre-existing `--f
Comment thread
robobun marked this conversation as resolved.
Resolved
expect(err).toContain("--production");
expect(await exited).toBe(1);
});
});

it("should perform bin-linking across multiple dependencies", async () => {
await withContext(defaultOpts, async ctx => {
const foo_package = JSON.stringify({
Expand Down
Loading