diff --git a/docs/pm/cli/install.mdx b/docs/pm/cli/install.mdx index 6782304a3c9d..11db6669b371 100644 --- a/docs/pm/cli/install.mdx +++ b/docs/pm/cli/install.mdx @@ -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. Production mode also aborts immediately on the first install error (for example, a bad lockfile or a failed bin link) instead of reporting it at the end. If you only want to skip `devDependencies` without these stricter checks, 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 diff --git a/docs/runtime/bunfig.mdx b/docs/runtime/bunfig.mdx index 3078a7b372a4..9963b449aded 100644 --- a/docs/runtime/bunfig.mdx +++ b/docs/runtime/bunfig.mdx @@ -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] diff --git a/docs/snippets/cli/add.mdx b/docs/snippets/cli/add.mdx index 2c0fd9de1860..b42c42128e03 100644 --- a/docs/snippets/cli/add.mdx +++ b/docs/snippets/cli/add.mdx @@ -7,7 +7,7 @@ bun add <@version> ### Dependency Management - Don't install devDependencies. Alias: -p + Don't install devDependencies. Implies --frozen-lockfile. Alias: -p diff --git a/docs/snippets/cli/install.mdx b/docs/snippets/cli/install.mdx index 1735bbf3260e..913aa6c6c860 100644 --- a/docs/snippets/cli/install.mdx +++ b/docs/snippets/cli/install.mdx @@ -17,7 +17,7 @@ bun install @ ### Dependency Scope & Management - Don't install devDependencies + Don't install devDependencies. Implies --frozen-lockfile diff --git a/docs/snippets/cli/link.mdx b/docs/snippets/cli/link.mdx index b648ae4469ad..f8b640c8e66f 100644 --- a/docs/snippets/cli/link.mdx +++ b/docs/snippets/cli/link.mdx @@ -13,7 +13,7 @@ bun link ### Dependency Management - Don't install devDependencies. Alias: -p + Don't install devDependencies. Implies --frozen-lockfile. Alias: -p diff --git a/docs/snippets/cli/outdated.mdx b/docs/snippets/cli/outdated.mdx index 62b4c8ae929b..1f2d37dbae46 100644 --- a/docs/snippets/cli/outdated.mdx +++ b/docs/snippets/cli/outdated.mdx @@ -43,7 +43,7 @@ bun outdated ### Dependency Scope & Target - Don't install devDependencies + Don't install devDependencies. Implies --frozen-lockfile diff --git a/docs/snippets/cli/patch.mdx b/docs/snippets/cli/patch.mdx index 5b7905000349..c48b0a70bc4b 100644 --- a/docs/snippets/cli/patch.mdx +++ b/docs/snippets/cli/patch.mdx @@ -17,7 +17,7 @@ bun patch @ ### Dependency Management - Don't install devDependencies. Alias: -p + Don't install devDependencies. Implies --frozen-lockfile. Alias: -p diff --git a/docs/snippets/cli/publish.mdx b/docs/snippets/cli/publish.mdx index 8b09553f36e8..8f1b39919606 100644 --- a/docs/snippets/cli/publish.mdx +++ b/docs/snippets/cli/publish.mdx @@ -125,7 +125,7 @@ bun publish --cafile ./ca-cert.pem #### Dependency Management - Don't install devDependencies + Don't install devDependencies. Implies --frozen-lockfile diff --git a/docs/snippets/cli/remove.mdx b/docs/snippets/cli/remove.mdx index 50972e59ed75..6360652c0520 100644 --- a/docs/snippets/cli/remove.mdx +++ b/docs/snippets/cli/remove.mdx @@ -51,7 +51,7 @@ bun remove ### Dependency Filtering - Don't install devDependencies. Alias: -p + Don't install devDependencies. Implies --frozen-lockfile. Alias: -p diff --git a/docs/snippets/cli/update.mdx b/docs/snippets/cli/update.mdx index 037f0ad63d06..f9e474106eb5 100644 --- a/docs/snippets/cli/update.mdx +++ b/docs/snippets/cli/update.mdx @@ -17,7 +17,7 @@ bun update ### Dependency Scope - Don't install devDependencies. Alias: -p + Don't install devDependencies. Implies --frozen-lockfile. Alias: -p diff --git a/src/install/PackageManager/CommandLineArguments.rs b/src/install/PackageManager/CommandLineArguments.rs index c16d2a646e70..c62fc10a6097 100644 --- a/src/install/PackageManager/CommandLineArguments.rs +++ b/src/install/PackageManager/CommandLineArguments.rs @@ -56,7 +56,9 @@ const BACKEND_PARAM: ParamType = clap::param!( const SHARED_PARAMS: &[ParamType] = &[ clap::param!("-c, --config ? 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" diff --git a/src/install/PackageManager/install_with_manager.rs b/src/install/PackageManager/install_with_manager.rs index d1ab3976fe4f..c480c0bad8d6 100644 --- a/src/install/PackageManager/install_with_manager.rs +++ b/src/install/PackageManager/install_with_manager.rs @@ -731,15 +731,7 @@ pub fn install_with_manager( } } - if log_level != Options::LogLevel::Silent { - bun_core::pretty_errorln!( - "error: lockfile had changes, but lockfile is frozen" - ); - bun_core::note!( - "try re-running without --frozen-lockfile and commit the updated lockfile" - ); - } - Global::crash(); + crash_frozen_lockfile(log_level); } } @@ -1376,6 +1368,20 @@ fn add_dependency_error( // `bun install` / `bun install --frozen-lockfile` (node_modules already up to // date) faults in far fewer distinct `.text` pages. +#[cold] +#[inline(never)] +fn crash_frozen_lockfile(log_level: Options::LogLevel) -> ! { + if log_level != Options::LogLevel::Silent { + bun_core::pretty_errorln!( + "error: lockfile had changes, but lockfile is frozen" + ); + bun_core::note!( + "try re-running without --frozen-lockfile or --production and commit the updated lockfile" + ); + } + Global::crash(); +} + #[cold] #[inline(never)] fn report_lockfile_load_error( @@ -1517,12 +1523,7 @@ fn create_new_lockfile_and_enqueue( if manager.options.enable.frozen_lockfile() && !matches!(load_result, lockfile::LoadResult::NotFound) { - if log_level != Options::LogLevel::Silent { - bun_core::pretty_errorln!( - "error: lockfile had changes, but lockfile is frozen" - ); - } - Global::crash(); + crash_frozen_lockfile(log_level); } // SAFETY: `manager.log` is a non-null backref to the CLI log set at init(). diff --git a/test/cli/install/bun-install.test.ts b/test/cli/install/bun-install.test.ts index 65a6a3e14d28..e26af64c73cf 100644 --- a/test/cli/install/bun-install.test.ts +++ b/test/cli/install/bun-install.test.ts @@ -6764,6 +6764,68 @@ describe.concurrent("bun-install", () => { }); }); + // 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: "ignore", + stdin: "ignore", + 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"); + expect(err).toContain("try re-running without --frozen-lockfile or --production and commit the updated lockfile"); + expect(await exited).toBe(1); + }); + }); + it("should perform bin-linking across multiple dependencies", async () => { await withContext(defaultOpts, async ctx => { const foo_package = JSON.stringify({