From 3f1d004f3e1f04c4b38900c6f14e9b0e117970b3 Mon Sep 17 00:00:00 2001 From: JongKyung Lee Date: Wed, 16 Sep 2026 14:30:47 +0900 Subject: [PATCH 1/9] fix(pm): preserve install options when adding packages Preserve common options when install with package names resolves to add, and map them to each package manager's supported flags. Diagnose options that do not apply to the selected install/add mode before conversion. Retain Yarn Classic's native frozen-lockfile behavior and warn for unsupported options without changing the warning-and-continue policy. Cover option forwarding and command modes with resolver tests and CLI snapshots. --- .../snapshots/command_add_bun.md | 10 + .../snapshots/command_add_npm10.md | 10 + .../snapshots/command_add_npm11.md | 10 + .../snapshots/command_add_pnpm10.global.md | 10 + .../snapshots/command_add_pnpm10.local.md | 20 ++ .../snapshots/command_add_pnpm11.md | 10 + .../snapshots/command_add_pnpm12.md | 10 + .../snapshots/command_add_pnpm9.md | 10 + .../snapshots/command_add_yarn4.md | 10 + .../dep-v2/package.json | 4 + .../install_package_options/dep/package.json | 4 + .../install_package_options/package.json | 6 + .../install_package_options/snapshots.toml | 109 +++++++++++ .../snapshots/bun_frozen_lockfile.md | 63 ++++++ ...ith_packages_drops_install_only_options.md | 42 ++++ ...without_packages_drops_add_only_options.md | 41 ++++ .../snapshots/npm_lockfile_only.md | 39 ++++ .../snapshots/npm_offline.md | 27 +++ .../pnpm_unsupported_install_options.md | 31 +++ .../yarn_berry_frozen_lockfile_modes.md | 128 ++++++++++++ .../snapshots/yarn_berry_lockfile_only.md | 43 +++++ .../yarn_classic_frozen_lockfile_modes.md | 117 +++++++++++ crates/vp_pm_cli/src/cli.rs | 5 +- .../vp_pm_cli/src/resolution/commands/add.rs | 182 +++++++++++++++++- .../src/resolution/commands/install.rs | 134 ++++++++++++- 25 files changed, 1057 insertions(+), 18 deletions(-) create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/dep-v2/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/dep/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/bun_frozen_lockfile.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/install_with_packages_drops_install_only_options.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/install_without_packages_drops_add_only_options.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/npm_lockfile_only.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/npm_offline.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/pnpm_unsupported_install_options.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_lockfile_only.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_classic_frozen_lockfile_modes.md diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_bun/snapshots/command_add_bun.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_bun/snapshots/command_add_bun.md index 375354450f..8e910cb567 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_bun/snapshots/command_add_bun.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_bun/snapshots/command_add_bun.md @@ -25,6 +25,16 @@ Options: --save-catalog Save the new dependency to the default catalog --allow-build A list of package names allowed to run postinstall --ignore-scripts Do not run lifecycle scripts + --no-optional Do not install optionalDependencies + --frozen-lockfile Fail if lockfile needs to be updated + --no-frozen-lockfile Allow lockfile updates + --lockfile-only Only update lockfile, don't install + --prefer-offline Use cached packages when available + --offline Only use packages already in cache + -f, --force Force reinstall all dependencies + --no-lockfile Don't read or generate lockfile + --shamefully-hoist Create flat node_modules (pnpm only) + --silent Suppress package manager output --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm10/snapshots/command_add_npm10.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm10/snapshots/command_add_npm10.md index aec68aaa9d..786bd04ea4 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm10/snapshots/command_add_npm10.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm10/snapshots/command_add_npm10.md @@ -25,6 +25,16 @@ Options: --save-catalog Save the new dependency to the default catalog --allow-build A list of package names allowed to run postinstall --ignore-scripts Do not run lifecycle scripts + --no-optional Do not install optionalDependencies + --frozen-lockfile Fail if lockfile needs to be updated + --no-frozen-lockfile Allow lockfile updates + --lockfile-only Only update lockfile, don't install + --prefer-offline Use cached packages when available + --offline Only use packages already in cache + -f, --force Force reinstall all dependencies + --no-lockfile Don't read or generate lockfile + --shamefully-hoist Create flat node_modules (pnpm only) + --silent Suppress package manager output --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm11/snapshots/command_add_npm11.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm11/snapshots/command_add_npm11.md index cc730352ab..0ff40ccc97 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm11/snapshots/command_add_npm11.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm11/snapshots/command_add_npm11.md @@ -25,6 +25,16 @@ Options: --save-catalog Save the new dependency to the default catalog --allow-build A list of package names allowed to run postinstall --ignore-scripts Do not run lifecycle scripts + --no-optional Do not install optionalDependencies + --frozen-lockfile Fail if lockfile needs to be updated + --no-frozen-lockfile Allow lockfile updates + --lockfile-only Only update lockfile, don't install + --prefer-offline Use cached packages when available + --offline Only use packages already in cache + -f, --force Force reinstall all dependencies + --no-lockfile Don't read or generate lockfile + --shamefully-hoist Create flat node_modules (pnpm only) + --silent Suppress package manager output --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.global.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.global.md index a45d66a732..f78ba8c3ae 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.global.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.global.md @@ -25,6 +25,16 @@ Options: --save-catalog Save the new dependency to the default catalog --allow-build A list of package names allowed to run postinstall --ignore-scripts Do not run lifecycle scripts + --no-optional Do not install optionalDependencies + --frozen-lockfile Fail if lockfile needs to be updated + --no-frozen-lockfile Allow lockfile updates + --lockfile-only Only update lockfile, don't install + --prefer-offline Use cached packages when available + --offline Only use packages already in cache + -f, --force Force reinstall all dependencies + --no-lockfile Don't read or generate lockfile + --shamefully-hoist Create flat node_modules (pnpm only) + --silent Suppress package manager output --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.local.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.local.md index f61cd548fa..41f5440d40 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.local.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.local.md @@ -32,6 +32,26 @@ Options: A list of package names allowed to run postinstall --ignore-scripts Do not run lifecycle scripts + --no-optional + Do not install optionalDependencies + --frozen-lockfile + Fail if lockfile needs to be updated + --no-frozen-lockfile + Allow lockfile updates + --lockfile-only + Only update lockfile, don't install + --prefer-offline + Use cached packages when available + --offline + Only use packages already in cache + -f, --force + Force reinstall all dependencies + --no-lockfile + Don't read or generate lockfile + --shamefully-hoist + Create flat node_modules (pnpm only) + --silent + Suppress package manager output --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm11/snapshots/command_add_pnpm11.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm11/snapshots/command_add_pnpm11.md index e4d2b58556..98775e73f1 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm11/snapshots/command_add_pnpm11.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm11/snapshots/command_add_pnpm11.md @@ -25,6 +25,16 @@ Options: --save-catalog Save the new dependency to the default catalog --allow-build A list of package names allowed to run postinstall --ignore-scripts Do not run lifecycle scripts + --no-optional Do not install optionalDependencies + --frozen-lockfile Fail if lockfile needs to be updated + --no-frozen-lockfile Allow lockfile updates + --lockfile-only Only update lockfile, don't install + --prefer-offline Use cached packages when available + --offline Only use packages already in cache + -f, --force Force reinstall all dependencies + --no-lockfile Don't read or generate lockfile + --shamefully-hoist Create flat node_modules (pnpm only) + --silent Suppress package manager output --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm12/snapshots/command_add_pnpm12.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm12/snapshots/command_add_pnpm12.md index 4911bc941d..0f2fb08902 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm12/snapshots/command_add_pnpm12.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm12/snapshots/command_add_pnpm12.md @@ -25,6 +25,16 @@ Options: --save-catalog Save the new dependency to the default catalog --allow-build A list of package names allowed to run postinstall --ignore-scripts Do not run lifecycle scripts + --no-optional Do not install optionalDependencies + --frozen-lockfile Fail if lockfile needs to be updated + --no-frozen-lockfile Allow lockfile updates + --lockfile-only Only update lockfile, don't install + --prefer-offline Use cached packages when available + --offline Only use packages already in cache + -f, --force Force reinstall all dependencies + --no-lockfile Don't read or generate lockfile + --shamefully-hoist Create flat node_modules (pnpm only) + --silent Suppress package manager output --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm9/snapshots/command_add_pnpm9.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm9/snapshots/command_add_pnpm9.md index ae46ba2654..cf7206a661 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm9/snapshots/command_add_pnpm9.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm9/snapshots/command_add_pnpm9.md @@ -25,6 +25,16 @@ Options: --save-catalog Save the new dependency to the default catalog --allow-build A list of package names allowed to run postinstall --ignore-scripts Do not run lifecycle scripts + --no-optional Do not install optionalDependencies + --frozen-lockfile Fail if lockfile needs to be updated + --no-frozen-lockfile Allow lockfile updates + --lockfile-only Only update lockfile, don't install + --prefer-offline Use cached packages when available + --offline Only use packages already in cache + -f, --force Force reinstall all dependencies + --no-lockfile Don't read or generate lockfile + --shamefully-hoist Create flat node_modules (pnpm only) + --silent Suppress package manager output --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_yarn4/snapshots/command_add_yarn4.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_yarn4/snapshots/command_add_yarn4.md index 5a6b3afdcf..f2cef46f82 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_yarn4/snapshots/command_add_yarn4.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_yarn4/snapshots/command_add_yarn4.md @@ -25,6 +25,16 @@ Options: --save-catalog Save the new dependency to the default catalog --allow-build A list of package names allowed to run postinstall --ignore-scripts Do not run lifecycle scripts + --no-optional Do not install optionalDependencies + --frozen-lockfile Fail if lockfile needs to be updated + --no-frozen-lockfile Allow lockfile updates + --lockfile-only Only update lockfile, don't install + --prefer-offline Use cached packages when available + --offline Only use packages already in cache + -f, --force Force reinstall all dependencies + --no-lockfile Don't read or generate lockfile + --shamefully-hoist Create flat node_modules (pnpm only) + --silent Suppress package manager output --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/dep-v2/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/dep-v2/package.json new file mode 100644 index 0000000000..295a4ceded --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/dep-v2/package.json @@ -0,0 +1,4 @@ +{ + "name": "install-option-dep", + "version": "2.0.0" +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/dep/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/dep/package.json new file mode 100644 index 0000000000..d624bdb8fc --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/dep/package.json @@ -0,0 +1,4 @@ +{ + "name": "install-option-dep", + "version": "1.0.0" +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/package.json new file mode 100644 index 0000000000..a5cf985ce1 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/package.json @@ -0,0 +1,6 @@ +{ + "name": "install-package-options", + "version": "1.0.0", + "private": true, + "packageManager": "npm@11.13.0" +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml new file mode 100644 index 0000000000..1164afb34e --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml @@ -0,0 +1,109 @@ +[[case]] +name = "install_with_packages_drops_install_only_options" +vp = "global" +steps = [ + { argv = ["vp", "install", "./dep", "--fix-lockfile", "--resolution-only", "--lockfile-only"], comment = "diagnose install-only options before converting to add, while preserving common options" }, + ["vpt", "stat-file", "package-lock.json", "--assert", "file"], + ["vpt", "stat-file", "node_modules", "--assert", "missing"], + ["vpt", "print-file", "package.json"], +] + +[[case]] +name = "install_without_packages_drops_add_only_options" +vp = "global" +steps = [ + { argv = ["vp", "install", "--save-exact", "--save-peer", "--save-optional", "--save-catalog", "--lockfile-only"], comment = "diagnose add-only options without package names instead of reporting manager support" }, + ["vpt", "stat-file", "package-lock.json", "--assert", "file"], + ["vpt", "stat-file", "node_modules", "--assert", "missing"], + ["vpt", "print-file", "package.json"], +] + +[[case]] +name = "npm_lockfile_only" +vp = "global" +steps = [ + { argv = ["vp", "install", "./dep", "--lockfile-only"], comment = "adding a package preserves lockfile-only" }, + ["vpt", "stat-file", "package-lock.json", "--assert", "file"], + ["vpt", "stat-file", "node_modules", "--assert", "missing"], + ["vpt", "print-file", "package.json"], +] + +[[case]] +name = "npm_offline" +vp = "global" +steps = [ + { argv = ["vp", "install", "vp-install-option-uncached-probe@1.0.0", "--offline", "--", "--registry=http://127.0.0.1:9", "--fetch-retries=0"], comment = "an uncached package fails in offline mode rather than attempting a connection", continue-on-failure = true }, + { argv = ["vpt", "stat-file", "node_modules", "--assert", "missing"], continue-on-failure = true }, + ["vpt", "stat-file", "package-lock.json", "--assert", "missing"], +] + +[[case]] +name = "bun_frozen_lockfile" +vp = "global" +steps = [ + { argv = ["vpt", "json-edit", "package.json", "packageManager", "bun@1.3.11"], snapshot = false }, + ["vp", "install", "./dep", "--lockfile-only"], + ["vpt", "stat-file", "node_modules", "--assert", "missing"], + { argv = ["vpt", "cp", "bun.lock", "before.lock"], snapshot = false }, + { argv = ["vp", "install", "./dep-v2", "--frozen-lockfile"], comment = "a named-package install must not rewrite a frozen lockfile", continue-on-failure = true }, + { argv = ["node", "-e", "const fs = require('node:fs'); if (!fs.readFileSync('bun.lock').equals(fs.readFileSync('before.lock'))) process.exit(1); console.log('lockfile unchanged');"], continue-on-failure = true }, + ["vpt", "stat-file", "node_modules", "--assert", "missing"], + ["vpt", "print-file", "package.json"], +] + +[[case]] +name = "pnpm_unsupported_install_options" +vp = "global" +steps = [ + { argv = ["vpt", "json-edit", "package.json", "packageManager", "pnpm@11.24.0"], snapshot = false }, + { argv = ["vp", "install", "./dep", "--lockfile-only", "--frozen-lockfile"], comment = "warn for frozen-lockfile that pnpm add cannot accept, but preserve lockfile-only" }, + ["vpt", "stat-file", "pnpm-lock.yaml", "--assert", "file"], + ["vpt", "stat-file", "node_modules", "--assert", "missing"], +] + +[[case]] +name = "yarn_berry_lockfile_only" +vp = "global" +env = { YARN_ENABLE_TELEMETRY = "0" } +steps = [ + { argv = ["vpt", "json-edit", "package.json", "packageManager", "yarn@4.10.3"], snapshot = false }, + { argv = ["vp", "install", "./dep", "--lockfile-only", "--ignore-scripts"], comment = "use update-lockfile rather than skip-build when both options are supplied" }, + ["vpt", "stat-file", "yarn.lock", "--assert", "file"], + ["vpt", "stat-file", "node_modules", "--assert", "missing"], + ["vpt", "stat-file", ".pnp.cjs", "--assert", "missing"], +] + +[[case]] +name = "yarn_classic_frozen_lockfile_modes" +vp = "global" +steps = [ + { argv = ["vpt", "json-edit", "package.json", "packageManager", "yarn@1.22.22"], snapshot = false }, + { argv = ["vp", "install", "./dep"], snapshot = false }, + { argv = ["vpt", "cp", "yarn.lock", "before.lock"], snapshot = false }, + { argv = ["vp", "install", "./dep-v2", "--frozen-lockfile"], comment = "Classic add preserves the lockfile but allows manifest changes" }, + ["node", "-e", "const fs = require('node:fs'); if (!fs.readFileSync('yarn.lock').equals(fs.readFileSync('before.lock'))) process.exit(1); console.log('lockfile unchanged');"], + ["node", "-p", "require('./package.json').dependencies['install-option-dep']"], + { argv = ["vp", "install", "--frozen-lockfile"], comment = "package-free installs still enforce the frozen lockfile", continue-on-failure = true }, + { argv = ["node", "-e", "const fs = require('node:fs'); if (!fs.readFileSync('yarn.lock').equals(fs.readFileSync('before.lock'))) process.exit(1); console.log('lockfile unchanged');"], continue-on-failure = true }, + { argv = ["vp", "install", "--no-frozen-lockfile"], comment = "package-free installs can explicitly allow lockfile changes" }, + ["node", "-p", "require('./node_modules/install-option-dep/package.json').version"], + { argv = ["vp", "add", "./dep", "--no-frozen-lockfile"], comment = "Classic add warns and drops the unsupported negated flag" }, + ["node", "-p", "require('./node_modules/install-option-dep/package.json').version"], +] + +[[case]] +name = "yarn_berry_frozen_lockfile_modes" +vp = "global" +env = { YARN_ENABLE_TELEMETRY = "0" } +steps = [ + { argv = ["vpt", "json-edit", "package.json", "packageManager", "yarn@4.10.3"], snapshot = false }, + { argv = ["vpt", "write-file", ".yarnrc.yml", "nodeLinker: node-modules\n"], snapshot = false }, + { argv = ["vp", "install", "./dep", "--frozen-lockfile"], comment = "named-package installs use add, which has no immutable option" }, + { argv = ["vpt", "cp", "yarn.lock", "before.lock"], snapshot = false }, + { argv = ["vpt", "json-edit", "package.json", "dependencies.install-option-dep", "file:./dep-v2"], snapshot = false }, + { argv = ["vp", "install", "--frozen-lockfile"], comment = "package-free installs map frozen-lockfile to immutable", continue-on-failure = true }, + { argv = ["node", "-e", "const fs = require('node:fs'); if (!fs.readFileSync('yarn.lock').equals(fs.readFileSync('before.lock'))) process.exit(1); console.log('lockfile unchanged');"], continue-on-failure = true }, + { argv = ["vp", "install", "--no-frozen-lockfile"], comment = "package-free installs map no-frozen-lockfile to no-immutable" }, + ["node", "-p", "require('./node_modules/install-option-dep/package.json').version"], + { argv = ["vp", "add", "./dep", "--no-frozen-lockfile"], comment = "direct add also warns for the unsupported immutable override" }, +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/bun_frozen_lockfile.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/bun_frozen_lockfile.md new file mode 100644 index 0000000000..9478c22b12 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/bun_frozen_lockfile.md @@ -0,0 +1,63 @@ +# bun_frozen_lockfile + +## `vpt json-edit package.json packageManager bun@1.3.11` + + +## `vp install ./dep --lockfile-only` + +``` +VITE+ - The Unified Toolchain for the Web + +bun add () + +Saved bun.lock (2 packages) [] +``` + +## `vpt stat-file node_modules --assert missing` + +``` +node_modules: missing +``` + +## `vpt cp bun.lock before.lock` + + +## `vp install ./dep-v2 --frozen-lockfile` + +a named-package install must not rewrite a frozen lockfile + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + +bun add () +error: lockfile had changes, but lockfile is frozen +note: try re-running without --frozen-lockfile and commit the updated lockfile +``` + +## `node -e 'const fs = require('\''node:fs'\''); if ('\!'fs.readFileSync('\''bun.lock'\'').equals(fs.readFileSync('\''before.lock'\''))) process.exit(1); console.log('\''lockfile unchanged'\'');'` + +``` +lockfile unchanged +``` + +## `vpt stat-file node_modules --assert missing` + +``` +node_modules: missing +``` + +## `vpt print-file package.json` + +``` +{ + "name": "install-package-options", + "packageManager": "bun@1.3.11", + "private": true, + "version": "1.0.0", + "dependencies": { + "install-option-dep": "./dep" + } +} +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/install_with_packages_drops_install_only_options.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/install_with_packages_drops_install_only_options.md new file mode 100644 index 0000000000..c59e4f8b64 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/install_with_packages_drops_install_only_options.md @@ -0,0 +1,42 @@ +# install_with_packages_drops_install_only_options + +## `vp install ./dep --fix-lockfile --resolution-only --lockfile-only` + +diagnose install-only options before converting to add, while preserving common options + +``` +VITE+ - The Unified Toolchain for the Web + +warn: install with package names does not support --fix-lockfile. +warn: install with package names does not support --resolution-only. + +up to date, audited 3 packages in + +found 0 vulnerabilities +``` + +## `vpt stat-file package-lock.json --assert file` + +``` +package-lock.json: file +``` + +## `vpt stat-file node_modules --assert missing` + +``` +node_modules: missing +``` + +## `vpt print-file package.json` + +``` +{ + "name": "install-package-options", + "version": "1.0.0", + "private": true, + "packageManager": "npm@11.13.0", + "dependencies": { + "install-option-dep": "file:dep" + } +} +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/install_without_packages_drops_add_only_options.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/install_without_packages_drops_add_only_options.md new file mode 100644 index 0000000000..0030cb1530 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/install_without_packages_drops_add_only_options.md @@ -0,0 +1,41 @@ +# install_without_packages_drops_add_only_options + +## `vp install --save-exact --save-peer --save-optional --save-catalog --lockfile-only` + +diagnose add-only options without package names instead of reporting manager support + +``` +VITE+ - The Unified Toolchain for the Web + +warn: install without package names does not support --save-exact. +warn: install without package names does not support --save-peer. +warn: install without package names does not support --save-optional. +warn: install without package names does not support --save-catalog. + +up to date, audited 1 package in + +found 0 vulnerabilities +``` + +## `vpt stat-file package-lock.json --assert file` + +``` +package-lock.json: file +``` + +## `vpt stat-file node_modules --assert missing` + +``` +node_modules: missing +``` + +## `vpt print-file package.json` + +``` +{ + "name": "install-package-options", + "version": "1.0.0", + "private": true, + "packageManager": "npm@11.13.0" +} +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/npm_lockfile_only.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/npm_lockfile_only.md new file mode 100644 index 0000000000..50d0d932a8 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/npm_lockfile_only.md @@ -0,0 +1,39 @@ +# npm_lockfile_only + +## `vp install ./dep --lockfile-only` + +adding a package preserves lockfile-only + +``` +VITE+ - The Unified Toolchain for the Web + +up to date, audited 3 packages in + +found 0 vulnerabilities +``` + +## `vpt stat-file package-lock.json --assert file` + +``` +package-lock.json: file +``` + +## `vpt stat-file node_modules --assert missing` + +``` +node_modules: missing +``` + +## `vpt print-file package.json` + +``` +{ + "name": "install-package-options", + "version": "1.0.0", + "private": true, + "packageManager": "npm@11.13.0", + "dependencies": { + "install-option-dep": "file:dep" + } +} +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/npm_offline.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/npm_offline.md new file mode 100644 index 0000000000..b5893e6aaf --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/npm_offline.md @@ -0,0 +1,27 @@ +# npm_offline + +## `vp install vp-install-option-uncached-probe@1.0.0 --offline -- --registry=http://127.0.0.1:9 --fetch-retries=0` + +an uncached package fails in offline mode rather than attempting a connection + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + +npm error code ENOTCACHED +npm error request to http://127.0.0.1:/vp-install-option-uncached-probe failed: cache mode is 'only-if-cached' but no cached response is available. +npm error A complete log of this run can be found in: /.npm/_logs/-debug-0.log +``` + +## `vpt stat-file node_modules --assert missing` + +``` +node_modules: missing +``` + +## `vpt stat-file package-lock.json --assert missing` + +``` +package-lock.json: missing +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/pnpm_unsupported_install_options.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/pnpm_unsupported_install_options.md new file mode 100644 index 0000000000..23990127e6 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/pnpm_unsupported_install_options.md @@ -0,0 +1,31 @@ +# pnpm_unsupported_install_options + +## `vpt json-edit package.json packageManager pnpm@11.24.0` + + +## `vp install ./dep --lockfile-only --frozen-lockfile` + +warn for frozen-lockfile that pnpm add cannot accept, but preserve lockfile-only + +``` +VITE+ - The Unified Toolchain for the Web + +warn: pnpm does not support --frozen-lockfile. + +dependencies: + install-option-dep link:dep + +Done in using pnpm +``` + +## `vpt stat-file pnpm-lock.yaml --assert file` + +``` +pnpm-lock.yaml: file +``` + +## `vpt stat-file node_modules --assert missing` + +``` +node_modules: missing +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md new file mode 100644 index 0000000000..427e424269 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md @@ -0,0 +1,128 @@ +# yarn_berry_frozen_lockfile_modes + +## `vpt json-edit package.json packageManager yarn@4.10.3` + + +## `vpt write-file .yarnrc.yml 'nodeLinker: node-modules +'` + + +## `vp install ./dep --frozen-lockfile` + +named-package installs use add, which has no immutable option + +``` +VITE+ - The Unified Toolchain for the Web + +warn: yarn >=2 does not support --frozen-lockfile. +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0085: │ + install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A. +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0013: │ A package was added to the project (+ KiB). +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in +``` + +## `vpt cp yarn.lock before.lock` + + +## `vpt json-edit package.json dependencies.install-option-dep file:./dep-v2` + + +## `vp install --frozen-lockfile` + +package-free installs map frozen-lockfile to immutable + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0085: │ + install-option-dep@file:./dep-v2#./dep-v2::hash=5590bc&locator=install-package-options%40workspace%3A. +➤ YN0085: │ - install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A. +➤ YN0000: └ Completed + +➤ YN0000: ┌ Post-resolution validation +➤ YN0000: │ @@ -4,18 +4,17 @@ +➤ YN0000: │ __metadata: +➤ YN0000: │ version: 8 +➤ YN0000: │ cacheKey: 10c0 +➤ YN0000: │ +➤ YN0028: │ -"install-option-dep@file:./dep::locator=install-package-options%40workspace%3A.": +➤ YN0028: │ - version: 1.0.0 +➤ YN0028: │ - resolution: "install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A." +➤ YN0028: │ - checksum: 10c0/32675cb2e55f886e9f975fdaefa9cb706071880078bda04af25dd0343941bf33780453c0ec848b30d3aa53cb9d5d5b3a6aeef902552100f255ed35fac8a8ff06 +➤ YN0028: │ +"install-option-dep@file:./dep-v2::locator=install-package-options%40workspace%3A.": +➤ YN0028: │ + version: 2.0.0 +➤ YN0028: │ + resolution: "install-option-dep@file:./dep-v2#./dep-v2::hash=5590bc&locator=install-package-options%40workspace%3A." +➤ YN0000: │ languageName: node +➤ YN0000: │ linkType: hard +➤ YN0000: │ +➤ YN0000: │ "install-package-options@workspace:.": +➤ YN0000: │ version: 0.0.0-use.local +➤ YN0000: │ resolution: "install-package-options@workspace:." +➤ YN0000: │ dependencies: +➤ YN0028: │ - install-option-dep: ./dep +➤ YN0028: │ + install-option-dep: "file:./dep-v2" +➤ YN0000: │ languageName: unknown +➤ YN0000: │ linkType: soft +➤ YN0000: │ +➤ YN0028: │ The lockfile would have been modified by this install, which is explicitly forbidden. +➤ YN0000: └ Completed +➤ YN0000: · Failed with errors in +``` + +## `node -e 'const fs = require('\''node:fs'\''); if ('\!'fs.readFileSync('\''yarn.lock'\'').equals(fs.readFileSync('\''before.lock'\''))) process.exit(1); console.log('\''lockfile unchanged'\'');'` + +``` +lockfile unchanged +``` + +## `vp install --no-frozen-lockfile` + +package-free installs map no-frozen-lockfile to no-immutable + +``` +VITE+ - The Unified Toolchain for the Web + +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0085: │ + install-option-dep@file:./dep-v2#./dep-v2::hash=5590bc&locator=install-package-options%40workspace%3A. +➤ YN0085: │ - install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A. +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in +``` + +## `node -p require('./node_modules/install-option-dep/package.json').version` + +``` +2.0.0 +``` + +## `vp add ./dep --no-frozen-lockfile` + +direct add also warns for the unsupported immutable override + +``` +warn: yarn does not support --no-frozen-lockfile. +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0085: │ + install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A. +➤ YN0085: │ - install-option-dep@file:./dep-v2#./dep-v2::hash=5590bc&locator=install-package-options%40workspace%3A. +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_lockfile_only.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_lockfile_only.md new file mode 100644 index 0000000000..4d6ee082f1 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_lockfile_only.md @@ -0,0 +1,43 @@ +# yarn_berry_lockfile_only + +## `vpt json-edit package.json packageManager yarn@4.10.3` + + +## `vp install ./dep --lockfile-only --ignore-scripts` + +use update-lockfile rather than skip-build when both options are supplied + +``` +VITE+ - The Unified Toolchain for the Web + +warn: yarn@2+ --mode can only be specified once; --lockfile-only takes priority over --ignore-scripts +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0085: │ + install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A. +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0013: │ A package was added to the project (+ KiB). +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0073: │ Skipped due to mode=update-lockfile +➤ YN0000: └ Completed +➤ YN0000: · Done with warnings in +``` + +## `vpt stat-file yarn.lock --assert file` + +``` +yarn.lock: file +``` + +## `vpt stat-file node_modules --assert missing` + +``` +node_modules: missing +``` + +## `vpt stat-file .pnp.cjs --assert missing` + +``` +.pnp.cjs: missing +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_classic_frozen_lockfile_modes.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_classic_frozen_lockfile_modes.md new file mode 100644 index 0000000000..9107a42f4d --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_classic_frozen_lockfile_modes.md @@ -0,0 +1,117 @@ +# yarn_classic_frozen_lockfile_modes + +## `vpt json-edit package.json packageManager yarn@1.22.22` + + +## `vp install ./dep` + + +## `vpt cp yarn.lock before.lock` + + +## `vp install ./dep-v2 --frozen-lockfile` + +Classic add preserves the lockfile but allows manifest changes + +``` +VITE+ - The Unified Toolchain for the Web + +yarn add +[1/4] Resolving packages... +[2/4] Fetching packages... +[3/4] Linking dependencies... +[4/4] Building fresh packages... + +success Saved 1 new dependency. +info Direct dependencies +└─ install-option-dep@2.0.0 +info All dependencies +└─ install-option-dep@2.0.0 + +Done in . +``` + +## `node -e 'const fs = require('\''node:fs'\''); if ('\!'fs.readFileSync('\''yarn.lock'\'').equals(fs.readFileSync('\''before.lock'\''))) process.exit(1); console.log('\''lockfile unchanged'\'');'` + +``` +lockfile unchanged +``` + +## `node -p require('./package.json').dependencies['install-option-dep']` + +``` +./dep-v2 +``` + +## `vp install --frozen-lockfile` + +package-free installs still enforce the frozen lockfile + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + +yarn install +[1/4] Resolving packages... +error Your lockfile needs to be updated, but yarn was run with `--frozen-lockfile`. +info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command. +``` + +## `node -e 'const fs = require('\''node:fs'\''); if ('\!'fs.readFileSync('\''yarn.lock'\'').equals(fs.readFileSync('\''before.lock'\''))) process.exit(1); console.log('\''lockfile unchanged'\'');'` + +``` +lockfile unchanged +``` + +## `vp install --no-frozen-lockfile` + +package-free installs can explicitly allow lockfile changes + +``` +VITE+ - The Unified Toolchain for the Web + +yarn install +[1/4] Resolving packages... +[2/4] Fetching packages... +[3/4] Linking dependencies... +[4/4] Building fresh packages... + +success Saved lockfile. + +Done in . +``` + +## `node -p require('./node_modules/install-option-dep/package.json').version` + +``` +2.0.0 +``` + +## `vp add ./dep --no-frozen-lockfile` + +Classic add warns and drops the unsupported negated flag + +``` +warn: yarn does not support --no-frozen-lockfile. +yarn add +[1/4] Resolving packages... +[2/4] Fetching packages... +[3/4] Linking dependencies... +[4/4] Building fresh packages... + +success Saved lockfile. +success Saved 1 new dependency. +info Direct dependencies +└─ install-option-dep@1.0.0 +info All dependencies +└─ install-option-dep@1.0.0 + +Done in . +``` + +## `node -p require('./node_modules/install-option-dep/package.json').version` + +``` +1.0.0 +``` diff --git a/crates/vp_pm_cli/src/cli.rs b/crates/vp_pm_cli/src/cli.rs index ffa93bb3c8..d6d2cb354b 100644 --- a/crates/vp_pm_cli/src/cli.rs +++ b/crates/vp_pm_cli/src/cli.rs @@ -207,10 +207,7 @@ impl PackageManagerCommand { /// only command whose typed clap shape selects between two resolvers. pub(crate) fn resolve_for_manager(self, manager: &PackageManager) -> Result { match self { - Self::Install(args) if !args.packages.is_empty() => { - resolve_args_for_manager(manager, args.into_add_args()) - } - Self::Install(args) => resolve_args_for_manager(manager, args), + Self::Install(args) => args.resolve_args_for_manager(manager), Self::Add(args) => resolve_args_for_manager(manager, args), Self::Remove(args) => resolve_args_for_manager(manager, args), Self::Update(args) => resolve_args_for_manager(manager, args), diff --git a/crates/vp_pm_cli/src/resolution/commands/add.rs b/crates/vp_pm_cli/src/resolution/commands/add.rs index 937f477031..2c051efe08 100644 --- a/crates/vp_pm_cli/src/resolution/commands/add.rs +++ b/crates/vp_pm_cli/src/resolution/commands/add.rs @@ -2,7 +2,7 @@ use vp_pm_cli_macros::pm_args; use super::parse_positive_usize; use crate::resolution::{ - Bun, CommandBuilder, CommandResolution, Diagnostics, Npm, Pnpm, Resolve, Yarn, + Bun, CommandBuilder, CommandResolution, DiagnosticKind, Diagnostics, Npm, Pnpm, Resolve, Yarn, }; #[pm_args] @@ -31,6 +31,52 @@ pub struct AddArgs { #[arg(long)] pub(crate) ignore_scripts: bool, + /// Do not install optionalDependencies + #[arg(long, conflicts_with = "global", not_supported(yarn >= "2"))] + pub(crate) no_optional: bool, + + /// Fail if lockfile needs to be updated + // Yarn Classic accepts this on `add` but skips install's frozen-lockfile validation. + #[arg(long, conflicts_with = "global", overrides_with = "no_frozen_lockfile", not_supported(npm, pnpm, yarn >= "2"))] + pub(crate) frozen_lockfile: bool, + + /// Allow lockfile updates + #[arg( + long, + conflicts_with = "global", + overrides_with = "frozen_lockfile", + not_supported(npm, pnpm, yarn) + )] + pub(crate) no_frozen_lockfile: bool, + + /// Only update lockfile, don't install + #[arg(long, conflicts_with = "global", not_supported(yarn < "2"))] + pub(crate) lockfile_only: bool, + + /// Use cached packages when available + #[arg(long, conflicts_with = "global", not_supported(yarn >= "2", bun))] + pub(crate) prefer_offline: bool, + + /// Only use packages already in cache + #[arg(long, conflicts_with = "global", not_supported(yarn >= "2", bun))] + pub(crate) offline: bool, + + /// Force reinstall all dependencies + #[arg(short = 'f', long, conflicts_with = "global", not_supported(yarn >= "2"))] + pub(crate) force: bool, + + /// Don't read or generate lockfile + #[arg(long, conflicts_with = "global", not_supported(yarn >= "2", bun))] + pub(crate) no_lockfile: bool, + + /// Create flat node_modules (pnpm only) + #[arg(long, conflicts_with = "global", not_supported(npm, yarn, bun))] + pub(crate) shamefully_hoist: bool, + + /// Suppress package manager output + #[arg(long, conflicts_with = "global", not_supported(yarn >= "2"))] + pub(crate) silent: bool, + /// Filter packages in monorepo (can be used multiple times) #[arg(long, value_name = "PATTERN", not_supported(bun < "1.4"))] pub(crate) filter: Vec, @@ -151,6 +197,14 @@ impl Resolve for Pnpm { cmd.arg(vt_str::format!("--allow-build={allow_build}")); } cmd.arg_if("--ignore-scripts", args.ignore_scripts) + .arg_if("--no-optional", args.no_optional) + .arg_if("--lockfile-only", args.lockfile_only) + .arg_if("--prefer-offline", args.prefer_offline) + .arg_if("--offline", args.offline) + .arg_if("--force", args.force) + .arg_if("--no-lockfile", args.no_lockfile) + .arg_if("--shamefully-hoist", args.shamefully_hoist) + .arg_if("--silent", args.silent) .extend(args.pass_through_args.iter()) .extend(args.packages.iter()); cmd.into() @@ -189,8 +243,16 @@ impl Npm { } cmd.arg_if("--save-exact", args.save_exact) .arg_if("--ignore-scripts", args.ignore_scripts) - .extend(args.pass_through_args.iter()) - .extend(args.packages.iter()); + .arg_if("--omit=optional", args.no_optional) + .arg_if("--package-lock-only", args.lockfile_only) + .arg_if("--prefer-offline", args.prefer_offline) + .arg_if("--offline", args.offline) + .arg_if("--force", args.force) + .arg_if("--no-package-lock", args.no_lockfile); + if args.silent { + cmd.arg("--loglevel").arg("silent"); + } + cmd.extend(args.pass_through_args.iter()).extend(args.packages.iter()); cmd.into() } } @@ -202,7 +264,7 @@ impl Resolve for Npm { } impl Resolve for Yarn { - fn resolve(&self, args: &AddArgs, _diag: &mut Diagnostics) -> CommandResolution { + fn resolve(&self, args: &AddArgs, diag: &mut Diagnostics) -> CommandResolution { if args.global { return Npm::resolve_add(args); } @@ -233,12 +295,27 @@ impl Resolve for Yarn { Some(SaveDependencyTarget::Production) | None => {} } cmd.arg_if("--exact", args.save_exact); - if args.ignore_scripts { - if self.is_berry() { + if self.is_berry() { + if args.lockfile_only { + cmd.arg("--mode").arg("update-lockfile"); + if args.ignore_scripts { + diag.warn( + DiagnosticKind::BehaviorChange, + "yarn@2+ --mode can only be specified once; --lockfile-only takes priority over --ignore-scripts", + ); + } + } else if args.ignore_scripts { cmd.arg("--mode").arg("skip-build"); - } else { - cmd.arg("--ignore-scripts"); } + } else { + cmd.arg_if("--ignore-scripts", args.ignore_scripts) + .arg_if("--ignore-optional", args.no_optional) + .arg_if("--frozen-lockfile", args.frozen_lockfile) + .arg_if("--prefer-offline", args.prefer_offline) + .arg_if("--offline", args.offline) + .arg_if("--force", args.force) + .arg_if("--no-lockfile", args.no_lockfile) + .arg_if("--silent", args.silent); } cmd.extend(args.pass_through_args.iter()).extend(args.packages.iter()); cmd.into() @@ -267,8 +344,18 @@ impl Resolve for Bun { cmd.arg_if("--exact", args.save_exact) .arg_if("--catalog", args.save_catalog) .arg_if("--ignore-scripts", args.ignore_scripts) - .extend(args.pass_through_args.iter()) - .extend(args.packages.iter()); + .arg_if("--lockfile-only", args.lockfile_only) + .arg_if("--force", args.force) + .arg_if("--silent", args.silent); + if args.no_optional { + cmd.arg("--omit").arg("optional"); + } + if args.no_frozen_lockfile { + cmd.arg("--no-frozen-lockfile"); + } else { + cmd.arg_if("--frozen-lockfile", args.frozen_lockfile); + } + cmd.extend(args.pass_through_args.iter()).extend(args.packages.iter()); cmd.into() } } @@ -564,6 +651,81 @@ mod tests { assert_eq!(command.args, vec!["add", "--allow-build=react,napi", "react"]); } + #[test] + fn yarn_add_respects_frozen_lockfile_support() { + for flag in ["--frozen-lockfile", "--no-frozen-lockfile"] { + let args = parse_args::([flag, "react"]).unwrap(); + let classic = resolve(&yarn("1.22.22"), args.clone()); + if flag == "--frozen-lockfile" { + assert_eq!(expect_run(classic.outcome).args, ["add", flag, "react"]); + assert!(classic.diagnostics.is_empty()); + } else { + assert_eq!(expect_run(classic.outcome).args, ["add", "react"]); + assert_eq!(classic.diagnostics.len(), 1); + assert_eq!( + classic.diagnostics[0].message, + "yarn does not support --no-frozen-lockfile.", + ); + } + + let berry = resolve(&yarn("4.0.0"), args); + assert_eq!(expect_run(berry.outcome).args, ["add", "react"]); + assert_eq!(berry.diagnostics.len(), 1); + assert_eq!(berry.diagnostics[0].kind, DiagnosticKind::UnsupportedOptionDropped); + assert_eq!( + berry.diagnostics[0].message, + if flag == "--frozen-lockfile" { + "yarn >=2 does not support --frozen-lockfile." + } else { + "yarn does not support --no-frozen-lockfile." + }, + ); + } + } + + #[test] + fn yarn_lockfile_only_takes_priority_over_ignore_scripts() { + let resolution = resolve( + &yarn("4.0.0"), + AddArgs { lockfile_only: true, ignore_scripts: true, ..add_args(&["react"]) }, + ); + let command = expect_run(resolution.outcome); + assert_eq!(command.args, ["add", "--mode", "update-lockfile", "react"]); + assert_eq!(resolution.diagnostics.len(), 1); + assert_eq!(resolution.diagnostics[0].kind, DiagnosticKind::BehaviorChange); + } + + #[test] + fn add_install_options_do_not_change_managed_global_commands() { + for flag in [ + "--no-optional", + "--frozen-lockfile", + "--no-frozen-lockfile", + "--lockfile-only", + "--prefer-offline", + "--offline", + "--force", + "--no-lockfile", + "--shamefully-hoist", + "--silent", + ] { + let error = parse_args::(["--global", flag, "react"]).unwrap_err(); + assert_eq!(error.kind(), clap::error::ErrorKind::ArgumentConflict, "{flag}"); + } + } + + #[test] + fn add_frozen_lockfile_flags_use_last_value() { + for (first, last) in [ + ("--frozen-lockfile", "--no-frozen-lockfile"), + ("--no-frozen-lockfile", "--frozen-lockfile"), + ] { + let args = parse_args::([first, last, "react"]).unwrap(); + let command = expect_run(resolve(&bun("1.3.11"), args).outcome); + assert_eq!(command.args, ["add", last, "react"]); + } + } + #[test] fn test_bun_basic_add() { let resolution = resolve(&bun("1.3.11"), add_args(&["react"])); diff --git a/crates/vp_pm_cli/src/resolution/commands/install.rs b/crates/vp_pm_cli/src/resolution/commands/install.rs index 0f151049a1..01012a0945 100644 --- a/crates/vp_pm_cli/src/resolution/commands/install.rs +++ b/crates/vp_pm_cli/src/resolution/commands/install.rs @@ -1,9 +1,12 @@ use vp_pm_cli_macros::pm_args; use super::parse_positive_usize; -use crate::resolution::{ - AddArgs, Bun, CommandBuilder, CommandResolution, DiagnosticKind, Diagnostics, Npm, Pnpm, - Resolve, SaveDependencyArgs, Yarn, +use crate::{ + Error, PackageManager, + resolution::{ + AddArgs, Bun, CommandBuilder, CommandResolution, DiagnosticKind, Diagnostics, Npm, Pnpm, + Resolution, Resolve, SaveDependencyArgs, Yarn, resolve_for_manager, + }, }; #[pm_args] @@ -143,7 +146,49 @@ impl Resolve for Pnpm { } impl InstallArgs { - pub(crate) fn into_add_args(self) -> AddArgs { + pub(crate) fn resolve_args_for_manager( + mut self, + manager: &PackageManager, + ) -> Result { + let adding_packages = !self.packages.is_empty(); + // Diagnose the selected mode before conversion discards fields, and before + // manager-specific support rules can produce misleading or duplicate warnings. + let (mode, unsupported) = if adding_packages { + ( + "with package names", + vec![ + ("--fix-lockfile", std::mem::take(&mut self.fix_lockfile)), + ("--resolution-only", std::mem::take(&mut self.resolution_only)), + ], + ) + } else { + ( + "without package names", + vec![ + ("--save-exact", std::mem::take(&mut self.save_exact)), + ("--save-peer", std::mem::take(&mut self.save_peer)), + ("--save-optional", std::mem::take(&mut self.save_optional)), + ("--save-catalog", std::mem::take(&mut self.save_catalog)), + ], + ) + }; + let mut resolution = if adding_packages { + resolve_for_manager(manager, self.into_add_args())? + } else { + resolve_for_manager(manager, self)? + }; + for (option, supplied) in unsupported { + if supplied { + resolution.diagnostics.warn( + DiagnosticKind::UnsupportedOptionDropped, + vt_str::format!("install {mode} does not support {option}."), + ); + } + } + Ok(resolution) + } + + fn into_add_args(self) -> AddArgs { let save_dependency = if self.dev { SaveDependencyArgs { save_dev: true, ..Default::default() } } else if self.save_peer { @@ -163,6 +208,16 @@ impl InstallArgs { save_catalog: self.save_catalog, allow_build: None, ignore_scripts: self.ignore_scripts, + no_optional: self.no_optional, + frozen_lockfile: self.frozen_lockfile, + no_frozen_lockfile: self.no_frozen_lockfile, + lockfile_only: self.lockfile_only, + prefer_offline: self.prefer_offline, + offline: self.offline, + force: self.force, + no_lockfile: self.no_lockfile, + shamefully_hoist: self.shamefully_hoist, + silent: self.silent, filter: self.filter, workspace_root: self.workspace_root, workspace: false, @@ -295,6 +350,77 @@ mod tests { test_utils::{bun, expect_run, npm, pnpm, yarn}, }; + #[test] + fn install_with_packages_warns_on_install_only_options() { + let manager = crate::PackageManager::from_bin_prefix( + crate::PackageManagerType::Pnpm, + "11.24.0", + vt_path::current_dir().unwrap().join(".test-package-manager/bin"), + ); + let args = InstallArgs { + packages: vec!["react".to_string()], + fix_lockfile: true, + resolution_only: true, + lockfile_only: true, + save_exact: true, + ..Default::default() + }; + let resolution = + crate::cli::PackageManagerCommand::Install(args).resolve_for_manager(&manager).unwrap(); + assert_eq!( + expect_run(resolution.outcome).args, + ["add", "--save-exact", "--lockfile-only", "react"] + ); + assert_eq!( + resolution + .diagnostics + .iter() + .map(|diagnostic| diagnostic.message.as_str()) + .collect::>(), + [ + "install with package names does not support --fix-lockfile.", + "install with package names does not support --resolution-only.", + ], + ); + } + + #[test] + fn install_without_packages_warns_on_add_only_options() { + let manager = crate::PackageManager::from_bin_prefix( + crate::PackageManagerType::Npm, + "11.13.0", + vt_path::current_dir().unwrap().join(".test-package-manager/bin"), + ); + let args = InstallArgs { + save_exact: true, + save_peer: true, + save_optional: true, + save_catalog: true, + lockfile_only: true, + offline: true, + ..Default::default() + }; + let resolution = + crate::cli::PackageManagerCommand::Install(args).resolve_for_manager(&manager).unwrap(); + assert_eq!( + expect_run(resolution.outcome).args, + ["install", "--package-lock-only", "--offline"] + ); + assert_eq!( + resolution + .diagnostics + .iter() + .map(|diagnostic| diagnostic.message.as_str()) + .collect::>(), + [ + "install without package names does not support --save-exact.", + "install without package names does not support --save-peer.", + "install without package names does not support --save-optional.", + "install without package names does not support --save-catalog.", + ], + ); + } + #[test] fn test_pnpm_basic_install() { let command = expect_run(resolve(&pnpm("10.0.0"), InstallArgs::default()).outcome); From 8933963ac5df6e245b9d4de5e5c8721b4305e82e Mon Sep 17 00:00:00 2001 From: JongKyung Lee Date: Wed, 16 Sep 2026 15:05:00 +0900 Subject: [PATCH 2/9] fix(pm): reject frozen package additions with Yarn Classic Classic add accepts --frozen-lockfile without checking manifest and lockfile consistency. Reject this combination before execution for both add and install with package names. Preserve package-free frozen installs and other package managers. Verify rejected additions leave the manifest, lockfile, and installed dependency unchanged, while package-free installs still enforce the frozen lockfile. --- .../install_package_options/snapshots.toml | 10 ++-- .../yarn_classic_frozen_lockfile_modes.md | 52 +++++++++++++------ .../vp_pm_cli/src/resolution/commands/add.rs | 16 ++++-- 3 files changed, 56 insertions(+), 22 deletions(-) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml index 1164afb34e..302d83bdb3 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml @@ -80,9 +80,13 @@ steps = [ { argv = ["vpt", "json-edit", "package.json", "packageManager", "yarn@1.22.22"], snapshot = false }, { argv = ["vp", "install", "./dep"], snapshot = false }, { argv = ["vpt", "cp", "yarn.lock", "before.lock"], snapshot = false }, - { argv = ["vp", "install", "./dep-v2", "--frozen-lockfile"], comment = "Classic add preserves the lockfile but allows manifest changes" }, - ["node", "-e", "const fs = require('node:fs'); if (!fs.readFileSync('yarn.lock').equals(fs.readFileSync('before.lock'))) process.exit(1); console.log('lockfile unchanged');"], - ["node", "-p", "require('./package.json').dependencies['install-option-dep']"], + { argv = ["vpt", "cp", "package.json", "before.json"], snapshot = false }, + { argv = ["vp", "install", "./dep-v2", "--frozen-lockfile"], comment = "reject named-package frozen installs before invoking Classic add", continue-on-failure = true }, + { argv = ["vp", "add", "./dep-v2", "--frozen-lockfile"], comment = "direct add rejects the same combination", continue-on-failure = true }, + { argv = ["node", "-e", "const fs = require('node:fs'); for (const [file, before] of [['package.json', 'before.json'], ['yarn.lock', 'before.lock']]) { if (!fs.readFileSync(file).equals(fs.readFileSync(before))) process.exit(1); } console.log('manifest and lockfile unchanged');"], continue-on-failure = true }, + ["node", "-p", "require('./node_modules/install-option-dep/package.json').version"], + { argv = ["vp", "install", "--frozen-lockfile"], comment = "package-free frozen installs succeed when the manifest and lockfile agree" }, + { argv = ["vpt", "json-edit", "package.json", "dependencies.install-option-dep", "file:./dep-v2"], snapshot = false }, { argv = ["vp", "install", "--frozen-lockfile"], comment = "package-free installs still enforce the frozen lockfile", continue-on-failure = true }, { argv = ["node", "-e", "const fs = require('node:fs'); if (!fs.readFileSync('yarn.lock').equals(fs.readFileSync('before.lock'))) process.exit(1); console.log('lockfile unchanged');"], continue-on-failure = true }, { argv = ["vp", "install", "--no-frozen-lockfile"], comment = "package-free installs can explicitly allow lockfile changes" }, diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_classic_frozen_lockfile_modes.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_classic_frozen_lockfile_modes.md index 9107a42f4d..6d098502af 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_classic_frozen_lockfile_modes.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_classic_frozen_lockfile_modes.md @@ -9,40 +9,60 @@ ## `vpt cp yarn.lock before.lock` +## `vpt cp package.json before.json` + + ## `vp install ./dep-v2 --frozen-lockfile` -Classic add preserves the lockfile but allows manifest changes +reject named-package frozen installs before invoking Classic add + +**Exit code:** 1 ``` VITE+ - The Unified Toolchain for the Web -yarn add -[1/4] Resolving packages... -[2/4] Fetching packages... -[3/4] Linking dependencies... -[4/4] Building fresh packages... +Invalid argument: Yarn Classic `add` cannot enforce `--frozen-lockfile`. +``` -success Saved 1 new dependency. -info Direct dependencies -└─ install-option-dep@2.0.0 -info All dependencies -└─ install-option-dep@2.0.0 +## `vp add ./dep-v2 --frozen-lockfile` -Done in . +direct add rejects the same combination + +**Exit code:** 1 + +``` +Invalid argument: Yarn Classic `add` cannot enforce `--frozen-lockfile`. ``` -## `node -e 'const fs = require('\''node:fs'\''); if ('\!'fs.readFileSync('\''yarn.lock'\'').equals(fs.readFileSync('\''before.lock'\''))) process.exit(1); console.log('\''lockfile unchanged'\'');'` +## `node -e 'const fs = require('\''node:fs'\''); for (const [file, before] of [['\''package.json'\'', '\''before.json'\''], ['\''yarn.lock'\'', '\''before.lock'\'']]) { if ('\!'fs.readFileSync(file).equals(fs.readFileSync(before))) process.exit(1); } console.log('\''manifest and lockfile unchanged'\'');'` ``` -lockfile unchanged +manifest and lockfile unchanged ``` -## `node -p require('./package.json').dependencies['install-option-dep']` +## `node -p require('./node_modules/install-option-dep/package.json').version` + +``` +1.0.0 +``` + +## `vp install --frozen-lockfile` + +package-free frozen installs succeed when the manifest and lockfile agree ``` -./dep-v2 +VITE+ - The Unified Toolchain for the Web + +yarn install +[1/4] Resolving packages... +success Already up-to-date. + +Done in . ``` +## `vpt json-edit package.json dependencies.install-option-dep file:./dep-v2` + + ## `vp install --frozen-lockfile` package-free installs still enforce the frozen lockfile diff --git a/crates/vp_pm_cli/src/resolution/commands/add.rs b/crates/vp_pm_cli/src/resolution/commands/add.rs index 2c051efe08..166866d0a3 100644 --- a/crates/vp_pm_cli/src/resolution/commands/add.rs +++ b/crates/vp_pm_cli/src/resolution/commands/add.rs @@ -36,7 +36,6 @@ pub struct AddArgs { pub(crate) no_optional: bool, /// Fail if lockfile needs to be updated - // Yarn Classic accepts this on `add` but skips install's frozen-lockfile validation. #[arg(long, conflicts_with = "global", overrides_with = "no_frozen_lockfile", not_supported(npm, pnpm, yarn >= "2"))] pub(crate) frozen_lockfile: bool, @@ -269,6 +268,14 @@ impl Resolve for Yarn { return Npm::resolve_add(args); } + // Classic accepts the flag but skips the frozen-lockfile consistency check on add. + if !self.is_berry() && args.frozen_lockfile { + return CommandResolution::InvalidArgument( + "Invalid argument: Yarn Classic `add` cannot enforce `--frozen-lockfile`." + .to_string(), + ); + } + let mut cmd = CommandBuilder::new("yarn"); if !args.filter.is_empty() { if !self.is_berry() { @@ -310,7 +317,6 @@ impl Resolve for Yarn { } else { cmd.arg_if("--ignore-scripts", args.ignore_scripts) .arg_if("--ignore-optional", args.no_optional) - .arg_if("--frozen-lockfile", args.frozen_lockfile) .arg_if("--prefer-offline", args.prefer_offline) .arg_if("--offline", args.offline) .arg_if("--force", args.force) @@ -657,7 +663,11 @@ mod tests { let args = parse_args::([flag, "react"]).unwrap(); let classic = resolve(&yarn("1.22.22"), args.clone()); if flag == "--frozen-lockfile" { - assert_eq!(expect_run(classic.outcome).args, ["add", flag, "react"]); + assert!(matches!( + classic.outcome, + CommandResolution::InvalidArgument(message) + if message == "Invalid argument: Yarn Classic `add` cannot enforce `--frozen-lockfile`." + )); assert!(classic.diagnostics.is_empty()); } else { assert_eq!(expect_run(classic.outcome).args, ["add", "react"]); From d6ed532df06c0984d8d39713a112e8cefa32bca1 Mon Sep 17 00:00:00 2001 From: JongKyung Lee Date: Wed, 16 Sep 2026 15:24:36 +0900 Subject: [PATCH 3/9] fix(pm): warn on frozen lockfile options for Yarn add Use the shared unsupported-option policy for all Yarn add versions instead of rejecting frozen additions only on Classic. Warn, drop the option, and continue with the add command. Preserve frozen-lockfile enforcement for package-free installs and Bun add. Cover both add entrypoints and package-free frozen installs in snapshots. --- .../install_package_options/snapshots.toml | 8 +-- .../yarn_berry_frozen_lockfile_modes.md | 2 +- .../yarn_classic_frozen_lockfile_modes.md | 62 ++++++++++++------- .../vp_pm_cli/src/resolution/commands/add.rs | 57 ++++++----------- 4 files changed, 64 insertions(+), 65 deletions(-) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml index 302d83bdb3..9a259f0878 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml @@ -78,12 +78,10 @@ name = "yarn_classic_frozen_lockfile_modes" vp = "global" steps = [ { argv = ["vpt", "json-edit", "package.json", "packageManager", "yarn@1.22.22"], snapshot = false }, - { argv = ["vp", "install", "./dep"], snapshot = false }, + { argv = ["vp", "install", "./dep", "--frozen-lockfile"], comment = "named-package installs warn and drop the unsupported add option" }, + ["vpt", "stat-file", "yarn.lock", "--assert", "file"], + { argv = ["vp", "add", "./dep", "--frozen-lockfile"], comment = "direct add also warns and continues without the option" }, { argv = ["vpt", "cp", "yarn.lock", "before.lock"], snapshot = false }, - { argv = ["vpt", "cp", "package.json", "before.json"], snapshot = false }, - { argv = ["vp", "install", "./dep-v2", "--frozen-lockfile"], comment = "reject named-package frozen installs before invoking Classic add", continue-on-failure = true }, - { argv = ["vp", "add", "./dep-v2", "--frozen-lockfile"], comment = "direct add rejects the same combination", continue-on-failure = true }, - { argv = ["node", "-e", "const fs = require('node:fs'); for (const [file, before] of [['package.json', 'before.json'], ['yarn.lock', 'before.lock']]) { if (!fs.readFileSync(file).equals(fs.readFileSync(before))) process.exit(1); } console.log('manifest and lockfile unchanged');"], continue-on-failure = true }, ["node", "-p", "require('./node_modules/install-option-dep/package.json').version"], { argv = ["vp", "install", "--frozen-lockfile"], comment = "package-free frozen installs succeed when the manifest and lockfile agree" }, { argv = ["vpt", "json-edit", "package.json", "dependencies.install-option-dep", "file:./dep-v2"], snapshot = false }, diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md index 427e424269..972b1c3a2a 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md @@ -14,7 +14,7 @@ named-package installs use add, which has no immutable option ``` VITE+ - The Unified Toolchain for the Web -warn: yarn >=2 does not support --frozen-lockfile. +warn: yarn does not support --frozen-lockfile. ➤ YN0000: · Yarn ➤ YN0000: ┌ Resolution step ➤ YN0085: │ + install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A. diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_classic_frozen_lockfile_modes.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_classic_frozen_lockfile_modes.md index 6d098502af..bb1fa1a3e6 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_classic_frozen_lockfile_modes.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_classic_frozen_lockfile_modes.md @@ -3,42 +3,60 @@ ## `vpt json-edit package.json packageManager yarn@1.22.22` -## `vp install ./dep` +## `vp install ./dep --frozen-lockfile` +named-package installs warn and drop the unsupported add option -## `vpt cp yarn.lock before.lock` - - -## `vpt cp package.json before.json` +``` +VITE+ - The Unified Toolchain for the Web +warn: yarn does not support --frozen-lockfile. +yarn add +info No lockfile found. +[1/4] Resolving packages... +[2/4] Fetching packages... +[3/4] Linking dependencies... +[4/4] Building fresh packages... -## `vp install ./dep-v2 --frozen-lockfile` +success Saved lockfile. +success Saved 1 new dependency. +info Direct dependencies +└─ install-option-dep@1.0.0 +info All dependencies +└─ install-option-dep@1.0.0 -reject named-package frozen installs before invoking Classic add +Done in . +``` -**Exit code:** 1 +## `vpt stat-file yarn.lock --assert file` ``` -VITE+ - The Unified Toolchain for the Web - -Invalid argument: Yarn Classic `add` cannot enforce `--frozen-lockfile`. +yarn.lock: file ``` -## `vp add ./dep-v2 --frozen-lockfile` +## `vp add ./dep --frozen-lockfile` -direct add rejects the same combination +direct add also warns and continues without the option -**Exit code:** 1 - -``` -Invalid argument: Yarn Classic `add` cannot enforce `--frozen-lockfile`. ``` +warn: yarn does not support --frozen-lockfile. +yarn add +[1/4] Resolving packages... +[2/4] Fetching packages... +[3/4] Linking dependencies... +[4/4] Building fresh packages... -## `node -e 'const fs = require('\''node:fs'\''); for (const [file, before] of [['\''package.json'\'', '\''before.json'\''], ['\''yarn.lock'\'', '\''before.lock'\'']]) { if ('\!'fs.readFileSync(file).equals(fs.readFileSync(before))) process.exit(1); } console.log('\''manifest and lockfile unchanged'\'');'` +success Saved 1 new dependency. +info Direct dependencies +└─ install-option-dep@1.0.0 +info All dependencies +└─ install-option-dep@1.0.0 +Done in . ``` -manifest and lockfile unchanged -``` + +## `vpt cp yarn.lock before.lock` + ## `node -p require('./node_modules/install-option-dep/package.json').version` @@ -55,7 +73,9 @@ VITE+ - The Unified Toolchain for the Web yarn install [1/4] Resolving packages... -success Already up-to-date. +[2/4] Fetching packages... +[3/4] Linking dependencies... +[4/4] Building fresh packages... Done in . ``` diff --git a/crates/vp_pm_cli/src/resolution/commands/add.rs b/crates/vp_pm_cli/src/resolution/commands/add.rs index 166866d0a3..eb00a11789 100644 --- a/crates/vp_pm_cli/src/resolution/commands/add.rs +++ b/crates/vp_pm_cli/src/resolution/commands/add.rs @@ -36,7 +36,12 @@ pub struct AddArgs { pub(crate) no_optional: bool, /// Fail if lockfile needs to be updated - #[arg(long, conflicts_with = "global", overrides_with = "no_frozen_lockfile", not_supported(npm, pnpm, yarn >= "2"))] + #[arg( + long, + conflicts_with = "global", + overrides_with = "no_frozen_lockfile", + not_supported(npm, pnpm, yarn) + )] pub(crate) frozen_lockfile: bool, /// Allow lockfile updates @@ -268,14 +273,6 @@ impl Resolve for Yarn { return Npm::resolve_add(args); } - // Classic accepts the flag but skips the frozen-lockfile consistency check on add. - if !self.is_berry() && args.frozen_lockfile { - return CommandResolution::InvalidArgument( - "Invalid argument: Yarn Classic `add` cannot enforce `--frozen-lockfile`." - .to_string(), - ); - } - let mut cmd = CommandBuilder::new("yarn"); if !args.filter.is_empty() { if !self.is_berry() { @@ -658,38 +655,22 @@ mod tests { } #[test] - fn yarn_add_respects_frozen_lockfile_support() { - for flag in ["--frozen-lockfile", "--no-frozen-lockfile"] { - let args = parse_args::([flag, "react"]).unwrap(); - let classic = resolve(&yarn("1.22.22"), args.clone()); - if flag == "--frozen-lockfile" { - assert!(matches!( - classic.outcome, - CommandResolution::InvalidArgument(message) - if message == "Invalid argument: Yarn Classic `add` cannot enforce `--frozen-lockfile`." - )); - assert!(classic.diagnostics.is_empty()); - } else { - assert_eq!(expect_run(classic.outcome).args, ["add", "react"]); - assert_eq!(classic.diagnostics.len(), 1); + fn yarn_add_drops_frozen_lockfile_options() { + for version in ["1.22.22", "4.0.0"] { + for flag in ["--frozen-lockfile", "--no-frozen-lockfile"] { + let args = parse_args::([flag, "react"]).unwrap(); + let resolution = resolve(&yarn(version), args); + assert_eq!(expect_run(resolution.outcome).args, ["add", "react"]); + assert_eq!(resolution.diagnostics.len(), 1); + assert_eq!( + resolution.diagnostics[0].kind, + DiagnosticKind::UnsupportedOptionDropped + ); assert_eq!( - classic.diagnostics[0].message, - "yarn does not support --no-frozen-lockfile.", + resolution.diagnostics[0].message, + vt_str::format!("yarn does not support {flag}.").as_str(), ); } - - let berry = resolve(&yarn("4.0.0"), args); - assert_eq!(expect_run(berry.outcome).args, ["add", "react"]); - assert_eq!(berry.diagnostics.len(), 1); - assert_eq!(berry.diagnostics[0].kind, DiagnosticKind::UnsupportedOptionDropped); - assert_eq!( - berry.diagnostics[0].message, - if flag == "--frozen-lockfile" { - "yarn >=2 does not support --frozen-lockfile." - } else { - "yarn does not support --no-frozen-lockfile." - }, - ); } } From 61d4f8375721a3ef2f6642433d0babd2a77e9985 Mon Sep 17 00:00:00 2001 From: JongKyung Lee Date: Wed, 16 Sep 2026 15:43:41 +0900 Subject: [PATCH 4/9] fix(pm): honor add silent mode in wrapper output Include add's silent flag in the shared quiet-mode and diagnostics checks so Vite+ suppresses compatibility warnings and cached upgrade notices. Cover silent and normal add classification, and verify a silent add leaves the pending upgrade notice available for the next interactive command. --- .../command_upgrade_check/snapshots.toml | 5 +++++ .../command_upgrade_background_notice.md | 19 +++++++++++++++++++ crates/vp_pm_cli/src/cli.rs | 11 ++++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_upgrade_check/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_upgrade_check/snapshots.toml index 493fa12dca..d718be0715 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_upgrade_check/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_upgrade_check/snapshots.toml @@ -17,6 +17,11 @@ steps = [ { argv = ["node", "-e", "(async()=>{const fs=require('node:fs');const path=require('node:path');const file=path.join(process.env.VP_HOME,'cache','upgrade-check.json');const deadline=Date.now()+5000;for(;;){try{if(JSON.parse(fs.readFileSync(file,'utf8')).status==='available')return}catch{}if(Date.now()>=deadline)process.exit(1);await new Promise(resolve=>setTimeout(resolve,25))}})()"], snapshot = false }, { argv = ["vpt", "grep-file", "$VP_HOME/cache/upgrade-check.json", '"status":"available"'], snapshot = false }, { argv = ["vp", "env", "list", "--json"], comment = "Machine-readable output does not consume the pending notice.", snapshot = false }, + { argv = ["vpt", "write-file", "package.json", '{"name":"silent-add","private":true,"packageManager":"npm@11.13.0"}'], snapshot = false }, + { argv = ["vpt", "mkdir", "dep"], snapshot = false }, + { argv = ["vpt", "write-file", "dep/package.json", '{"name":"silent-add-dep","version":"1.0.0"}'], snapshot = false }, + { argv = ["vp", "add", "./dep", "--silent", "--save-catalog", "--lockfile-only"], comment = "Silent add suppresses compatibility diagnostics and leaves the cached update notice pending." }, + { argv = ["vpt", "stat-file", "package-lock.json", "--assert", "file"], snapshot = false }, { argv = ["vp", "env", "off"], comment = "The next interactive command displays the cached update notice." }, { argv = ["vp", "env", "off"], comment = "A subsequent command stays quiet after the notice timestamp is recorded." }, ] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_upgrade_check/snapshots/command_upgrade_background_notice.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_upgrade_check/snapshots/command_upgrade_background_notice.md index 96ff3840a1..64b3baf14f 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_upgrade_check/snapshots/command_upgrade_background_notice.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_upgrade_check/snapshots/command_upgrade_background_notice.md @@ -18,6 +18,25 @@ The foreground command launches the detached checker and returns without waiting Machine-readable output does not consume the pending notice. +## `vpt write-file package.json '{"name":"silent-add","private":true,"packageManager":"npm@11.13.0"}'` + + +## `vpt mkdir dep` + + +## `vpt write-file dep/package.json '{"name":"silent-add-dep","version":"1.0.0"}'` + + +## `vp add ./dep --silent --save-catalog --lockfile-only` + +Silent add suppresses compatibility diagnostics and leaves the cached update notice pending. + +``` +``` + +## `vpt stat-file package-lock.json --assert file` + + ## `vp env off` The next interactive command displays the cached update notice. diff --git a/crates/vp_pm_cli/src/cli.rs b/crates/vp_pm_cli/src/cli.rs index d6d2cb354b..3196434d32 100644 --- a/crates/vp_pm_cli/src/cli.rs +++ b/crates/vp_pm_cli/src/cli.rs @@ -280,6 +280,7 @@ impl PackageManagerCommand { pub fn is_quiet_or_machine_readable(&self) -> bool { match self { Self::Install(args) => args.silent, + Self::Add(args) => args.silent, Self::Dlx(args) => args.silent, Self::Outdated(args) => { matches!(args.format, Some(OutdatedFormat::Json | OutdatedFormat::List)) @@ -298,6 +299,7 @@ impl PackageManagerCommand { pub(crate) fn should_render_diagnostics(&self) -> bool { match self { Self::Install(args) => !args.silent, + Self::Add(args) => !args.silent, Self::Dlx(args) => !args.silent, _ => true, } @@ -664,6 +666,7 @@ mod tests { fn classifies_quiet_and_machine_readable_commands() { for args in [ &["install", "--silent"][..], + &["add", "react", "--silent"][..], &["dlx", "--silent", "tsx"][..], &["outdated", "--format", "json"][..], &["why", "react", "--parseable"][..], @@ -686,6 +689,7 @@ mod tests { assert!(!parse(args).unwrap().is_quiet_or_machine_readable(), "{args:?}"); } assert!(!parse(&["install"]).unwrap().is_quiet_or_machine_readable()); + assert!(!parse(&["add", "react"]).unwrap().is_quiet_or_machine_readable()); } #[test] @@ -757,6 +761,7 @@ mod tests { #[test] fn suppresses_diagnostics_only_for_explicit_silent_modes() { for args in [ + &["add", "react"][..], &["outdated", "--format", "json"][..], &["why", "react", "--parseable"][..], &["info", "react", "--json"][..], @@ -765,7 +770,11 @@ mod tests { assert!(parse(args).unwrap().should_render_diagnostics(), "{args:?}"); } - for args in [&["install", "--silent"][..], &["dlx", "--silent", "tsx"][..]] { + for args in [ + &["install", "--silent"][..], + &["add", "react", "--silent"][..], + &["dlx", "--silent", "tsx"][..], + ] { assert!(!parse(args).unwrap().should_render_diagnostics(), "{args:?}"); } } From 88bea0d16b68549511c0dfa333cd5195a283ed97 Mon Sep 17 00:00:00 2001 From: JongKyung Lee Date: Wed, 16 Sep 2026 16:03:31 +0900 Subject: [PATCH 5/9] test(pm): stabilize install option fixtures and refresh help Use project npm configuration for the offline probe and isolate Berry's cache. Give the updated local dependency a distinct manifest size so Yarn Classic does not skip copying files with identical size and mtime. Refresh the add help and affected snapshots. Leave cross-platform Yarn hash normalization for a separate change. --- .../cli_helper_message/snapshots/cli_helper_message.md | 10 ++++++++++ .../install_package_options/dep-v2/package.json | 3 ++- .../fixtures/install_package_options/snapshots.toml | 8 +++++--- .../install_package_options/snapshots/npm_offline.md | 10 ++++++++-- .../snapshots/yarn_berry_frozen_lockfile_modes.md | 10 ++++++---- 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/cli_helper_message/snapshots/cli_helper_message.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/cli_helper_message/snapshots/cli_helper_message.md index 0f320133e7..332f564d54 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/cli_helper_message/snapshots/cli_helper_message.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/cli_helper_message/snapshots/cli_helper_message.md @@ -160,6 +160,16 @@ Options: --save-catalog Save the new dependency to the default catalog --allow-build A list of package names allowed to run postinstall --ignore-scripts Do not run lifecycle scripts + --no-optional Do not install optionalDependencies + --frozen-lockfile Fail if lockfile needs to be updated + --no-frozen-lockfile Allow lockfile updates + --lockfile-only Only update lockfile, don't install + --prefer-offline Use cached packages when available + --offline Only use packages already in cache + -f, --force Force reinstall all dependencies + --no-lockfile Don't read or generate lockfile + --shamefully-hoist Create flat node_modules (pnpm only) + --silent Suppress package manager output --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/dep-v2/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/dep-v2/package.json index 295a4ceded..59b804c5da 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/dep-v2/package.json +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/dep-v2/package.json @@ -1,4 +1,5 @@ { "name": "install-option-dep", - "version": "2.0.0" + "version": "2.0.0", + "description": "Updated dependency fixture" } diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml index 9a259f0878..b6bcce133e 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml @@ -32,7 +32,8 @@ steps = [ name = "npm_offline" vp = "global" steps = [ - { argv = ["vp", "install", "vp-install-option-uncached-probe@1.0.0", "--offline", "--", "--registry=http://127.0.0.1:9", "--fetch-retries=0"], comment = "an uncached package fails in offline mode rather than attempting a connection", continue-on-failure = true }, + { argv = ["vpt", "write-file", ".npmrc", "registry=http://127.0.0.1:9\nfetch-retries=0\ncache=.npm-cache\n"], snapshot = false }, + { argv = ["vp", "install", "vp-install-option-uncached-probe@1.0.0", "--offline"], comment = "an uncached package fails in offline mode rather than attempting a connection", continue-on-failure = true }, { argv = ["vpt", "stat-file", "node_modules", "--assert", "missing"], continue-on-failure = true }, ["vpt", "stat-file", "package-lock.json", "--assert", "missing"], ] @@ -64,7 +65,7 @@ steps = [ [[case]] name = "yarn_berry_lockfile_only" vp = "global" -env = { YARN_ENABLE_TELEMETRY = "0" } +env = { YARN_ENABLE_TELEMETRY = "0", YARN_ENABLE_GLOBAL_CACHE = "0" } steps = [ { argv = ["vpt", "json-edit", "package.json", "packageManager", "yarn@4.10.3"], snapshot = false }, { argv = ["vp", "install", "./dep", "--lockfile-only", "--ignore-scripts"], comment = "use update-lockfile rather than skip-build when both options are supplied" }, @@ -76,6 +77,7 @@ steps = [ [[case]] name = "yarn_classic_frozen_lockfile_modes" vp = "global" +# dep-v2's manifest must differ in size because Classic skips copying files with equal size/mtime. steps = [ { argv = ["vpt", "json-edit", "package.json", "packageManager", "yarn@1.22.22"], snapshot = false }, { argv = ["vp", "install", "./dep", "--frozen-lockfile"], comment = "named-package installs warn and drop the unsupported add option" }, @@ -96,7 +98,7 @@ steps = [ [[case]] name = "yarn_berry_frozen_lockfile_modes" vp = "global" -env = { YARN_ENABLE_TELEMETRY = "0" } +env = { YARN_ENABLE_TELEMETRY = "0", YARN_ENABLE_GLOBAL_CACHE = "0" } steps = [ { argv = ["vpt", "json-edit", "package.json", "packageManager", "yarn@4.10.3"], snapshot = false }, { argv = ["vpt", "write-file", ".yarnrc.yml", "nodeLinker: node-modules\n"], snapshot = false }, diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/npm_offline.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/npm_offline.md index b5893e6aaf..93ebb9fbfa 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/npm_offline.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/npm_offline.md @@ -1,6 +1,12 @@ # npm_offline -## `vp install vp-install-option-uncached-probe@1.0.0 --offline -- --registry=http://127.0.0.1:9 --fetch-retries=0` +## `vpt write-file .npmrc 'registry=http://127.0.0.1:9 +fetch-retries=0 +cache=.npm-cache +'` + + +## `vp install vp-install-option-uncached-probe@1.0.0 --offline` an uncached package fails in offline mode rather than attempting a connection @@ -11,7 +17,7 @@ VITE+ - The Unified Toolchain for the Web npm error code ENOTCACHED npm error request to http://127.0.0.1:/vp-install-option-uncached-probe failed: cache mode is 'only-if-cached' but no cached response is available. -npm error A complete log of this run can be found in: /.npm/_logs/-debug-0.log +npm error A complete log of this run can be found in: /.npm-cache/_logs/-debug-0.log ``` ## `vpt stat-file node_modules --assert missing` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md index 972b1c3a2a..777d8637e9 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md @@ -44,7 +44,7 @@ VITE+ - The Unified Toolchain for the Web ➤ YN0000: · Yarn ➤ YN0000: ┌ Resolution step -➤ YN0085: │ + install-option-dep@file:./dep-v2#./dep-v2::hash=5590bc&locator=install-package-options%40workspace%3A. +➤ YN0085: │ + install-option-dep@file:./dep-v2#./dep-v2::hash=ca2da2&locator=install-package-options%40workspace%3A. ➤ YN0085: │ - install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A. ➤ YN0000: └ Completed @@ -60,7 +60,7 @@ VITE+ - The Unified Toolchain for the Web ➤ YN0028: │ - checksum: 10c0/32675cb2e55f886e9f975fdaefa9cb706071880078bda04af25dd0343941bf33780453c0ec848b30d3aa53cb9d5d5b3a6aeef902552100f255ed35fac8a8ff06 ➤ YN0028: │ +"install-option-dep@file:./dep-v2::locator=install-package-options%40workspace%3A.": ➤ YN0028: │ + version: 2.0.0 -➤ YN0028: │ + resolution: "install-option-dep@file:./dep-v2#./dep-v2::hash=5590bc&locator=install-package-options%40workspace%3A." +➤ YN0028: │ + resolution: "install-option-dep@file:./dep-v2#./dep-v2::hash=ca2da2&locator=install-package-options%40workspace%3A." ➤ YN0000: │ languageName: node ➤ YN0000: │ linkType: hard ➤ YN0000: │ @@ -93,10 +93,11 @@ VITE+ - The Unified Toolchain for the Web ➤ YN0000: · Yarn ➤ YN0000: ┌ Resolution step -➤ YN0085: │ + install-option-dep@file:./dep-v2#./dep-v2::hash=5590bc&locator=install-package-options%40workspace%3A. +➤ YN0085: │ + install-option-dep@file:./dep-v2#./dep-v2::hash=ca2da2&locator=install-package-options%40workspace%3A. ➤ YN0085: │ - install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A. ➤ YN0000: └ Completed ➤ YN0000: ┌ Fetch step +➤ YN0013: │ A package was added to the project, and one was removed (+ KiB). ➤ YN0000: └ Completed ➤ YN0000: ┌ Link step ➤ YN0000: └ Completed @@ -118,9 +119,10 @@ warn: yarn does not support --no-frozen-lockfile. ➤ YN0000: · Yarn ➤ YN0000: ┌ Resolution step ➤ YN0085: │ + install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A. -➤ YN0085: │ - install-option-dep@file:./dep-v2#./dep-v2::hash=5590bc&locator=install-package-options%40workspace%3A. +➤ YN0085: │ - install-option-dep@file:./dep-v2#./dep-v2::hash=ca2da2&locator=install-package-options%40workspace%3A. ➤ YN0000: └ Completed ➤ YN0000: ┌ Fetch step +➤ YN0013: │ A package was added to the project, and one was removed (- KiB). ➤ YN0000: └ Completed ➤ YN0000: ┌ Link step ➤ YN0000: └ Completed From 287616f1bbf6a8e84a40660b574c9d33a5bdedf2 Mon Sep 17 00:00:00 2001 From: JongKyung Lee Date: Wed, 16 Sep 2026 16:09:02 +0900 Subject: [PATCH 6/9] test(pm): defer Windows-specific fixture fixes Restore the npm offline probe and Berry cache settings, retaining only the help snapshot update and the Classic file-copy fixture correction. Regenerate snapshots without changing redaction. --- .../fixtures/install_package_options/snapshots.toml | 7 +++---- .../install_package_options/snapshots/npm_offline.md | 10 ++-------- .../snapshots/yarn_berry_frozen_lockfile_modes.md | 2 -- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml index b6bcce133e..535de02fb3 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml @@ -32,8 +32,7 @@ steps = [ name = "npm_offline" vp = "global" steps = [ - { argv = ["vpt", "write-file", ".npmrc", "registry=http://127.0.0.1:9\nfetch-retries=0\ncache=.npm-cache\n"], snapshot = false }, - { argv = ["vp", "install", "vp-install-option-uncached-probe@1.0.0", "--offline"], comment = "an uncached package fails in offline mode rather than attempting a connection", continue-on-failure = true }, + { argv = ["vp", "install", "vp-install-option-uncached-probe@1.0.0", "--offline", "--", "--registry=http://127.0.0.1:9", "--fetch-retries=0"], comment = "an uncached package fails in offline mode rather than attempting a connection", continue-on-failure = true }, { argv = ["vpt", "stat-file", "node_modules", "--assert", "missing"], continue-on-failure = true }, ["vpt", "stat-file", "package-lock.json", "--assert", "missing"], ] @@ -65,7 +64,7 @@ steps = [ [[case]] name = "yarn_berry_lockfile_only" vp = "global" -env = { YARN_ENABLE_TELEMETRY = "0", YARN_ENABLE_GLOBAL_CACHE = "0" } +env = { YARN_ENABLE_TELEMETRY = "0" } steps = [ { argv = ["vpt", "json-edit", "package.json", "packageManager", "yarn@4.10.3"], snapshot = false }, { argv = ["vp", "install", "./dep", "--lockfile-only", "--ignore-scripts"], comment = "use update-lockfile rather than skip-build when both options are supplied" }, @@ -98,7 +97,7 @@ steps = [ [[case]] name = "yarn_berry_frozen_lockfile_modes" vp = "global" -env = { YARN_ENABLE_TELEMETRY = "0", YARN_ENABLE_GLOBAL_CACHE = "0" } +env = { YARN_ENABLE_TELEMETRY = "0" } steps = [ { argv = ["vpt", "json-edit", "package.json", "packageManager", "yarn@4.10.3"], snapshot = false }, { argv = ["vpt", "write-file", ".yarnrc.yml", "nodeLinker: node-modules\n"], snapshot = false }, diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/npm_offline.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/npm_offline.md index 93ebb9fbfa..b5893e6aaf 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/npm_offline.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/npm_offline.md @@ -1,12 +1,6 @@ # npm_offline -## `vpt write-file .npmrc 'registry=http://127.0.0.1:9 -fetch-retries=0 -cache=.npm-cache -'` - - -## `vp install vp-install-option-uncached-probe@1.0.0 --offline` +## `vp install vp-install-option-uncached-probe@1.0.0 --offline -- --registry=http://127.0.0.1:9 --fetch-retries=0` an uncached package fails in offline mode rather than attempting a connection @@ -17,7 +11,7 @@ VITE+ - The Unified Toolchain for the Web npm error code ENOTCACHED npm error request to http://127.0.0.1:/vp-install-option-uncached-probe failed: cache mode is 'only-if-cached' but no cached response is available. -npm error A complete log of this run can be found in: /.npm-cache/_logs/-debug-0.log +npm error A complete log of this run can be found in: /.npm/_logs/-debug-0.log ``` ## `vpt stat-file node_modules --assert missing` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md index 777d8637e9..600f5156a5 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md @@ -97,7 +97,6 @@ VITE+ - The Unified Toolchain for the Web ➤ YN0085: │ - install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A. ➤ YN0000: └ Completed ➤ YN0000: ┌ Fetch step -➤ YN0013: │ A package was added to the project, and one was removed (+ KiB). ➤ YN0000: └ Completed ➤ YN0000: ┌ Link step ➤ YN0000: └ Completed @@ -122,7 +121,6 @@ warn: yarn does not support --no-frozen-lockfile. ➤ YN0085: │ - install-option-dep@file:./dep-v2#./dep-v2::hash=ca2da2&locator=install-package-options%40workspace%3A. ➤ YN0000: └ Completed ➤ YN0000: ┌ Fetch step -➤ YN0013: │ A package was added to the project, and one was removed (- KiB). ➤ YN0000: └ Completed ➤ YN0000: ┌ Link step ➤ YN0000: └ Completed From 16a0cba033010f4dccc12fa6fe46001811fc9531 Mon Sep 17 00:00:00 2001 From: JongKyung Lee Date: Wed, 16 Sep 2026 16:31:34 +0900 Subject: [PATCH 7/9] test(pm): normalize platform-dependent Yarn snapshot hashes Mask file locator hashes and checksums in Yarn immutable lockfile diffs while preserving package versions and diagnostics. Add redaction coverage and regenerate the affected snapshots. --- .../yarn_berry_frozen_lockfile_modes.md | 20 +++++++++---------- .../snapshots/yarn_berry_lockfile_only.md | 2 +- .../tests/cli_snapshots/redact.rs | 8 ++++++++ crates/vp_cli_snapshots/tests/redact_unit.rs | 17 ++++++++++++++++ 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md index 600f5156a5..212f7019ee 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_frozen_lockfile_modes.md @@ -17,7 +17,7 @@ VITE+ - The Unified Toolchain for the Web warn: yarn does not support --frozen-lockfile. ➤ YN0000: · Yarn ➤ YN0000: ┌ Resolution step -➤ YN0085: │ + install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A. +➤ YN0085: │ + install-option-dep@file:./dep#./dep::hash=&locator=install-package-options%40workspace%3A. ➤ YN0000: └ Completed ➤ YN0000: ┌ Fetch step ➤ YN0013: │ A package was added to the project (+ KiB). @@ -44,8 +44,8 @@ VITE+ - The Unified Toolchain for the Web ➤ YN0000: · Yarn ➤ YN0000: ┌ Resolution step -➤ YN0085: │ + install-option-dep@file:./dep-v2#./dep-v2::hash=ca2da2&locator=install-package-options%40workspace%3A. -➤ YN0085: │ - install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A. +➤ YN0085: │ + install-option-dep@file:./dep-v2#./dep-v2::hash=&locator=install-package-options%40workspace%3A. +➤ YN0085: │ - install-option-dep@file:./dep#./dep::hash=&locator=install-package-options%40workspace%3A. ➤ YN0000: └ Completed ➤ YN0000: ┌ Post-resolution validation @@ -56,11 +56,11 @@ VITE+ - The Unified Toolchain for the Web ➤ YN0000: │ ➤ YN0028: │ -"install-option-dep@file:./dep::locator=install-package-options%40workspace%3A.": ➤ YN0028: │ - version: 1.0.0 -➤ YN0028: │ - resolution: "install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A." -➤ YN0028: │ - checksum: 10c0/32675cb2e55f886e9f975fdaefa9cb706071880078bda04af25dd0343941bf33780453c0ec848b30d3aa53cb9d5d5b3a6aeef902552100f255ed35fac8a8ff06 +➤ YN0028: │ - resolution: "install-option-dep@file:./dep#./dep::hash=&locator=install-package-options%40workspace%3A." +➤ YN0028: │ - checksum: ➤ YN0028: │ +"install-option-dep@file:./dep-v2::locator=install-package-options%40workspace%3A.": ➤ YN0028: │ + version: 2.0.0 -➤ YN0028: │ + resolution: "install-option-dep@file:./dep-v2#./dep-v2::hash=ca2da2&locator=install-package-options%40workspace%3A." +➤ YN0028: │ + resolution: "install-option-dep@file:./dep-v2#./dep-v2::hash=&locator=install-package-options%40workspace%3A." ➤ YN0000: │ languageName: node ➤ YN0000: │ linkType: hard ➤ YN0000: │ @@ -93,8 +93,8 @@ VITE+ - The Unified Toolchain for the Web ➤ YN0000: · Yarn ➤ YN0000: ┌ Resolution step -➤ YN0085: │ + install-option-dep@file:./dep-v2#./dep-v2::hash=ca2da2&locator=install-package-options%40workspace%3A. -➤ YN0085: │ - install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A. +➤ YN0085: │ + install-option-dep@file:./dep-v2#./dep-v2::hash=&locator=install-package-options%40workspace%3A. +➤ YN0085: │ - install-option-dep@file:./dep#./dep::hash=&locator=install-package-options%40workspace%3A. ➤ YN0000: └ Completed ➤ YN0000: ┌ Fetch step ➤ YN0000: └ Completed @@ -117,8 +117,8 @@ direct add also warns for the unsupported immutable override warn: yarn does not support --no-frozen-lockfile. ➤ YN0000: · Yarn ➤ YN0000: ┌ Resolution step -➤ YN0085: │ + install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A. -➤ YN0085: │ - install-option-dep@file:./dep-v2#./dep-v2::hash=ca2da2&locator=install-package-options%40workspace%3A. +➤ YN0085: │ + install-option-dep@file:./dep#./dep::hash=&locator=install-package-options%40workspace%3A. +➤ YN0085: │ - install-option-dep@file:./dep-v2#./dep-v2::hash=&locator=install-package-options%40workspace%3A. ➤ YN0000: └ Completed ➤ YN0000: ┌ Fetch step ➤ YN0000: └ Completed diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_lockfile_only.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_lockfile_only.md index 4d6ee082f1..36fec4e261 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_lockfile_only.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots/yarn_berry_lockfile_only.md @@ -13,7 +13,7 @@ VITE+ - The Unified Toolchain for the Web warn: yarn@2+ --mode can only be specified once; --lockfile-only takes priority over --ignore-scripts ➤ YN0000: · Yarn ➤ YN0000: ┌ Resolution step -➤ YN0085: │ + install-option-dep@file:./dep#./dep::hash=8572a9&locator=install-package-options%40workspace%3A. +➤ YN0085: │ + install-option-dep@file:./dep#./dep::hash=&locator=install-package-options%40workspace%3A. ➤ YN0000: └ Completed ➤ YN0000: ┌ Fetch step ➤ YN0013: │ A package was added to the project (+ KiB). diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/redact.rs b/crates/vp_cli_snapshots/tests/cli_snapshots/redact.rs index fcce014b60..750847ebdb 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/redact.rs +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/redact.rs @@ -269,6 +269,12 @@ static YARN1_STEP_EMOJI_RE: LazyLock = LazyLock::new(|| { // depends on what ran earlier in the environment; strip it entirely. static YARN_TELEMETRY_RE: LazyLock = LazyLock::new(|| regex::Regex::new(r"(?m)^\u{27A4} YN0065: [^\n]*\n(?:[ \t]*\n)*").unwrap()); +// Yarn's file archive hashes and lockfile checksums differ across platforms. +static YARN_FILE_HASH_RE: LazyLock = + LazyLock::new(|| regex::Regex::new(r"(::hash=)[0-9a-f]+(&locator=)").unwrap()); +static YARN_LOCKFILE_CHECKSUM_RE: LazyLock = LazyLock::new(|| { + regex::Regex::new(r"(?m)^(\x{27A4} YN0028: [^\n]*checksum: )[0-9a-f]+/[0-9a-f]+").unwrap() +}); // `vp staged` reports the backup stash it created; the short hash covers a // commit of the working tree at run time, so it can never be stable. static STASH_HASH_RE: LazyLock = @@ -583,6 +589,8 @@ pub fn redact_output( // notice, and the stash hash `vp staged` reports for its backup output = YARN1_STEP_EMOJI_RE.replace_all(&output, "${1} ").into_owned(); output = YARN_TELEMETRY_RE.replace_all(&output, "").into_owned(); + output = YARN_FILE_HASH_RE.replace_all(&output, "${1}${2}").into_owned(); + output = YARN_LOCKFILE_CHECKSUM_RE.replace_all(&output, "${1}").into_owned(); output = STASH_HASH_RE.replace_all(&output, "${1}${2}").into_owned(); // Mask the local-registry proxy's ephemeral port, npm's timestamped debug diff --git a/crates/vp_cli_snapshots/tests/redact_unit.rs b/crates/vp_cli_snapshots/tests/redact_unit.rs index 7a20411af9..7be23948d3 100644 --- a/crates/vp_cli_snapshots/tests/redact_unit.rs +++ b/crates/vp_cli_snapshots/tests/redact_unit.rs @@ -47,6 +47,23 @@ fn masks_size_numbers_keeping_units_and_spares_plain_stems() { ); } +#[test] +fn masks_yarn_file_hashes_and_lockfile_diff_checksums() { + let input = concat!( + "➤ YN0085: │ + dep@file:./dep#./dep::hash=8572a9&locator=app%40workspace%3A.\n", + "➤ YN0028: │ - checksum: 10c0/deadbeef\n", + "➤ YN0028: │ + version: 2.0.0\n", + "checksum: 10c0/deadbeef\n", + ); + let expected = concat!( + "➤ YN0085: │ + dep@file:./dep#./dep::hash=&locator=app%40workspace%3A.\n", + "➤ YN0028: │ - checksum: \n", + "➤ YN0028: │ + version: 2.0.0\n", + "checksum: 10c0/deadbeef\n", + ); + assert_eq!(redact_output(input.to_owned(), &[], true), expected); +} + #[test] fn drops_the_vite_build_banner_line() { // The banner races the Rust reporter's same-line erase writes, so its From 9ba2c96b3c2ea986fc48b579586a82a0b698dfa0 Mon Sep 17 00:00:00 2001 From: JongKyung Lee Date: Wed, 16 Sep 2026 16:51:12 +0900 Subject: [PATCH 8/9] test(pm): isolate Yarn lockfile-only snapshot cache Disable the global cache for yarn_berry_lockfile_only so its fetch summary does not depend on packages cached by other cases on Windows. --- .../fixtures/install_package_options/snapshots.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml index 535de02fb3..5cfad4d0c5 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_package_options/snapshots.toml @@ -64,7 +64,7 @@ steps = [ [[case]] name = "yarn_berry_lockfile_only" vp = "global" -env = { YARN_ENABLE_TELEMETRY = "0" } +env = { YARN_ENABLE_TELEMETRY = "0", YARN_ENABLE_GLOBAL_CACHE = "0" } steps = [ { argv = ["vpt", "json-edit", "package.json", "packageManager", "yarn@4.10.3"], snapshot = false }, { argv = ["vp", "install", "./dep", "--lockfile-only", "--ignore-scripts"], comment = "use update-lockfile rather than skip-build when both options are supplied" }, From 0b3bad24b6f3e97e6865bce4d64c6a4df8c58571 Mon Sep 17 00:00:00 2001 From: JongKyung Lee Date: Wed, 16 Sep 2026 17:34:03 +0900 Subject: [PATCH 9/9] docs(pm): clarify add silent mode in help Explain that silent mode suppresses Vite+ output and enables native silent mode where supported. Refresh the global and local help snapshots without changing runtime behavior. --- .../fixtures/cli_helper_message/snapshots/cli_helper_message.md | 2 +- .../fixtures/command_add_bun/snapshots/command_add_bun.md | 2 +- .../fixtures/command_add_npm10/snapshots/command_add_npm10.md | 2 +- .../fixtures/command_add_npm11/snapshots/command_add_npm11.md | 2 +- .../command_add_pnpm10/snapshots/command_add_pnpm10.global.md | 2 +- .../command_add_pnpm10/snapshots/command_add_pnpm10.local.md | 2 +- .../fixtures/command_add_pnpm11/snapshots/command_add_pnpm11.md | 2 +- .../fixtures/command_add_pnpm12/snapshots/command_add_pnpm12.md | 2 +- .../fixtures/command_add_pnpm9/snapshots/command_add_pnpm9.md | 2 +- .../fixtures/command_add_yarn4/snapshots/command_add_yarn4.md | 2 +- crates/vp_pm_cli/src/resolution/commands/add.rs | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/cli_helper_message/snapshots/cli_helper_message.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/cli_helper_message/snapshots/cli_helper_message.md index 332f564d54..aa0b6e0fe5 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/cli_helper_message/snapshots/cli_helper_message.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/cli_helper_message/snapshots/cli_helper_message.md @@ -169,7 +169,7 @@ Options: -f, --force Force reinstall all dependencies --no-lockfile Don't read or generate lockfile --shamefully-hoist Create flat node_modules (pnpm only) - --silent Suppress package manager output + --silent Suppress Vite+ output and enable native silent mode where supported --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_bun/snapshots/command_add_bun.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_bun/snapshots/command_add_bun.md index 8e910cb567..65acc370c9 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_bun/snapshots/command_add_bun.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_bun/snapshots/command_add_bun.md @@ -34,7 +34,7 @@ Options: -f, --force Force reinstall all dependencies --no-lockfile Don't read or generate lockfile --shamefully-hoist Create flat node_modules (pnpm only) - --silent Suppress package manager output + --silent Suppress Vite+ output and enable native silent mode where supported --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm10/snapshots/command_add_npm10.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm10/snapshots/command_add_npm10.md index 786bd04ea4..fa89053e35 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm10/snapshots/command_add_npm10.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm10/snapshots/command_add_npm10.md @@ -34,7 +34,7 @@ Options: -f, --force Force reinstall all dependencies --no-lockfile Don't read or generate lockfile --shamefully-hoist Create flat node_modules (pnpm only) - --silent Suppress package manager output + --silent Suppress Vite+ output and enable native silent mode where supported --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm11/snapshots/command_add_npm11.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm11/snapshots/command_add_npm11.md index 0ff40ccc97..6994f3ef11 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm11/snapshots/command_add_npm11.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm11/snapshots/command_add_npm11.md @@ -34,7 +34,7 @@ Options: -f, --force Force reinstall all dependencies --no-lockfile Don't read or generate lockfile --shamefully-hoist Create flat node_modules (pnpm only) - --silent Suppress package manager output + --silent Suppress Vite+ output and enable native silent mode where supported --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.global.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.global.md index f78ba8c3ae..1d8c2acc5d 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.global.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.global.md @@ -34,7 +34,7 @@ Options: -f, --force Force reinstall all dependencies --no-lockfile Don't read or generate lockfile --shamefully-hoist Create flat node_modules (pnpm only) - --silent Suppress package manager output + --silent Suppress Vite+ output and enable native silent mode where supported --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.local.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.local.md index 41f5440d40..2c76ae81bf 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.local.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.local.md @@ -51,7 +51,7 @@ Options: --shamefully-hoist Create flat node_modules (pnpm only) --silent - Suppress package manager output + Suppress Vite+ output and enable native silent mode where supported --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm11/snapshots/command_add_pnpm11.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm11/snapshots/command_add_pnpm11.md index 98775e73f1..f605e1480e 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm11/snapshots/command_add_pnpm11.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm11/snapshots/command_add_pnpm11.md @@ -34,7 +34,7 @@ Options: -f, --force Force reinstall all dependencies --no-lockfile Don't read or generate lockfile --shamefully-hoist Create flat node_modules (pnpm only) - --silent Suppress package manager output + --silent Suppress Vite+ output and enable native silent mode where supported --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm12/snapshots/command_add_pnpm12.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm12/snapshots/command_add_pnpm12.md index 0f2fb08902..c1c4688e28 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm12/snapshots/command_add_pnpm12.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm12/snapshots/command_add_pnpm12.md @@ -34,7 +34,7 @@ Options: -f, --force Force reinstall all dependencies --no-lockfile Don't read or generate lockfile --shamefully-hoist Create flat node_modules (pnpm only) - --silent Suppress package manager output + --silent Suppress Vite+ output and enable native silent mode where supported --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm9/snapshots/command_add_pnpm9.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm9/snapshots/command_add_pnpm9.md index cf7206a661..597153f2f6 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm9/snapshots/command_add_pnpm9.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm9/snapshots/command_add_pnpm9.md @@ -34,7 +34,7 @@ Options: -f, --force Force reinstall all dependencies --no-lockfile Don't read or generate lockfile --shamefully-hoist Create flat node_modules (pnpm only) - --silent Suppress package manager output + --silent Suppress Vite+ output and enable native silent mode where supported --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_yarn4/snapshots/command_add_yarn4.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_yarn4/snapshots/command_add_yarn4.md index f2cef46f82..a0f813bc34 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_yarn4/snapshots/command_add_yarn4.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_yarn4/snapshots/command_add_yarn4.md @@ -34,7 +34,7 @@ Options: -f, --force Force reinstall all dependencies --no-lockfile Don't read or generate lockfile --shamefully-hoist Create flat node_modules (pnpm only) - --silent Suppress package manager output + --silent Suppress Vite+ output and enable native silent mode where supported --filter Filter packages in monorepo (can be used multiple times) -w, --workspace-root Add to workspace root --workspace Only add if package exists in workspace (pnpm-specific) diff --git a/crates/vp_pm_cli/src/resolution/commands/add.rs b/crates/vp_pm_cli/src/resolution/commands/add.rs index eb00a11789..49ce1cc68c 100644 --- a/crates/vp_pm_cli/src/resolution/commands/add.rs +++ b/crates/vp_pm_cli/src/resolution/commands/add.rs @@ -77,7 +77,7 @@ pub struct AddArgs { #[arg(long, conflicts_with = "global", not_supported(npm, yarn, bun))] pub(crate) shamefully_hoist: bool, - /// Suppress package manager output + /// Suppress Vite+ output and enable native silent mode where supported #[arg(long, conflicts_with = "global", not_supported(yarn >= "2"))] pub(crate) silent: bool,