diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_global_option_conflicts/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_global_option_conflicts/package.json new file mode 100644 index 0000000000..b2d3bad364 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_global_option_conflicts/package.json @@ -0,0 +1,4 @@ +{ + "name": "global-option-conflicts", + "private": true +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_global_option_conflicts/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_global_option_conflicts/snapshots.toml new file mode 100644 index 0000000000..9a43fd2090 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_global_option_conflicts/snapshots.toml @@ -0,0 +1,12 @@ +[[case]] +name = "managed_global_rejects_project_options" +vp = "global" +comment = "Project-only flags are rejected before managed global operations can install, remove, or update packages." +steps = [ + { argv = ["vp", "install", "-g", "-D", "--frozen-lockfile", "--filter", "app", "typescript"], continue-on-failure = true }, + { argv = ["vp", "add", "-g", "--allow-build", "typescript", "typescript"], continue-on-failure = true }, + { argv = ["vp", "remove", "-g", "--filter", "app", "typescript"], continue-on-failure = true }, + { argv = ["vp", "update", "-g", "--interactive", "typescript"], continue-on-failure = true }, + { argv = ["vp", "outdated", "-g", "--compatible"], continue-on-failure = true }, + { argv = ["vp", "pm", "list", "-g", "--depth", "0"], continue-on-failure = true }, +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_global_option_conflicts/snapshots/managed_global_rejects_project_options.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_global_option_conflicts/snapshots/managed_global_rejects_project_options.md new file mode 100644 index 0000000000..0a2698bd0b --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_global_option_conflicts/snapshots/managed_global_rejects_project_options.md @@ -0,0 +1,78 @@ +# managed_global_rejects_project_options + +Project-only flags are rejected before managed global operations can install, remove, or update packages. + +## `vp install -g -D --frozen-lockfile --filter app typescript` + +**Exit code:** 2 + +``` +error: the argument '--global' cannot be used with: + --dev + --frozen-lockfile + --filter + +Usage: vp install --global ... [-- ...] + +For more information, try '--help'. +``` + +## `vp add -g --allow-build typescript typescript` + +**Exit code:** 2 + +``` +error: the argument '--global' cannot be used with '--allow-build ' + +Usage: vp add --global ... [-- ...] + +For more information, try '--help'. +``` + +## `vp remove -g --filter app typescript` + +**Exit code:** 2 + +``` +error: the argument '--global' cannot be used with '--filter ' + +Usage: vp remove --global ... [-- ...] + +For more information, try '--help'. +``` + +## `vp update -g --interactive typescript` + +**Exit code:** 2 + +``` +error: the argument '--global' cannot be used with '--interactive' + +Usage: vp update --global ... [-- ...] + +For more information, try '--help'. +``` + +## `vp outdated -g --compatible` + +**Exit code:** 2 + +``` +error: the argument '--global' cannot be used with '--compatible' + +Usage: vp outdated --global [PACKAGES]... [-- ...] + +For more information, try '--help'. +``` + +## `vp pm list -g --depth 0` + +**Exit code:** 2 + +``` +error: the argument '--global' cannot be used with '--depth ' + +Usage: vp pm list --global [PATTERN] [-- ...] + +For more information, try '--help'. +``` diff --git a/crates/vp_pm_cli/src/cli.rs b/crates/vp_pm_cli/src/cli.rs index 9cec64c9b9..56cd534f42 100644 --- a/crates/vp_pm_cli/src/cli.rs +++ b/crates/vp_pm_cli/src/cli.rs @@ -783,11 +783,134 @@ mod tests { assert!(add.is_managed_global()); } + #[test] + fn managed_global_rejects_project_only_options() { + let cases: &[(&[&str], &[&[&str]])] = &[ + ( + &["install"], + &[ + &["--prod"], + &["--dev"], + &["--no-optional"], + &["--frozen-lockfile"], + &["--no-frozen-lockfile"], + &["--lockfile-only"], + &["--prefer-offline"], + &["--offline"], + &["--no-lockfile"], + &["--fix-lockfile"], + &["--shamefully-hoist"], + &["--resolution-only"], + &["--filter", "app"], + &["--workspace-root"], + &["--save-exact"], + &["--save-peer"], + &["--save-optional"], + &["--save-catalog"], + ], + ), + ( + &["add"], + &[ + &["--save-prod"], + &["--save-dev"], + &["--save-peer"], + &["--save-optional"], + &["--save-exact"], + &["--save-catalog-name", "default"], + &["--save-catalog"], + &["--allow-build", "react"], + &["--filter", "app"], + &["--workspace-root"], + &["--workspace"], + ], + ), + ( + &["remove"], + &[ + &["--save-dev"], + &["--save-optional"], + &["--save-prod"], + &["--filter", "app"], + &["--workspace-root"], + &["--recursive"], + ], + ), + ( + &["update"], + &[ + &["--recursive"], + &["--filter", "app"], + &["--workspace-root"], + &["--dev"], + &["--prod"], + &["--interactive"], + &["--no-optional"], + &["--no-save"], + &["--workspace"], + ], + ), + ( + &["outdated"], + &[ + &["--recursive"], + &["--filter", "app"], + &["--workspace-root"], + &["--prod"], + &["--dev"], + &["--no-optional"], + &["--compatible"], + &["--sort-by", "name"], + ], + ), + ( + &["pm", "list"], + &[ + &["--depth", "0"], + &["--long"], + &["--parseable"], + &["--prod"], + &["--dev"], + &["--no-optional"], + &["--exclude-peers"], + &["--only-projects"], + &["--find-by", "finder"], + &["--recursive"], + &["--filter", "app"], + ], + ), + ]; + for &(command, options) in cases { + for option in options { + let mut argv = command.to_vec(); + argv.extend_from_slice(option); + argv.push("react"); + assert!(parse(&argv).is_ok(), "{argv:?}"); + for global in ["-g", "--global"] { + let mut argv = argv.clone(); + argv.push(global); + let error = parse(&argv).unwrap_err(); + assert_eq!(error.kind(), clap::error::ErrorKind::ArgumentConflict, "{argv:?}"); + assert!(error.to_string().contains("--global"), "{error}"); + } + } + } + } + #[test] fn projects_managed_global_commands() { - let install = - parse(&["install", "-g", "--node", "22", "--force", "--concurrency", "2", "tsx"]) - .unwrap(); + let install = parse(&[ + "install", + "-g", + "--node", + "22", + "--force", + "--silent", + "--concurrency", + "2", + "tsx", + ]) + .unwrap(); let Some(ManagedGlobalCommand::Install { packages, node, force, concurrency, .. }) = install.managed_global_command() else { diff --git a/crates/vp_pm_cli/src/resolution/commands/add.rs b/crates/vp_pm_cli/src/resolution/commands/add.rs index 9f48c74e15..4ec0915d29 100644 --- a/crates/vp_pm_cli/src/resolution/commands/add.rs +++ b/crates/vp_pm_cli/src/resolution/commands/add.rs @@ -12,19 +12,19 @@ pub struct AddArgs { pub(crate) save_dependency: SaveDependencyArgs, /// Save exact version rather than semver range - #[arg(short = 'E', long)] + #[arg(short = 'E', long, conflicts_with = "global")] pub(crate) save_exact: bool, /// Save the new dependency to the specified catalog name - #[arg(long, value_name = "CATALOG_NAME", not_supported(npm, pnpm < "10.12.0", yarn, bun < "1.4"))] + #[arg(long, value_name = "CATALOG_NAME", conflicts_with = "global", not_supported(npm, pnpm < "10.12.0", yarn, bun < "1.4"))] pub(crate) save_catalog_name: Option, /// Save the new dependency to the default catalog - #[arg(long, not_supported(npm, pnpm < "10.12.0", yarn, bun < "1.4"))] + #[arg(long, conflicts_with = "global", not_supported(npm, pnpm < "10.12.0", yarn, bun < "1.4"))] pub(crate) save_catalog: bool, /// A list of package names allowed to run postinstall - #[arg(long, value_name = "NAMES", not_supported(npm, pnpm < "10.4.0", yarn, bun))] + #[arg(long, value_name = "NAMES", conflicts_with = "global", not_supported(npm, pnpm < "10.4.0", yarn, bun))] pub(crate) allow_build: Option, /// Do not run lifecycle scripts @@ -82,15 +82,15 @@ pub struct AddArgs { pub(crate) silent: bool, /// Filter packages in monorepo (can be used multiple times) - #[arg(long, value_name = "PATTERN", not_supported(npm < "7.14.0", yarn < "2", bun < "1.4"))] + #[arg(long, value_name = "PATTERN", conflicts_with = "global", not_supported(npm < "7.14.0", yarn < "2", bun < "1.4"))] pub(crate) filter: Vec, /// Add to workspace root - #[arg(short = 'w', long, not_supported(npm < "8.1.0", yarn >= "2", bun))] + #[arg(short = 'w', long, conflicts_with = "global", not_supported(npm < "8.1.0", yarn >= "2", bun))] pub(crate) workspace_root: bool, /// Only add if package exists in workspace (pnpm-specific) - #[arg(long, not_supported(npm, yarn, bun))] + #[arg(long, conflicts_with = "global", not_supported(npm, yarn, bun))] pub(crate) workspace: bool, /// Install globally @@ -126,19 +126,19 @@ pub(crate) enum SaveDependencyTarget { #[group(id = "save_dependency_target", multiple = false)] pub(crate) struct SaveDependencyArgs { /// Save to `dependencies` (default) - #[arg(short = 'P', long)] + #[arg(short = 'P', long, conflicts_with = "global")] pub(crate) save_prod: bool, /// Save to `devDependencies` - #[arg(short = 'D', long)] + #[arg(short = 'D', long, conflicts_with = "global")] pub(crate) save_dev: bool, /// Save to `peerDependencies` and `devDependencies` - #[arg(long)] + #[arg(long, conflicts_with = "global")] pub(crate) save_peer: bool, /// Save to `optionalDependencies` - #[arg(short = 'O', long)] + #[arg(short = 'O', long, conflicts_with = "global")] pub(crate) save_optional: bool, } diff --git a/crates/vp_pm_cli/src/resolution/commands/install.rs b/crates/vp_pm_cli/src/resolution/commands/install.rs index 78789b9c87..ca15df6a1c 100644 --- a/crates/vp_pm_cli/src/resolution/commands/install.rs +++ b/crates/vp_pm_cli/src/resolution/commands/install.rs @@ -14,37 +14,37 @@ use crate::{ #[derive(clap::Args, Clone, Debug, Default, PartialEq, Eq)] pub struct InstallArgs { /// Do not install devDependencies - #[arg(short = 'P', long)] + #[arg(short = 'P', long, conflicts_with = "global")] pub(crate) prod: bool, /// Install devDependencies (install) / Save to devDependencies (add) - #[arg(short = 'D', long, not_supported(yarn >= "2"))] + #[arg(short = 'D', long, conflicts_with = "global", not_supported(yarn >= "2"))] pub(crate) dev: bool, /// Do not install optionalDependencies - #[arg(long, not_supported(yarn >= "2"))] + #[arg(long, conflicts_with = "global", not_supported(yarn >= "2"))] pub(crate) no_optional: bool, /// Fail if lockfile needs to be updated (CI mode) - #[arg(long, overrides_with = "no_frozen_lockfile")] + #[arg(long, conflicts_with = "global", overrides_with = "no_frozen_lockfile")] pub(crate) frozen_lockfile: bool, /// Allow lockfile updates (opposite of --frozen-lockfile) - #[arg(long, overrides_with = "frozen_lockfile")] + #[arg(long, conflicts_with = "global", overrides_with = "frozen_lockfile")] pub(crate) no_frozen_lockfile: bool, /// Only update lockfile, don't install // `yarn install --mode update-lockfile` landed in Yarn 3.0.0. // https://github.com/yarnpkg/berry/blob/master/CHANGELOG.md#300 - #[arg(long, not_supported(yarn < "3"))] + #[arg(long, conflicts_with = "global", not_supported(yarn < "3"))] pub(crate) lockfile_only: bool, /// Use cached packages when available - #[arg(long, not_supported(yarn >= "2", bun < "1.4.1"))] + #[arg(long, conflicts_with = "global", not_supported(yarn >= "2", bun < "1.4.1"))] pub(crate) prefer_offline: bool, /// Only use packages already in cache - #[arg(long, not_supported(yarn >= "2", bun < "1.4.1"))] + #[arg(long, conflicts_with = "global", not_supported(yarn >= "2", bun < "1.4.1"))] pub(crate) offline: bool, /// Force reinstall all dependencies @@ -56,19 +56,19 @@ pub struct InstallArgs { pub(crate) ignore_scripts: bool, /// Don't read or generate lockfile - #[arg(long, not_supported(yarn >= "2", bun))] + #[arg(long, conflicts_with = "global", not_supported(yarn >= "2", bun))] pub(crate) no_lockfile: bool, /// Fix broken lockfile entries (pnpm and yarn@4+ only) - #[arg(long, not_supported(npm, bun, yarn < "4"))] + #[arg(long, conflicts_with = "global", not_supported(npm, bun, yarn < "4"))] pub(crate) fix_lockfile: bool, /// Create flat `node_modules` (pnpm only) - #[arg(long, not_supported(npm, bun, yarn))] + #[arg(long, conflicts_with = "global", not_supported(npm, bun, yarn))] pub(crate) shamefully_hoist: bool, /// Re-run resolution for peer dependency analysis (pnpm only) - #[arg(long, not_supported(npm, bun, yarn))] + #[arg(long, conflicts_with = "global", not_supported(npm, bun, yarn))] pub(crate) resolution_only: bool, /// Suppress output (silent mode) @@ -76,27 +76,27 @@ pub struct InstallArgs { pub(crate) silent: bool, /// Filter packages in monorepo (can be used multiple times) - #[arg(long, value_name = "PATTERN", not_supported(npm < "7.14.0", yarn < "2"))] + #[arg(long, value_name = "PATTERN", conflicts_with = "global", not_supported(npm < "7.14.0", yarn < "2"))] pub(crate) filter: Vec, /// Install in workspace root only - #[arg(short = 'w', long, not_supported(npm < "8.1.0", yarn, bun))] + #[arg(short = 'w', long, conflicts_with = "global", not_supported(npm < "8.1.0", yarn, bun))] pub(crate) workspace_root: bool, /// Save exact version (only when adding packages) - #[arg(short = 'E', long)] + #[arg(short = 'E', long, conflicts_with = "global")] pub(crate) save_exact: bool, /// Save to peerDependencies (only when adding packages) - #[arg(long)] + #[arg(long, conflicts_with = "global")] pub(crate) save_peer: bool, /// Save to optionalDependencies (only when adding packages) - #[arg(short = 'O', long)] + #[arg(short = 'O', long, conflicts_with = "global")] pub(crate) save_optional: bool, /// Save the new dependency to the default catalog (only when adding packages) - #[arg(long, not_supported(npm, yarn, bun))] + #[arg(long, conflicts_with = "global", not_supported(npm, yarn, bun))] pub(crate) save_catalog: bool, /// Install globally (requires package names) diff --git a/crates/vp_pm_cli/src/resolution/commands/list.rs b/crates/vp_pm_cli/src/resolution/commands/list.rs index 3a82c98013..b10921a1f6 100644 --- a/crates/vp_pm_cli/src/resolution/commands/list.rs +++ b/crates/vp_pm_cli/src/resolution/commands/list.rs @@ -11,7 +11,7 @@ pub struct ListArgs { pub(crate) pattern: Option, /// Maximum depth of dependency tree - #[arg(long, not_supported(yarn >= "2", bun))] + #[arg(long, conflicts_with = "global", not_supported(yarn >= "2", bun))] pub(crate) depth: Option, /// Output in JSON format @@ -19,43 +19,43 @@ pub struct ListArgs { pub(crate) json: bool, /// Show extended information - #[arg(long, not_supported(yarn, bun))] + #[arg(long, conflicts_with = "global", not_supported(yarn, bun))] pub(crate) long: bool, /// Parseable output format - #[arg(long, not_supported(yarn, bun))] + #[arg(long, conflicts_with = "global", not_supported(yarn, bun))] pub(crate) parseable: bool, /// Only production dependencies - #[arg(short = 'P', long, not_supported(yarn >= "2", bun))] + #[arg(short = 'P', long, conflicts_with = "global", not_supported(yarn >= "2", bun))] pub(crate) prod: bool, /// Include dev dependencies - #[arg(short = 'D', long, not_supported(yarn >= "2", bun))] + #[arg(short = 'D', long, conflicts_with = "global", not_supported(yarn >= "2", bun))] pub(crate) dev: bool, /// Exclude optional dependencies - #[arg(long, not_supported(npm < "7", yarn, bun))] + #[arg(long, conflicts_with = "global", not_supported(npm < "7", yarn, bun))] pub(crate) no_optional: bool, /// Exclude peer dependencies - #[arg(long, not_supported(npm < "7", yarn, bun))] + #[arg(long, conflicts_with = "global", not_supported(npm < "7", yarn, bun))] pub(crate) exclude_peers: bool, /// Show only project packages - #[arg(long, not_supported(npm, yarn, bun))] + #[arg(long, conflicts_with = "global", not_supported(npm, yarn, bun))] pub(crate) only_projects: bool, /// Use a finder function - #[arg(long, value_name = "FINDER_NAME", not_supported(npm, pnpm < "10.16.0", yarn, bun))] + #[arg(long, value_name = "FINDER_NAME", conflicts_with = "global", not_supported(npm, pnpm < "10.16.0", yarn, bun))] pub(crate) find_by: Option, /// List across all workspaces - #[arg(short = 'r', long, not_supported(npm < "7.14.0", yarn, bun))] + #[arg(short = 'r', long, conflicts_with = "global", not_supported(npm < "7.14.0", yarn, bun))] pub(crate) recursive: bool, /// Filter packages in monorepo - #[arg(long, value_name = "PATTERN", not_supported(npm < "7.14.0", yarn, bun))] + #[arg(long, value_name = "PATTERN", conflicts_with = "global", not_supported(npm < "7.14.0", yarn, bun))] pub(crate) filter: Vec, /// List global packages @@ -190,12 +190,17 @@ mod tests { }; #[test] - fn test_parser_accepts_short_dev_and_global_flags() { - let args = parse_args::(["react", "-D", "-g"]).unwrap(); - + fn test_parser_accepts_short_dev_and_global_flags_separately() { + let args = parse_args::(["react", "-D"]).unwrap(); assert_eq!(args.pattern, Some("react".to_string())); assert!(args.dev); + + let args = parse_args::(["react", "-g"]).unwrap(); + assert_eq!(args.pattern, Some("react".to_string())); assert!(args.global); + + let error = parse_args::(["react", "-D", "-g"]).unwrap_err(); + assert_eq!(error.kind(), clap::error::ErrorKind::ArgumentConflict); } #[test] diff --git a/crates/vp_pm_cli/src/resolution/commands/outdated.rs b/crates/vp_pm_cli/src/resolution/commands/outdated.rs index 47c5e48a4d..4633a8f904 100644 --- a/crates/vp_pm_cli/src/resolution/commands/outdated.rs +++ b/crates/vp_pm_cli/src/resolution/commands/outdated.rs @@ -60,35 +60,35 @@ pub struct OutdatedArgs { pub(crate) format: Option, /// Check recursively across all workspaces - #[arg(short = 'r', long, not_supported(npm < "7.14.0", yarn))] + #[arg(short = 'r', long, conflicts_with = "global", not_supported(npm < "7.14.0", yarn))] pub(crate) recursive: bool, /// Filter packages in monorepo - #[arg(long, value_name = "PATTERN", not_supported(npm < "7.14.0", yarn))] + #[arg(long, value_name = "PATTERN", conflicts_with = "global", not_supported(npm < "7.14.0", yarn))] pub(crate) filter: Vec, /// Include workspace root - #[arg(short = 'w', long, not_supported(npm < "8.1.0", yarn, bun))] + #[arg(short = 'w', long, conflicts_with = "global", not_supported(npm < "8.1.0", yarn, bun))] pub(crate) workspace_root: bool, /// Only production and optional dependencies - #[arg(short = 'P', long, not_supported(npm < "7", yarn))] + #[arg(short = 'P', long, conflicts_with = "global", not_supported(npm < "7", yarn))] pub(crate) prod: bool, /// Include dev dependencies - #[arg(short = 'D', long, not_supported(yarn, bun))] + #[arg(short = 'D', long, conflicts_with = "global", not_supported(yarn, bun))] pub(crate) dev: bool, /// Exclude optional dependencies - #[arg(long, not_supported(npm < "7", yarn))] + #[arg(long, conflicts_with = "global", not_supported(npm < "7", yarn))] pub(crate) no_optional: bool, /// Only show compatible versions - #[arg(long, not_supported(npm, yarn, bun))] + #[arg(long, conflicts_with = "global", not_supported(npm, yarn, bun))] pub(crate) compatible: bool, /// Sort results by field - #[arg(long, value_name = "FIELD", not_supported(npm, yarn, bun))] + #[arg(long, value_name = "FIELD", conflicts_with = "global", not_supported(npm, yarn, bun))] pub(crate) sort_by: Option, /// Check globally installed packages diff --git a/crates/vp_pm_cli/src/resolution/commands/remove.rs b/crates/vp_pm_cli/src/resolution/commands/remove.rs index d55e6d2059..e2b3a6516e 100644 --- a/crates/vp_pm_cli/src/resolution/commands/remove.rs +++ b/crates/vp_pm_cli/src/resolution/commands/remove.rs @@ -8,27 +8,27 @@ use crate::resolution::{ #[derive(clap::Args, Clone, Debug, Default, PartialEq, Eq)] pub struct RemoveArgs { /// Only remove from `devDependencies` (pnpm-specific) - #[arg(short = 'D', long, not_supported(yarn, bun))] + #[arg(short = 'D', long, conflicts_with = "global", not_supported(yarn, bun))] pub(crate) save_dev: bool, /// Only remove from `optionalDependencies` (pnpm-specific) - #[arg(short = 'O', long, not_supported(yarn, bun))] + #[arg(short = 'O', long, conflicts_with = "global", not_supported(yarn, bun))] pub(crate) save_optional: bool, /// Only remove from `dependencies` (pnpm-specific) - #[arg(short = 'P', long, not_supported(yarn, bun))] + #[arg(short = 'P', long, conflicts_with = "global", not_supported(yarn, bun))] pub(crate) save_prod: bool, /// Filter packages in monorepo (can be used multiple times) - #[arg(long, value_name = "PATTERN", not_supported(npm < "7.14.0", bun < "1.4"))] + #[arg(long, value_name = "PATTERN", conflicts_with = "global", not_supported(npm < "7.14.0", bun < "1.4"))] pub(crate) filter: Vec, /// Remove from workspace root - #[arg(short = 'w', long, not_supported(npm < "8.1.0", yarn, bun))] + #[arg(short = 'w', long, conflicts_with = "global", not_supported(npm < "8.1.0", yarn, bun))] pub(crate) workspace_root: bool, /// Remove recursively from all workspace packages - #[arg(short = 'r', long, not_supported(npm < "8.1.0", yarn < "2", bun))] + #[arg(short = 'r', long, conflicts_with = "global", not_supported(npm < "8.1.0", yarn < "2", bun))] pub(crate) recursive: bool, /// Remove global packages diff --git a/crates/vp_pm_cli/src/resolution/commands/update.rs b/crates/vp_pm_cli/src/resolution/commands/update.rs index a0732a588b..146b690a77 100644 --- a/crates/vp_pm_cli/src/resolution/commands/update.rs +++ b/crates/vp_pm_cli/src/resolution/commands/update.rs @@ -29,39 +29,39 @@ pub struct UpdateArgs { pub(crate) ignore_node_mismatch: bool, /// Update recursively in all workspace packages - #[arg(short = 'r', long, not_supported(npm < "8.1.0", yarn < "2", bun < "1.4"))] + #[arg(short = 'r', long, conflicts_with = "global", not_supported(npm < "8.1.0", yarn < "2", bun < "1.4"))] pub(crate) recursive: bool, /// Filter packages in monorepo (can be used multiple times) - #[arg(long, value_name = "PATTERN", not_supported(npm < "7.14.0", yarn >= "2", bun < "1.4"))] + #[arg(long, value_name = "PATTERN", conflicts_with = "global", not_supported(npm < "7.14.0", yarn >= "2", bun < "1.4"))] pub(crate) filter: Vec, /// Include workspace root - #[arg(short = 'w', long, not_supported(npm < "8.1.0", yarn, bun))] + #[arg(short = 'w', long, conflicts_with = "global", not_supported(npm < "8.1.0", yarn, bun))] pub(crate) workspace_root: bool, /// Update devDependencies - #[arg(short = 'D', long, not_supported(yarn, bun < "1.4"))] + #[arg(short = 'D', long, conflicts_with = "global", not_supported(yarn, bun < "1.4"))] pub(crate) dev: bool, /// Update dependencies (production) - #[arg(short = 'P', long, not_supported(npm < "7", yarn >= "2", bun < "1.4"))] + #[arg(short = 'P', long, conflicts_with = "global", not_supported(npm < "7", yarn >= "2", bun < "1.4"))] pub(crate) prod: bool, /// Interactive mode - #[arg(short = 'i', long, not_supported(npm))] + #[arg(short = 'i', long, conflicts_with = "global", not_supported(npm))] pub(crate) interactive: bool, /// Exclude optionalDependencies - #[arg(long, not_supported(yarn >= "2", bun < "1.4"))] + #[arg(long, conflicts_with = "global", not_supported(yarn >= "2", bun < "1.4"))] pub(crate) no_optional: bool, /// Update lockfile only, don't modify package.json - #[arg(long)] + #[arg(long, conflicts_with = "global")] pub(crate) no_save: bool, /// Only update if package exists in workspace (pnpm-specific) - #[arg(long, not_supported(npm, yarn, bun))] + #[arg(long, conflicts_with = "global", not_supported(npm, yarn, bun))] pub(crate) workspace: bool, /// Packages to update (optional - updates all if omitted)