diff --git a/crates/vite_package_manager/src/lib.rs b/crates/vite_package_manager/src/lib.rs index 30a1feca27..0ec524c0e9 100644 --- a/crates/vite_package_manager/src/lib.rs +++ b/crates/vite_package_manager/src/lib.rs @@ -3,6 +3,7 @@ mod config; mod install; pub mod package; pub mod package_manager; +pub mod remove; mod request; mod shim; @@ -21,6 +22,7 @@ pub use crate::{ add::{AddCommandOptions, SaveDependencyType}, package::{DependencyType, PackageJson}, package_manager::{WorkspaceFile, WorkspaceRoot, find_package_root, find_workspace_root}, + remove::RemoveCommandOptions, }; /// The workspace configuration for pnpm. diff --git a/crates/vite_package_manager/src/remove.rs b/crates/vite_package_manager/src/remove.rs new file mode 100644 index 0000000000..4cf8e9b165 --- /dev/null +++ b/crates/vite_package_manager/src/remove.rs @@ -0,0 +1,660 @@ +use std::{collections::HashMap, process::ExitStatus}; + +use vite_error::Error; +use vite_path::AbsolutePath; + +use crate::package_manager::{ + PackageManager, PackageManagerType, ResolveCommandResult, format_path_env, run_command, +}; + +/// Options for the remove command. +#[derive(Debug, Default)] +pub struct RemoveCommandOptions<'a> { + pub packages: &'a [String], + pub filters: Option<&'a [String]>, + pub workspace_root: bool, + pub recursive: bool, + pub global: bool, + pub save_dev: bool, + pub save_optional: bool, + pub save_prod: bool, + pub pass_through_args: Option<&'a [String]>, +} + +impl PackageManager { + /// Run the remove command with the package manager. + /// Return the exit status of the command. + #[must_use] + pub async fn run_remove_command( + &self, + options: &RemoveCommandOptions<'_>, + cwd: impl AsRef, + ) -> Result { + let resolve_command = self.resolve_remove_command(options); + run_command(&resolve_command.bin_path, &resolve_command.args, &resolve_command.envs, cwd) + .await + } + + /// Resolve the remove command. + #[must_use] + pub fn resolve_remove_command(&self, options: &RemoveCommandOptions) -> ResolveCommandResult { + let bin_name: String; + let envs = HashMap::from([("PATH".to_string(), format_path_env(self.get_bin_prefix()))]); + let mut args: Vec = Vec::new(); + + // global packages should use npm cli only + if options.global { + // TODO(@fengmk2): Need to handle the case where the npm CLI does not exist in the PATH + bin_name = "npm".into(); + args.push("uninstall".into()); + args.push("--global".into()); + if let Some(pass_through_args) = options.pass_through_args { + args.extend_from_slice(pass_through_args); + } + args.extend_from_slice(options.packages); + + return ResolveCommandResult { bin_path: bin_name, args, envs }; + } + + match self.client { + PackageManagerType::Pnpm => { + bin_name = "pnpm".into(); + // pnpm: --filter must come before command + if let Some(filters) = options.filters { + for filter in filters { + args.push("--filter".into()); + args.push(filter.clone()); + } + } + args.push("remove".into()); + if options.workspace_root { + args.push("--workspace-root".into()); + } + if options.recursive { + args.push("--recursive".into()); + } + // https://pnpm.io/cli/remove#options + if options.save_dev { + args.push("--save-dev".into()); + } + if options.save_optional { + args.push("--save-optional".into()); + } + if options.save_prod { + args.push("--save-prod".into()); + } + } + PackageManagerType::Yarn => { + bin_name = "yarn".into(); + // NOTE: filters are not supported in recursive mode + // yarn: workspaces foreach --all --include {filter} remove + // https://yarnpkg.com/cli/workspace + if let Some(filters) = options.filters + && !options.recursive + { + args.push("workspaces".into()); + args.push("foreach".into()); + args.push("--all".into()); + for filter in filters { + args.push("--include".into()); + args.push(filter.clone()); + } + } + args.push("remove".into()); + if options.recursive { + args.push("--all".into()); + } + // NOTE: yarn doesn't support -w flag for workspace root in remove command + } + PackageManagerType::Npm => { + bin_name = "npm".into(); + // npm: uninstall --workspace + args.push("uninstall".into()); + if let Some(filters) = options.filters { + for filter in filters { + args.push("--workspace".into()); + args.push(filter.clone()); + } + } + // https://docs.npmjs.com/cli/v11/commands/npm-uninstall#configuration + if options.workspace_root || options.recursive { + // recursive mode will remove from workspace root + args.push("--include-workspace-root".into()); + } + if options.recursive { + args.push("--workspaces".into()); + } + // not support: save_dev, save_optional, save_prod, just ignore them + } + } + + if let Some(pass_through_args) = options.pass_through_args { + args.extend_from_slice(pass_through_args); + } + args.extend_from_slice(options.packages); + + ResolveCommandResult { bin_path: bin_name, args, envs } + } +} + +#[cfg(test)] +mod tests { + use tempfile::{TempDir, tempdir}; + use vite_path::AbsolutePathBuf; + use vite_str::Str; + + use super::*; + + fn create_temp_dir() -> TempDir { + tempdir().expect("Failed to create temp directory") + } + + fn create_mock_package_manager(pm_type: PackageManagerType) -> PackageManager { + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + let install_dir = temp_dir_path.join("install"); + + PackageManager { + client: pm_type, + package_name: pm_type.to_string().into(), + version: Str::from("1.0.0"), + hash: None, + bin_name: pm_type.to_string().into(), + workspace_root: temp_dir_path.clone(), + install_dir, + } + } + + #[test] + fn test_pnpm_basic_remove() { + let pm = create_mock_package_manager(PackageManagerType::Pnpm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["lodash".to_string()], + filters: None, + workspace_root: false, + recursive: false, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.bin_path, "pnpm"); + assert_eq!(result.args, vec!["remove", "lodash"]); + } + + #[test] + fn test_pnpm_remove_with_filter() { + let pm = create_mock_package_manager(PackageManagerType::Pnpm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["lodash".to_string()], + filters: Some(&["app".to_string()]), + workspace_root: false, + recursive: false, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["--filter", "app", "remove", "lodash"]); + assert_eq!(result.bin_path, "pnpm"); + } + + #[test] + fn test_pnpm_remove_workspace_root() { + let pm = create_mock_package_manager(PackageManagerType::Pnpm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["typescript".to_string()], + filters: None, + workspace_root: true, + recursive: false, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["remove", "--workspace-root", "typescript"]); + assert_eq!(result.bin_path, "pnpm"); + } + + #[test] + fn test_pnpm_remove_recursive() { + let pm = create_mock_package_manager(PackageManagerType::Pnpm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["lodash".to_string()], + filters: None, + workspace_root: false, + recursive: true, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["remove", "--recursive", "lodash"]); + assert_eq!(result.bin_path, "pnpm"); + } + + #[test] + fn test_pnpm_remove_multiple_filters() { + let pm = create_mock_package_manager(PackageManagerType::Pnpm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["axios".to_string()], + filters: Some(&["app".to_string(), "web".to_string()]), + workspace_root: false, + recursive: false, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["--filter", "app", "--filter", "web", "remove", "axios"]); + assert_eq!(result.bin_path, "pnpm"); + } + + #[test] + fn test_yarn_basic_remove() { + let pm = create_mock_package_manager(PackageManagerType::Yarn); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["lodash".to_string()], + filters: None, + workspace_root: false, + recursive: false, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["remove", "lodash"]); + assert_eq!(result.bin_path, "yarn"); + } + + #[test] + fn test_yarn_remove_with_workspace() { + let pm = create_mock_package_manager(PackageManagerType::Yarn); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["lodash".to_string()], + filters: Some(&["app".to_string()]), + workspace_root: false, + recursive: false, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!( + result.args, + vec!["workspaces", "foreach", "--all", "--include", "app", "remove", "lodash"] + ); + assert_eq!(result.bin_path, "yarn"); + } + + #[test] + fn test_yarn_remove_recursive() { + let pm = create_mock_package_manager(PackageManagerType::Yarn); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["lodash".to_string()], + filters: None, + workspace_root: false, + recursive: true, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["remove", "--all", "lodash"]); + assert_eq!(result.bin_path, "yarn"); + } + + #[test] + fn test_npm_basic_remove() { + let pm = create_mock_package_manager(PackageManagerType::Npm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["lodash".to_string()], + filters: None, + workspace_root: false, + recursive: false, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["uninstall", "lodash"]); + assert_eq!(result.bin_path, "npm"); + } + + #[test] + fn test_npm_remove_with_workspace() { + let pm = create_mock_package_manager(PackageManagerType::Npm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["lodash".to_string()], + filters: Some(&["app".to_string()]), + workspace_root: false, + recursive: false, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["uninstall", "--workspace", "app", "lodash"]); + assert_eq!(result.bin_path, "npm"); + } + + #[test] + fn test_npm_remove_workspace_root() { + let pm = create_mock_package_manager(PackageManagerType::Npm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["typescript".to_string()], + filters: None, + workspace_root: true, + recursive: false, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["uninstall", "--include-workspace-root", "typescript"]); + assert_eq!(result.bin_path, "npm"); + } + + #[test] + fn test_npm_remove_recursive() { + let pm = create_mock_package_manager(PackageManagerType::Npm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["lodash".to_string()], + filters: None, + workspace_root: false, + recursive: true, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!( + result.args, + vec!["uninstall", "--include-workspace-root", "--workspaces", "lodash"] + ); + assert_eq!(result.bin_path, "npm"); + } + + #[test] + fn test_npm_remove_multiple_workspaces() { + let pm = create_mock_package_manager(PackageManagerType::Npm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["lodash".to_string()], + filters: Some(&["app".to_string(), "web".to_string()]), + workspace_root: false, + recursive: false, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!( + result.args, + vec!["uninstall", "--workspace", "app", "--workspace", "web", "lodash"] + ); + assert_eq!(result.bin_path, "npm"); + } + + #[test] + fn test_global_remove() { + let pm = create_mock_package_manager(PackageManagerType::Pnpm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["typescript".to_string()], + filters: None, + workspace_root: false, + recursive: false, + global: true, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["uninstall", "--global", "typescript"]); + assert_eq!(result.bin_path, "npm"); + } + + #[test] + fn test_remove_multiple_packages() { + let pm = create_mock_package_manager(PackageManagerType::Pnpm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["lodash".to_string(), "axios".to_string(), "underscore".to_string()], + filters: None, + workspace_root: false, + recursive: false, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["remove", "lodash", "axios", "underscore"]); + assert_eq!(result.bin_path, "pnpm"); + } + + #[test] + fn test_remove_with_pass_through_args() { + let pm = create_mock_package_manager(PackageManagerType::Pnpm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["lodash".to_string()], + filters: None, + workspace_root: false, + recursive: false, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: Some(&["--use-stderr".to_string()]), + }); + assert_eq!(result.args, vec!["remove", "--use-stderr", "lodash"]); + assert_eq!(result.bin_path, "pnpm"); + } + + #[test] + fn test_pnpm_remove_save_dev() { + let pm = create_mock_package_manager(PackageManagerType::Pnpm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["typescript".to_string()], + filters: None, + workspace_root: false, + recursive: false, + global: false, + save_dev: true, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["remove", "--save-dev", "typescript"]); + assert_eq!(result.bin_path, "pnpm"); + } + + #[test] + fn test_pnpm_remove_save_optional() { + let pm = create_mock_package_manager(PackageManagerType::Pnpm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["sharp".to_string()], + filters: None, + workspace_root: false, + recursive: false, + global: false, + save_dev: false, + save_optional: true, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["remove", "--save-optional", "sharp"]); + assert_eq!(result.bin_path, "pnpm"); + } + + #[test] + fn test_pnpm_remove_save_prod() { + let pm = create_mock_package_manager(PackageManagerType::Pnpm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["react".to_string()], + filters: None, + workspace_root: false, + recursive: false, + global: false, + save_dev: false, + save_optional: false, + save_prod: true, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["remove", "--save-prod", "react"]); + assert_eq!(result.bin_path, "pnpm"); + } + + #[test] + fn test_npm_remove_save_dev() { + let pm = create_mock_package_manager(PackageManagerType::Npm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["typescript".to_string()], + filters: None, + workspace_root: false, + recursive: false, + global: false, + save_dev: true, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["uninstall", "typescript"]); + assert_eq!(result.bin_path, "npm"); + } + + #[test] + fn test_npm_remove_save_optional() { + let pm = create_mock_package_manager(PackageManagerType::Npm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["sharp".to_string()], + filters: None, + workspace_root: false, + recursive: false, + global: false, + save_dev: false, + save_optional: true, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["uninstall", "sharp"]); + assert_eq!(result.bin_path, "npm"); + } + + #[test] + fn test_npm_remove_save_prod() { + let pm = create_mock_package_manager(PackageManagerType::Npm); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["react".to_string()], + filters: None, + workspace_root: false, + recursive: false, + global: false, + save_dev: false, + save_optional: false, + save_prod: true, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["uninstall", "react"]); + assert_eq!(result.bin_path, "npm"); + } + + #[test] + fn test_yarn_remove_save_flags_ignored() { + // Yarn doesn't support save flags, so they should be ignored + let pm = create_mock_package_manager(PackageManagerType::Yarn); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["lodash".to_string()], + filters: None, + workspace_root: false, + recursive: false, + global: false, + save_dev: true, + save_optional: true, + save_prod: true, + pass_through_args: None, + }); + // Should not include any save flags for yarn + assert_eq!(result.args, vec!["remove", "lodash"]); + assert_eq!(result.bin_path, "yarn"); + } + + #[test] + fn test_yarn_remove_with_recursive() { + let pm = create_mock_package_manager(PackageManagerType::Yarn); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["lodash".to_string()], + filters: None, + workspace_root: false, + recursive: true, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!(result.args, vec!["remove", "--all", "lodash"]); + assert_eq!(result.bin_path, "yarn"); + } + + #[test] + fn test_yarn_remove_with_multiple_filters() { + let pm = create_mock_package_manager(PackageManagerType::Yarn); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["lodash".to_string()], + filters: Some(&["app".to_string(), "web".to_string()]), + workspace_root: false, + recursive: false, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + assert_eq!( + result.args, + vec![ + "workspaces", + "foreach", + "--all", + "--include", + "app", + "--include", + "web", + "remove", + "lodash" + ] + ); + assert_eq!(result.bin_path, "yarn"); + } + + #[test] + fn test_yarn_remove_with_recursive_and_multiple_filters() { + let pm = create_mock_package_manager(PackageManagerType::Yarn); + let result = pm.resolve_remove_command(&RemoveCommandOptions { + packages: &["lodash".to_string()], + filters: Some(&["app".to_string(), "web".to_string()]), + workspace_root: false, + recursive: true, + global: false, + save_dev: false, + save_optional: false, + save_prod: false, + pass_through_args: None, + }); + // ignore filters in recursive mode + assert_eq!(result.args, vec!["remove", "--all", "lodash"]); + assert_eq!(result.bin_path, "yarn"); + } +} diff --git a/packages/cli/binding/src/cli.rs b/packages/cli/binding/src/cli.rs index fc73cafd5c..3f3c3afe96 100644 --- a/packages/cli/binding/src/cli.rs +++ b/packages/cli/binding/src/cli.rs @@ -24,6 +24,7 @@ use crate::commands::{ install::InstallCommand, lib_cmd::lib, lint::{LintConfig, lint}, + remove::RemoveCommand, test::test, vite::vite as vite_cmd, }; @@ -182,6 +183,51 @@ pub enum Commands { #[arg(last = true, allow_hyphen_values = true)] pass_through_args: Option>, }, + /// Remove packages from dependencies + #[command(alias = "rm", alias = "un", alias = "uninstall")] + Remove { + /// Only remove from `devDependencies` (pnpm-specific) + #[arg(short = 'D', long)] + save_dev: bool, + + /// Only remove from `optionalDependencies` (pnpm-specific) + #[arg(short = 'O', long)] + save_optional: bool, + + /// Only remove from `dependencies` (pnpm-specific) + #[arg(short = 'P', long)] + save_prod: bool, + + /// Filter packages in monorepo (can be used multiple times) + #[arg(long, value_name = "PATTERN")] + filter: Option>, + + /// Remove from workspace root + #[arg(short = 'w', long)] + workspace_root: bool, + + /// Remove recursively from all workspace packages, including workspace root + #[arg(short = 'r', long)] + recursive: bool, + + /// Remove global packages + #[arg(short = 'g', long)] + global: bool, + + /// Packages to remove + packages: Vec, + + /// Additional arguments to pass through to the package manager + #[arg(last = true, allow_hyphen_values = true)] + pass_through_args: Option>, + }, +} + +impl Commands { + /// Check if this command is a package manager command that should skip auto-install + pub fn is_package_manager_command(&self) -> bool { + matches!(self, Commands::Install { .. } | Commands::Add { .. } | Commands::Remove { .. }) + } } #[derive(Subcommand, Debug)] @@ -325,8 +371,8 @@ pub async fn main< >, >, ) -> Result { - // Auto-install dependencies if needed, but skip for install command itself, or if `VITE_DISABLE_AUTO_INSTALL=1` is set. - if !matches!(args.commands, Commands::Install { .. } | Commands::Add { .. }) + // Auto-install dependencies if needed, but skip for package manager commands, or if `VITE_DISABLE_AUTO_INSTALL=1` is set. + if !args.commands.is_package_manager_command() && std::env::var_os("VITE_DISABLE_AUTO_INSTALL") != Some("1".into()) { auto_install(&cwd).await?; @@ -542,6 +588,32 @@ pub async fn main< .await?; return Ok(exit_status); } + Commands::Remove { + save_dev, + save_optional, + save_prod, + filter, + workspace_root, + recursive, + global, + packages, + pass_through_args, + } => { + let exit_status = RemoveCommand::new(cwd) + .execute( + packages, + *save_dev, + *save_optional, + *save_prod, + filter.as_deref(), + *workspace_root, + *recursive, + *global, + pass_through_args.as_deref(), + ) + .await?; + return Ok(exit_status); + } }; let execution_summary_dir = EXECUTION_SUMMARY_DIR.as_path(); @@ -1478,4 +1550,310 @@ mod tests { } } } + + mod remove_command_tests { + use super::*; + + #[test] + fn test_args_remove_command() { + let args = Args::try_parse_from(&["vite-plus", "remove", "react"]).unwrap(); + if let Commands::Remove { + save_dev, + save_optional, + save_prod, + filter, + workspace_root, + recursive, + global, + packages, + pass_through_args, + } = &args.commands + { + assert_eq!(packages, &vec!["react".to_string()]); + assert!(!save_dev); + assert!(!save_optional); + assert!(!save_prod); + assert!(filter.is_none()); + assert!(!workspace_root); + assert!(!recursive); + assert!(!global); + assert!(pass_through_args.is_none()); + } else { + panic!("Expected Remove command"); + } + } + + #[test] + fn test_args_remove_command_with_dev_flag() { + let args = Args::try_parse_from(&["vite-plus", "remove", "-D", "typescript"]).unwrap(); + if let Commands::Remove { save_dev, packages, .. } = &args.commands { + assert_eq!(packages, &vec!["typescript".to_string()]); + assert!(save_dev); + } else { + panic!("Expected Remove command"); + } + } + + #[test] + fn test_args_remove_command_with_optional_flag() { + let args = Args::try_parse_from(&["vite-plus", "remove", "-O", "lodash"]).unwrap(); + if let Commands::Remove { save_optional, packages, .. } = &args.commands { + assert_eq!(packages, &vec!["lodash".to_string()]); + assert!(save_optional); + } else { + panic!("Expected Remove command"); + } + } + + #[test] + fn test_args_remove_command_with_prod_flag() { + let args = Args::try_parse_from(&["vite-plus", "remove", "-P", "express"]).unwrap(); + if let Commands::Remove { save_prod, packages, .. } = &args.commands { + assert_eq!(packages, &vec!["express".to_string()]); + assert!(save_prod); + } else { + panic!("Expected Remove command"); + } + } + + #[test] + fn test_args_remove_command_with_workspace_root() { + let args = Args::try_parse_from(&["vite-plus", "remove", "-w", "react"]).unwrap(); + if let Commands::Remove { workspace_root, packages, .. } = &args.commands { + assert_eq!(packages, &vec!["react".to_string()]); + assert!(workspace_root); + } else { + panic!("Expected Remove command"); + } + + let args = Args::try_parse_from(&["vite-plus", "remove", "react", "--workspace-root"]) + .unwrap(); + if let Commands::Remove { workspace_root, packages, .. } = &args.commands { + assert_eq!(packages, &vec!["react".to_string()]); + assert!(workspace_root); + } else { + panic!("Expected Remove command"); + } + } + + #[test] + fn test_args_remove_command_with_recursive() { + let args = Args::try_parse_from(&["vite-plus", "remove", "-r", "react"]).unwrap(); + if let Commands::Remove { recursive, packages, .. } = &args.commands { + assert_eq!(packages, &vec!["react".to_string()]); + assert!(recursive); + } else { + panic!("Expected Remove command"); + } + + let args = + Args::try_parse_from(&["vite-plus", "remove", "react", "--recursive"]).unwrap(); + if let Commands::Remove { recursive, packages, .. } = &args.commands { + assert_eq!(packages, &vec!["react".to_string()]); + assert!(recursive); + } else { + panic!("Expected Remove command"); + } + } + + #[test] + fn test_args_remove_command_with_global() { + let args = Args::try_parse_from(&["vite-plus", "remove", "-g", "npm"]).unwrap(); + if let Commands::Remove { global, packages, .. } = &args.commands { + assert_eq!(packages, &vec!["npm".to_string()]); + assert!(global); + } else { + panic!("Expected Remove command"); + } + + let args = Args::try_parse_from(&["vite-plus", "remove", "npm", "--global"]).unwrap(); + if let Commands::Remove { global, packages, .. } = &args.commands { + assert_eq!(packages, &vec!["npm".to_string()]); + assert!(global); + } else { + panic!("Expected Remove command"); + } + } + + #[test] + fn test_args_remove_command_multiple_packages() { + let args = Args::try_parse_from(&[ + "vite-plus", + "remove", + "react", + "react-dom", + "@types/react", + ]) + .unwrap(); + if let Commands::Remove { packages, .. } = &args.commands { + assert_eq!(packages, &vec!["react", "react-dom", "@types/react"]); + } else { + panic!("Expected Remove command"); + } + } + + #[test] + fn test_args_remove_command_with_single_filter() { + let args = + Args::try_parse_from(&["vite-plus", "remove", "--filter", "app", "typescript"]) + .unwrap(); + if let Commands::Remove { filter, packages, .. } = &args.commands { + assert_eq!(filter, &Some(vec!["app".to_string()])); + assert_eq!(packages, &vec!["typescript"]); + } else { + panic!("Expected Remove command"); + } + } + + #[test] + fn test_args_remove_command_with_multiple_filters() { + let args = Args::try_parse_from(&[ + "vite-plus", + "remove", + "--filter", + "app", + "--filter", + "web", + "react", + ]) + .unwrap(); + if let Commands::Remove { filter, packages, .. } = &args.commands { + assert_eq!(filter, &Some(vec!["app".to_string(), "web".to_string()])); + assert_eq!(packages, &vec!["react"]); + } else { + panic!("Expected Remove command"); + } + } + + #[test] + fn test_args_remove_command_with_combined_flags() { + let args = Args::try_parse_from(&[ + "vite-plus", + "remove", + "-D", + "-w", + "--filter", + "app", + "typescript", + "eslint", + ]) + .unwrap(); + if let Commands::Remove { save_dev, workspace_root, filter, packages, .. } = + &args.commands + { + assert!(save_dev); + assert!(workspace_root); + assert_eq!(filter, &Some(vec!["app".to_string()])); + assert_eq!(packages, &vec!["typescript", "eslint"]); + } else { + panic!("Expected Remove command"); + } + } + + #[test] + fn test_args_remove_command_with_pass_through_args() { + let args = Args::try_parse_from(&[ + "vite-plus", + "remove", + "react", + "--", + "--ignore-scripts", + "--force", + ]) + .unwrap(); + if let Commands::Remove { packages, pass_through_args, .. } = &args.commands { + assert_eq!(packages, &vec!["react"]); + assert_eq!( + pass_through_args, + &Some(vec!["--ignore-scripts".to_string(), "--force".to_string()]) + ); + } else { + panic!("Expected Remove command"); + } + } + + #[test] + fn test_args_remove_command_alias_rm() { + let args = Args::try_parse_from(&["vite-plus", "rm", "react"]).unwrap(); + if let Commands::Remove { packages, .. } = &args.commands { + assert_eq!(packages, &vec!["react"]); + } else { + panic!("Expected Remove command"); + } + } + + #[test] + fn test_args_remove_command_alias_un() { + let args = Args::try_parse_from(&["vite-plus", "un", "react"]).unwrap(); + if let Commands::Remove { packages, .. } = &args.commands { + assert_eq!(packages, &vec!["react"]); + } else { + panic!("Expected Remove command"); + } + } + + #[test] + fn test_args_remove_command_alias_uninstall() { + let args = Args::try_parse_from(&["vite-plus", "uninstall", "react"]).unwrap(); + if let Commands::Remove { packages, .. } = &args.commands { + assert_eq!(packages, &vec!["react"]); + } else { + panic!("Expected Remove command"); + } + } + + #[test] + fn test_args_remove_command_invalid_filter() { + let args = Args::try_parse_from(&["vite-plus", "remove", "react", "--filter"]); + assert!(args.is_err()); + } + + #[test] + fn test_args_remove_command_no_packages() { + // Remove command should accept empty packages list (package manager will handle this) + let args = Args::try_parse_from(&["vite-plus", "remove"]).unwrap(); + if let Commands::Remove { packages, .. } = &args.commands { + assert!(packages.is_empty()); + } else { + panic!("Expected Remove command"); + } + } + + #[test] + fn test_args_remove_command_complex_scenario() { + let args = Args::try_parse_from(&[ + "vite-plus", + "remove", + "-D", + "-r", + "--filter", + "app", + "--filter", + "web", + "typescript", + "eslint", + "@types/node", + "--", + "--ignore-scripts", + ]) + .unwrap(); + if let Commands::Remove { + save_dev, + recursive, + filter, + packages, + pass_through_args, + .. + } = &args.commands + { + assert!(save_dev); + assert!(recursive); + assert_eq!(filter, &Some(vec!["app".to_string(), "web".to_string()])); + assert_eq!(packages, &vec!["typescript", "eslint", "@types/node"]); + assert_eq!(pass_through_args, &Some(vec!["--ignore-scripts".to_string()])); + } else { + panic!("Expected Remove command"); + } + } + } } diff --git a/packages/cli/binding/src/commands/mod.rs b/packages/cli/binding/src/commands/mod.rs index fda8da6f0d..0b0a6ccd0c 100644 --- a/packages/cli/binding/src/commands/mod.rs +++ b/packages/cli/binding/src/commands/mod.rs @@ -4,5 +4,6 @@ pub(crate) mod fmt; pub(crate) mod install; pub(crate) mod lib_cmd; pub(crate) mod lint; +pub(crate) mod remove; pub(crate) mod test; pub(crate) mod vite; diff --git a/packages/cli/binding/src/commands/remove.rs b/packages/cli/binding/src/commands/remove.rs new file mode 100644 index 0000000000..8220fad54a --- /dev/null +++ b/packages/cli/binding/src/commands/remove.rs @@ -0,0 +1,86 @@ +use std::process::ExitStatus; + +use vite_package_manager::{package_manager::PackageManager, remove::RemoveCommandOptions}; +use vite_path::AbsolutePathBuf; + +use crate::Error; + +/// Remove command for removing packages from dependencies. +/// +/// This command automatically detects the package manager and translates +/// the remove command to the appropriate package manager-specific syntax. +pub struct RemoveCommand { + cwd: AbsolutePathBuf, +} + +impl RemoveCommand { + pub fn new(cwd: AbsolutePathBuf) -> Self { + Self { cwd } + } + + pub async fn execute( + self, + packages: &[String], + save_dev: bool, + save_optional: bool, + save_prod: bool, + filters: Option<&[String]>, + workspace_root: bool, + recursive: bool, + global: bool, + pass_through_args: Option<&[String]>, + ) -> Result { + if packages.is_empty() { + return Err(Error::NoPackagesSpecified); + } + + // Detect package manager + let package_manager = PackageManager::builder(&self.cwd).build().await?; + + let remove_command_options = RemoveCommandOptions { + packages, + filters, + workspace_root, + recursive, + global, + save_dev, + save_optional, + save_prod, + pass_through_args, + }; + package_manager.run_remove_command(&remove_command_options, &self.cwd).await + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_remove_command_new() { + let workspace_root = if cfg!(windows) { + AbsolutePathBuf::new("C:\\test".into()).unwrap() + } else { + AbsolutePathBuf::new("/test".into()).unwrap() + }; + + let cmd = RemoveCommand::new(workspace_root.clone()); + assert_eq!(cmd.cwd, workspace_root); + } + + #[tokio::test] + async fn test_remove_command_no_packages() { + let workspace_root = if cfg!(windows) { + AbsolutePathBuf::new("C:\\test".into()).unwrap() + } else { + AbsolutePathBuf::new("/test".into()).unwrap() + }; + + let cmd = RemoveCommand::new(workspace_root); + let result = + cmd.execute(&vec![], false, false, false, None, false, false, false, None).await; + + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), Error::NoPackagesSpecified)); + } +} diff --git a/packages/cli/snap-tests/exit-non-zero-on-cmd-not-exists/snap.txt b/packages/cli/snap-tests/exit-non-zero-on-cmd-not-exists/snap.txt index 8a5cc1a1b4..e9d8b4b4c7 100644 --- a/packages/cli/snap-tests/exit-non-zero-on-cmd-not-exists/snap.txt +++ b/packages/cli/snap-tests/exit-non-zero-on-cmd-not-exists/snap.txt @@ -1,6 +1,6 @@ [2]> vite command-not-exists # should exit with non-zero code error: 'vite' requires a subcommand but one was not provided - [subcommands: run, lint, fmt, build, test, lib, dev, doc, cache, install, i, add, help] + [subcommands: run, lint, fmt, build, test, lib, dev, doc, cache, install, i, add, remove, rm, un, uninstall, help] Usage: vite [OPTIONS] [TASK] [-- ...] diff --git a/packages/global/snap-tests/cli-helper-message/snap.txt b/packages/global/snap-tests/cli-helper-message/snap.txt index e6ec6a72f4..af6229f8c9 100644 --- a/packages/global/snap-tests/cli-helper-message/snap.txt +++ b/packages/global/snap-tests/cli-helper-message/snap.txt @@ -13,6 +13,7 @@ Commands: cache Manage the task cache install Install command. It will be passed to the package manager's install command currently add Add packages to dependencies + remove Remove packages from dependencies help Print this message or the help of the given subcommand(s) Arguments: diff --git a/packages/global/snap-tests/command-remove-npm10-with-workspace/package.json b/packages/global/snap-tests/command-remove-npm10-with-workspace/package.json new file mode 100644 index 0000000000..0de5d24f02 --- /dev/null +++ b/packages/global/snap-tests/command-remove-npm10-with-workspace/package.json @@ -0,0 +1,8 @@ +{ + "name": "command-remove-npm10-with-workspace", + "version": "1.0.0", + "packageManager": "npm@10.9.4", + "workspaces": [ + "packages/*" + ] +} diff --git a/packages/global/snap-tests/command-remove-npm10-with-workspace/packages/app/package.json b/packages/global/snap-tests/command-remove-npm10-with-workspace/packages/app/package.json new file mode 100644 index 0000000000..fd1f8f6386 --- /dev/null +++ b/packages/global/snap-tests/command-remove-npm10-with-workspace/packages/app/package.json @@ -0,0 +1,3 @@ +{ + "name": "app" +} diff --git a/packages/global/snap-tests/command-remove-npm10-with-workspace/packages/utils/package.json b/packages/global/snap-tests/command-remove-npm10-with-workspace/packages/utils/package.json new file mode 100644 index 0000000000..d063b255a7 --- /dev/null +++ b/packages/global/snap-tests/command-remove-npm10-with-workspace/packages/utils/package.json @@ -0,0 +1,5 @@ +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "private": true +} diff --git a/packages/global/snap-tests/command-remove-npm10-with-workspace/snap.txt b/packages/global/snap-tests/command-remove-npm10-with-workspace/snap.txt new file mode 100644 index 0000000000..2e8f6f48ec --- /dev/null +++ b/packages/global/snap-tests/command-remove-npm10-with-workspace/snap.txt @@ -0,0 +1,167 @@ +> vp add testnpm2 -D -w --filter=* -- --no-audit && vp add test-vite-plus-install -w --filter=* -- --no-audit && vp add test-vite-plus-package-optional -O --filter=* -- --no-audit && cat package.json packages/app/package.json packages/utils/package.json # prepare packages +Running: npm install --workspace * --include-workspace-root --save-dev --no-audit testnpm2 + +added 3 packages in ms +Running: npm install --workspace * --include-workspace-root --no-audit test-vite-plus-install + +added 1 package in ms +Running: npm install --workspace * --save-optional --no-audit test-vite-plus-package-optional + +added 1 package in ms +{ + "name": "command-remove-npm10-with-workspace", + "version": "1.0.0", + "packageManager": "npm@", + "workspaces": [ + "packages/*" + ], + "devDependencies": { + "testnpm2": "^1.0.1" + }, + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} +{ + "name": "app", + "devDependencies": { + "testnpm2": "^1.0.1" + }, + "dependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "private": true, + "devDependencies": { + "testnpm2": "^1.0.1" + }, + "dependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} + +> vp remove testnpm2 -r -- --no-audit && cat package.json packages/app/package.json packages/utils/package.json # should remove package from all workspaces and root +Running: npm uninstall --include-workspace-root --workspaces --no-audit testnpm2 + +removed 1 package in ms +{ + "name": "command-remove-npm10-with-workspace", + "version": "1.0.0", + "packageManager": "npm@", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} +{ + "name": "app", + "dependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "private": true, + "dependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} + +> vp remove -O test-vite-plus-package-optional -r -- --no-audit && cat package.json packages/app/package.json packages/utils/package.json # should remove optional package from all workspaces +Running: npm uninstall --include-workspace-root --workspaces --no-audit test-vite-plus-package-optional + +removed 1 package in ms +{ + "name": "command-remove-npm10-with-workspace", + "version": "1.0.0", + "packageManager": "npm@", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} +{ + "name": "app", + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "private": true, + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} + +> vp remove test-vite-plus-install --filter=app -- --no-audit && cat package.json packages/app/package.json packages/utils/package.json # should remove package by filter=app +Running: npm uninstall --workspace app --no-audit test-vite-plus-install + +up to date in ms +{ + "name": "command-remove-npm10-with-workspace", + "version": "1.0.0", + "packageManager": "npm@", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} +{ + "name": "app" +} +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "private": true, + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} + +> vp remove test-vite-plus-install --filter=* -- --no-audit && cat package.json packages/app/package.json packages/utils/package.json # should remove package by filter=* +Running: npm uninstall --workspace * --no-audit test-vite-plus-install + +up to date in ms +{ + "name": "command-remove-npm10-with-workspace", + "version": "1.0.0", + "packageManager": "npm@", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} +{ + "name": "app" +} +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "private": true +} diff --git a/packages/global/snap-tests/command-remove-npm10-with-workspace/steps.json b/packages/global/snap-tests/command-remove-npm10-with-workspace/steps.json new file mode 100644 index 0000000000..e43d0af0f3 --- /dev/null +++ b/packages/global/snap-tests/command-remove-npm10-with-workspace/steps.json @@ -0,0 +1,12 @@ +{ + "env": { + "VITE_DISABLE_AUTO_INSTALL": "1" + }, + "commands": [ + "vp add testnpm2 -D -w --filter=* -- --no-audit && vp add test-vite-plus-install -w --filter=* -- --no-audit && vp add test-vite-plus-package-optional -O --filter=* -- --no-audit && cat package.json packages/app/package.json packages/utils/package.json # prepare packages", + "vp remove testnpm2 -r -- --no-audit && cat package.json packages/app/package.json packages/utils/package.json # should remove package from all workspaces and root", + "vp remove -O test-vite-plus-package-optional -r -- --no-audit && cat package.json packages/app/package.json packages/utils/package.json # should remove optional package from all workspaces", + "vp remove test-vite-plus-install --filter=app -- --no-audit && cat package.json packages/app/package.json packages/utils/package.json # should remove package by filter=app", + "vp remove test-vite-plus-install --filter=* -- --no-audit && cat package.json packages/app/package.json packages/utils/package.json # should remove package by filter=*" + ] +} diff --git a/packages/global/snap-tests/command-remove-npm10/package.json b/packages/global/snap-tests/command-remove-npm10/package.json new file mode 100644 index 0000000000..1791f39a0e --- /dev/null +++ b/packages/global/snap-tests/command-remove-npm10/package.json @@ -0,0 +1,5 @@ +{ + "name": "command-remove-npm10", + "version": "1.0.0", + "packageManager": "npm@10.9.4" +} diff --git a/packages/global/snap-tests/command-remove-npm10/snap.txt b/packages/global/snap-tests/command-remove-npm10/snap.txt new file mode 100644 index 0000000000..9982f7e8b4 --- /dev/null +++ b/packages/global/snap-tests/command-remove-npm10/snap.txt @@ -0,0 +1,67 @@ +> vp remove testnpm2 -D -- --no-audit && cat package.json # should pass when remove not exists package +Running: npm uninstall --no-audit testnpm2 + +up to date in ms +{ + "name": "command-remove-npm10", + "version": "1.0.0", + "packageManager": "npm@" +} + +> vp add testnpm2 -- --no-audit && vp add -D test-vite-plus-install -- --no-audit && vp add -O test-vite-plus-package-optional -- --no-audit && cat package.json # should add packages to dependencies +Running: npm install --no-audit testnpm2 + +added 1 package in ms +Running: npm install --save-dev --no-audit test-vite-plus-install + +added 1 package in ms +Running: npm install --save-optional --no-audit test-vite-plus-package-optional + +added 1 package in ms +{ + "name": "command-remove-npm10", + "version": "1.0.0", + "packageManager": "npm@", + "dependencies": { + "testnpm2": "^1.0.1" + }, + "devDependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} + +> vp remove testnpm2 test-vite-plus-install -- --no-audit && cat package.json # should remove packages from dependencies +Running: npm uninstall --no-audit testnpm2 test-vite-plus-install + +removed 2 packages in ms +{ + "name": "command-remove-npm10", + "version": "1.0.0", + "packageManager": "npm@", + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} + +> vp remove -D test-vite-plus-package-optional -- --loglevel=warn --no-audit && cat package.json # support ignore -O flag and remove package from optional dependencies +Running: npm uninstall --loglevel=warn --no-audit test-vite-plus-package-optional + +removed 1 package in ms +{ + "name": "command-remove-npm10", + "version": "1.0.0", + "packageManager": "npm@" +} + +> vp remove -g testnpm2 -- --dry-run --no-audit && cat package.json # support remove global package with dry-run +Running: npm uninstall --global --dry-run --no-audit testnpm2 + +up to date in ms +{ + "name": "command-remove-npm10", + "version": "1.0.0", + "packageManager": "npm@" +} diff --git a/packages/global/snap-tests/command-remove-npm10/steps.json b/packages/global/snap-tests/command-remove-npm10/steps.json new file mode 100644 index 0000000000..e9906be1b2 --- /dev/null +++ b/packages/global/snap-tests/command-remove-npm10/steps.json @@ -0,0 +1,12 @@ +{ + "env": { + "VITE_DISABLE_AUTO_INSTALL": "1" + }, + "commands": [ + "vp remove testnpm2 -D -- --no-audit && cat package.json # should pass when remove not exists package", + "vp add testnpm2 -- --no-audit && vp add -D test-vite-plus-install -- --no-audit && vp add -O test-vite-plus-package-optional -- --no-audit && cat package.json # should add packages to dependencies", + "vp remove testnpm2 test-vite-plus-install -- --no-audit && cat package.json # should remove packages from dependencies", + "vp remove -D test-vite-plus-package-optional -- --loglevel=warn --no-audit && cat package.json # support ignore -O flag and remove package from optional dependencies", + "vp remove -g testnpm2 -- --dry-run --no-audit && cat package.json # support remove global package with dry-run" + ] +} diff --git a/packages/global/snap-tests/command-remove-pnpm10-with-workspace/package.json b/packages/global/snap-tests/command-remove-pnpm10-with-workspace/package.json new file mode 100644 index 0000000000..2b8aa374fc --- /dev/null +++ b/packages/global/snap-tests/command-remove-pnpm10-with-workspace/package.json @@ -0,0 +1,5 @@ +{ + "name": "command-remove-pnpm10-with-workspace", + "version": "1.0.0", + "packageManager": "pnpm@10.18.0" +} diff --git a/packages/global/snap-tests/command-remove-pnpm10-with-workspace/packages/app/package.json b/packages/global/snap-tests/command-remove-pnpm10-with-workspace/packages/app/package.json new file mode 100644 index 0000000000..fd1f8f6386 --- /dev/null +++ b/packages/global/snap-tests/command-remove-pnpm10-with-workspace/packages/app/package.json @@ -0,0 +1,3 @@ +{ + "name": "app" +} diff --git a/packages/global/snap-tests/command-remove-pnpm10-with-workspace/packages/utils/package.json b/packages/global/snap-tests/command-remove-pnpm10-with-workspace/packages/utils/package.json new file mode 100644 index 0000000000..d063b255a7 --- /dev/null +++ b/packages/global/snap-tests/command-remove-pnpm10-with-workspace/packages/utils/package.json @@ -0,0 +1,5 @@ +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "private": true +} diff --git a/packages/global/snap-tests/command-remove-pnpm10-with-workspace/pnpm-workspace.yaml b/packages/global/snap-tests/command-remove-pnpm10-with-workspace/pnpm-workspace.yaml new file mode 100644 index 0000000000..924b55f42e --- /dev/null +++ b/packages/global/snap-tests/command-remove-pnpm10-with-workspace/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - packages/* diff --git a/packages/global/snap-tests/command-remove-pnpm10-with-workspace/snap.txt b/packages/global/snap-tests/command-remove-pnpm10-with-workspace/snap.txt new file mode 100644 index 0000000000..fc657bdf6b --- /dev/null +++ b/packages/global/snap-tests/command-remove-pnpm10-with-workspace/snap.txt @@ -0,0 +1,158 @@ +> vp add testnpm2 -D -w --filter=* && vp add test-vite-plus-install -w --filter=* && vp add test-vite-plus-package-optional -O --filter=* && cat package.json packages/app/package.json packages/utils/package.json # prepare packages +Running: pnpm --filter * add --workspace-root --save-dev testnpm2 +. | +1 + +Progress: resolved , reused , downloaded , added , done +Done in ms using pnpm v +Running: pnpm --filter * add --workspace-root test-vite-plus-install +. | +1 + +Progress: resolved , reused , downloaded , added , done +Done in ms using pnpm v +Running: pnpm --filter * add --save-optional test-vite-plus-package-optional +. | +1 + +Progress: resolved , reused , downloaded , added , done +Done in ms using pnpm v +{ + "name": "command-remove-pnpm10-with-workspace", + "version": "1.0.0", + "packageManager": "pnpm@", + "devDependencies": { + "testnpm2": "^1.0.1" + }, + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} +{ + "name": "app", + "devDependencies": { + "testnpm2": "^1.0.1" + }, + "dependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "private": true, + "devDependencies": { + "testnpm2": "^1.0.1" + }, + "dependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} + +> vp remove testnpm2 -r && cat package.json packages/app/package.json packages/utils/package.json # should remove package from all workspaces and root +Running: pnpm remove --recursive testnpm2 +Scope: all workspace projects +. | -1 - +Progress: resolved , reused , downloaded , added , done +Done in ms using pnpm v +{ + "name": "command-remove-pnpm10-with-workspace", + "version": "1.0.0", + "packageManager": "pnpm@", + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} +{ + "name": "app", + "dependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "private": true, + "dependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} + +> vp remove -O test-vite-plus-package-optional -r && cat package.json packages/app/package.json packages/utils/package.json # should remove optional package from all workspaces +Running: pnpm remove --recursive --save-optional test-vite-plus-package-optional +Scope: all workspace projects +. | -1 - +Progress: resolved , reused , downloaded , added , done +Done in ms using pnpm v +{ + "name": "command-remove-pnpm10-with-workspace", + "version": "1.0.0", + "packageManager": "pnpm@", + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} +{ + "name": "app", + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "private": true, + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} + +> vp remove test-vite-plus-install --filter=app && cat package.json packages/app/package.json packages/utils/package.json # should remove package by filter=app +Running: pnpm --filter app remove test-vite-plus-install +. |  WARN  `node_modules` is present. Lockfile only installation will make it out-of-date +Progress: resolved , reused , downloaded , added , done +Done in ms using pnpm v +{ + "name": "command-remove-pnpm10-with-workspace", + "version": "1.0.0", + "packageManager": "pnpm@", + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} +{ + "name": "app" +} +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "private": true, + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} + +> vp remove test-vite-plus-install --filter=* && cat package.json packages/app/package.json packages/utils/package.json # should remove package by filter=* +Running: pnpm --filter * remove test-vite-plus-install +Scope: all workspace projects +. | -1 - +Done in ms using pnpm v +{ + "name": "command-remove-pnpm10-with-workspace", + "version": "1.0.0", + "packageManager": "pnpm@" +} +{ + "name": "app" +} +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "private": true +} diff --git a/packages/global/snap-tests/command-remove-pnpm10-with-workspace/steps.json b/packages/global/snap-tests/command-remove-pnpm10-with-workspace/steps.json new file mode 100644 index 0000000000..6843d8c3ad --- /dev/null +++ b/packages/global/snap-tests/command-remove-pnpm10-with-workspace/steps.json @@ -0,0 +1,12 @@ +{ + "env": { + "VITE_DISABLE_AUTO_INSTALL": "1" + }, + "commands": [ + "vp add testnpm2 -D -w --filter=* && vp add test-vite-plus-install -w --filter=* && vp add test-vite-plus-package-optional -O --filter=* && cat package.json packages/app/package.json packages/utils/package.json # prepare packages", + "vp remove testnpm2 -r && cat package.json packages/app/package.json packages/utils/package.json # should remove package from all workspaces and root", + "vp remove -O test-vite-plus-package-optional -r && cat package.json packages/app/package.json packages/utils/package.json # should remove optional package from all workspaces", + "vp remove test-vite-plus-install --filter=app && cat package.json packages/app/package.json packages/utils/package.json # should remove package by filter=app", + "vp remove test-vite-plus-install --filter=* && cat package.json packages/app/package.json packages/utils/package.json # should remove package by filter=*" + ] +} diff --git a/packages/global/snap-tests/command-remove-pnpm10/package.json b/packages/global/snap-tests/command-remove-pnpm10/package.json new file mode 100644 index 0000000000..b0f25d1d6c --- /dev/null +++ b/packages/global/snap-tests/command-remove-pnpm10/package.json @@ -0,0 +1,5 @@ +{ + "name": "command-remove-pnpm10", + "version": "1.0.0", + "packageManager": "pnpm@10.18.0" +} diff --git a/packages/global/snap-tests/command-remove-pnpm10/snap.txt b/packages/global/snap-tests/command-remove-pnpm10/snap.txt new file mode 100644 index 0000000000..93475751e2 --- /dev/null +++ b/packages/global/snap-tests/command-remove-pnpm10/snap.txt @@ -0,0 +1,114 @@ +> vp remove --help # should show help +Remove packages from dependencies + +Usage: vp remove [OPTIONS] [PACKAGES]... [-- ...] + +Arguments: + [PACKAGES]... Packages to remove + [PASS_THROUGH_ARGS]... Additional arguments to pass through to the package manager + +Options: + -D, --save-dev Only remove from `devDependencies` (pnpm-specific) + -O, --save-optional Only remove from `optionalDependencies` (pnpm-specific) + -P, --save-prod Only remove from `dependencies` (pnpm-specific) + --filter Filter packages in monorepo (can be used multiple times) + -w, --workspace-root Remove from workspace root + -r, --recursive Remove recursively from all workspace packages, including workspace root + -g, --global Remove global packages + -h, --help Print help + +[1]> vp remove testnpm2 -D && cat package.json # should error when remove not exists package from dev dependencies +Running: pnpm remove --save-dev testnpm2 + ERR_PNPM_CANNOT_REMOVE_MISSING_DEPS  Cannot remove 'testnpm2': project has no 'devDependencies' + +> vp add testnpm2 && vp add -D test-vite-plus-install && vp add -O test-vite-plus-package-optional && cat package.json # should add packages to dependencies +Running: pnpm add testnpm2 +Packages: + ++ +Progress: resolved , reused , downloaded , added , done + +dependencies: ++ testnpm2 + +Done in ms using pnpm v +Running: pnpm add --save-dev test-vite-plus-install +Packages: + ++ +Progress: resolved , reused , downloaded , added , done + +devDependencies: ++ test-vite-plus-install + +Done in ms using pnpm v +Running: pnpm add --save-optional test-vite-plus-package-optional +Packages: + ++ +Progress: resolved , reused , downloaded , added , done + +optionalDependencies: ++ test-vite-plus-package-optional + +Done in ms using pnpm v +{ + "name": "command-remove-pnpm10", + "version": "1.0.0", + "packageManager": "pnpm@", + "dependencies": { + "testnpm2": "^1.0.1" + }, + "devDependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} + +> vp remove testnpm2 test-vite-plus-install && cat package.json # should remove packages from dependencies +Running: pnpm remove testnpm2 test-vite-plus-install +Packages: -2 +-- +Progress: resolved , reused , downloaded , added , done + +dependencies: +- testnpm2 + +devDependencies: +- test-vite-plus-install + +Done in ms using pnpm v +{ + "name": "command-remove-pnpm10", + "version": "1.0.0", + "packageManager": "pnpm@", + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} + +> vp remove -O test-vite-plus-package-optional -- --loglevel=warn && cat package.json # support remove package from optional dependencies and pass through arguments +Running: pnpm remove --save-optional --loglevel=warn test-vite-plus-package-optional +{ + "name": "command-remove-pnpm10", + "version": "1.0.0", + "packageManager": "pnpm@" +} + +> vp remove -g testnpm2 -- --dry-run && cat package.json # support remove global package with dry-run +Running: npm uninstall --global --dry-run testnpm2 + +up to date in ms +{ + "name": "command-remove-pnpm10", + "version": "1.0.0", + "packageManager": "pnpm@" +} + +[2]> vp rm --stream foo && should show tips to use pass through arguments when options are not supported +error: unexpected argument '--stream' found + + tip: to pass '--stream' as a value, use '-- --stream' + +Usage: vp remove [OPTIONS] [PACKAGES]... [-- ...] + +For more information, try '--help'. diff --git a/packages/global/snap-tests/command-remove-pnpm10/steps.json b/packages/global/snap-tests/command-remove-pnpm10/steps.json new file mode 100644 index 0000000000..d6feeb9897 --- /dev/null +++ b/packages/global/snap-tests/command-remove-pnpm10/steps.json @@ -0,0 +1,14 @@ +{ + "env": { + "VITE_DISABLE_AUTO_INSTALL": "1" + }, + "commands": [ + "vp remove --help # should show help", + "vp remove testnpm2 -D && cat package.json # should error when remove not exists package from dev dependencies", + "vp add testnpm2 && vp add -D test-vite-plus-install && vp add -O test-vite-plus-package-optional && cat package.json # should add packages to dependencies", + "vp remove testnpm2 test-vite-plus-install && cat package.json # should remove packages from dependencies", + "vp remove -O test-vite-plus-package-optional -- --loglevel=warn && cat package.json # support remove package from optional dependencies and pass through arguments", + "vp remove -g testnpm2 -- --dry-run && cat package.json # support remove global package with dry-run", + "vp rm --stream foo && should show tips to use pass through arguments when options are not supported" + ] +} diff --git a/packages/global/snap-tests/command-remove-yarn4-with-workspace/package.json b/packages/global/snap-tests/command-remove-yarn4-with-workspace/package.json new file mode 100644 index 0000000000..8e398849d1 --- /dev/null +++ b/packages/global/snap-tests/command-remove-yarn4-with-workspace/package.json @@ -0,0 +1,8 @@ +{ + "name": "command-remove-yarn4-with-workspace", + "version": "1.0.0", + "packageManager": "yarn@4.10.3", + "workspaces": [ + "packages/*" + ] +} diff --git a/packages/global/snap-tests/command-remove-yarn4-with-workspace/packages/admin/package.json b/packages/global/snap-tests/command-remove-yarn4-with-workspace/packages/admin/package.json new file mode 100644 index 0000000000..789682bf09 --- /dev/null +++ b/packages/global/snap-tests/command-remove-yarn4-with-workspace/packages/admin/package.json @@ -0,0 +1,3 @@ +{ + "name": "admin" +} diff --git a/packages/global/snap-tests/command-remove-yarn4-with-workspace/packages/app/package.json b/packages/global/snap-tests/command-remove-yarn4-with-workspace/packages/app/package.json new file mode 100644 index 0000000000..fd1f8f6386 --- /dev/null +++ b/packages/global/snap-tests/command-remove-yarn4-with-workspace/packages/app/package.json @@ -0,0 +1,3 @@ +{ + "name": "app" +} diff --git a/packages/global/snap-tests/command-remove-yarn4-with-workspace/packages/utils/package.json b/packages/global/snap-tests/command-remove-yarn4-with-workspace/packages/utils/package.json new file mode 100644 index 0000000000..30b08efda4 --- /dev/null +++ b/packages/global/snap-tests/command-remove-yarn4-with-workspace/packages/utils/package.json @@ -0,0 +1,4 @@ +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0" +} diff --git a/packages/global/snap-tests/command-remove-yarn4-with-workspace/snap.txt b/packages/global/snap-tests/command-remove-yarn4-with-workspace/snap.txt new file mode 100644 index 0000000000..3e291cb5db --- /dev/null +++ b/packages/global/snap-tests/command-remove-yarn4-with-workspace/snap.txt @@ -0,0 +1,363 @@ +> vp add testnpm2 -D && vp add testnpm2 -D --filter=* --filter=@vite-plus-test/utils && vp add test-vite-plus-install --filter=* --filter=@vite-plus-test/utils && vp add test-vite-plus-package-optional -O --filter=* --filter=@vite-plus-test/utils && cat package.json packages/app/package.json packages/admin/package.json packages/utils/package.json # prepare packages +Running: yarn add --dev testnpm2 +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0085: │ + testnpm2@npm:1.0.1 +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +Running: yarn workspaces foreach --all --include * --include @vite-plus-test/utils add --dev testnpm2 +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +Done in ms ms +Running: yarn workspaces foreach --all --include * --include @vite-plus-test/utils add test-vite-plus-install +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0085: │ + test-vite-plus-install@npm:1.0.0 +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +Done in ms ms +Running: yarn workspaces foreach --all --include * --include @vite-plus-test/utils add --optional test-vite-plus-package-optional +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0085: │ + test-vite-plus-package-optional@npm:1.0.0 +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +Done in ms ms +{ + "name": "command-remove-yarn4-with-workspace", + "version": "1.0.0", + "packageManager": "yarn@", + "workspaces": [ + "packages/*" + ], + "devDependencies": { + "testnpm2": "^1.0.1" + }, + "dependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} +{ + "name": "app", + "devDependencies": { + "testnpm2": "^1.0.1" + }, + "dependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} +{ + "name": "admin", + "devDependencies": { + "testnpm2": "^1.0.1" + }, + "dependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "devDependencies": { + "testnpm2": "^1.0.1" + }, + "dependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} + +> vp remove testnpm2 -r && cat package.json packages/app/package.json packages/admin/package.json packages/utils/package.json # should remove package from all workspaces and root +Running: yarn remove --all testnpm2 +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0085: │ - testnpm2@npm:1.0.1 +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +{ + "name": "command-remove-yarn4-with-workspace", + "version": "1.0.0", + "packageManager": "yarn@", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} +{ + "name": "app", + "dependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} +{ + "name": "admin", + "dependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "dependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} + +> vp remove -O test-vite-plus-package-optional -r && cat package.json packages/app/package.json packages/admin/package.json packages/utils/package.json # should remove optional package from all workspaces +Running: yarn remove --all test-vite-plus-package-optional +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0085: │ - test-vite-plus-package-optional@npm:1.0.0 +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +{ + "name": "command-remove-yarn4-with-workspace", + "version": "1.0.0", + "packageManager": "yarn@", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} +{ + "name": "app", + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} +{ + "name": "admin", + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} + +> vp remove test-vite-plus-install --filter=app && cat package.json packages/app/package.json packages/admin/package.json packages/utils/package.json # should remove package by filter=app +Running: yarn workspaces foreach --all --include app remove test-vite-plus-install +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +Done in ms ms +{ + "name": "command-remove-yarn4-with-workspace", + "version": "1.0.0", + "packageManager": "yarn@", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} +{ + "name": "app" +} +{ + "name": "admin", + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} + +> vp add test-vite-plus-install --filter=app && vp remove test-vite-plus-install --filter=* && cat package.json packages/app/package.json packages/admin/package.json packages/utils/package.json # should remove package by filter=* +Running: yarn workspaces foreach --all --include app add test-vite-plus-install +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +Done in ms ms +Running: yarn workspaces foreach --all --include * remove test-vite-plus-install +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +Done in ms ms +{ + "name": "command-remove-yarn4-with-workspace", + "version": "1.0.0", + "packageManager": "yarn@", + "workspaces": [ + "packages/*" + ] +} +{ + "name": "app" +} +{ + "name": "admin" +} +{ + "name": "@vite-plus-test/utils", + "version": "1.0.0", + "dependencies": { + "test-vite-plus-install": "^1.0.0" + } +} diff --git a/packages/global/snap-tests/command-remove-yarn4-with-workspace/steps.json b/packages/global/snap-tests/command-remove-yarn4-with-workspace/steps.json new file mode 100644 index 0000000000..56d12de8b8 --- /dev/null +++ b/packages/global/snap-tests/command-remove-yarn4-with-workspace/steps.json @@ -0,0 +1,12 @@ +{ + "env": { + "VITE_DISABLE_AUTO_INSTALL": "1" + }, + "commands": [ + "vp add testnpm2 -D && vp add testnpm2 -D --filter=* --filter=@vite-plus-test/utils && vp add test-vite-plus-install --filter=* --filter=@vite-plus-test/utils && vp add test-vite-plus-package-optional -O --filter=* --filter=@vite-plus-test/utils && cat package.json packages/app/package.json packages/admin/package.json packages/utils/package.json # prepare packages", + "vp remove testnpm2 -r && cat package.json packages/app/package.json packages/admin/package.json packages/utils/package.json # should remove package from all workspaces and root", + "vp remove -O test-vite-plus-package-optional -r && cat package.json packages/app/package.json packages/admin/package.json packages/utils/package.json # should remove optional package from all workspaces", + "vp remove test-vite-plus-install --filter=app && cat package.json packages/app/package.json packages/admin/package.json packages/utils/package.json # should remove package by filter=app", + "vp add test-vite-plus-install --filter=app && vp remove test-vite-plus-install --filter=* && cat package.json packages/app/package.json packages/admin/package.json packages/utils/package.json # should remove package by filter=*" + ] +} diff --git a/packages/global/snap-tests/command-remove-yarn4/package.json b/packages/global/snap-tests/command-remove-yarn4/package.json new file mode 100644 index 0000000000..c1d966d7c8 --- /dev/null +++ b/packages/global/snap-tests/command-remove-yarn4/package.json @@ -0,0 +1,5 @@ +{ + "name": "command-remove-yarn4", + "version": "1.0.0", + "packageManager": "yarn@4.10.3" +} diff --git a/packages/global/snap-tests/command-remove-yarn4/snap.txt b/packages/global/snap-tests/command-remove-yarn4/snap.txt new file mode 100644 index 0000000000..b963980873 --- /dev/null +++ b/packages/global/snap-tests/command-remove-yarn4/snap.txt @@ -0,0 +1,98 @@ +[1]> vp remove testnpm2 -D && cat package.json # should error when remove not exists package +Running: yarn remove testnpm2 +Usage Error: Pattern testnpm2 doesn't match any packages referenced by this workspace + +$ yarn remove [-A,--all] [--mode #0] ... + +> vp add testnpm2 && vp add -D test-vite-plus-install && vp add -O test-vite-plus-package-optional && cat package.json # should add packages to dependencies +Running: yarn add testnpm2 +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0085: │ + testnpm2@npm:1.0.1 +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +Running: yarn add --dev test-vite-plus-install +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0085: │ + test-vite-plus-install@npm:1.0.0 +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +Running: yarn add --optional test-vite-plus-package-optional +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0085: │ + test-vite-plus-package-optional@npm:1.0.0 +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +{ + "name": "command-remove-yarn4", + "version": "1.0.0", + "packageManager": "yarn@", + "dependencies": { + "testnpm2": "^1.0.1" + }, + "devDependencies": { + "test-vite-plus-install": "^1.0.0" + }, + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} + +> vp remove testnpm2 test-vite-plus-install && cat package.json # should remove packages from dependencies +Running: yarn remove testnpm2 test-vite-plus-install +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0085: │ - test-vite-plus-install@npm:1.0.0, testnpm2@npm:1.0.1 +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +{ + "name": "command-remove-yarn4", + "version": "1.0.0", + "packageManager": "yarn@", + "optionalDependencies": { + "test-vite-plus-package-optional": "^1.0.0" + } +} + +> vp remove -D test-vite-plus-package-optional && cat package.json # support ignore -O flag and remove package from optional dependencies +Running: yarn remove test-vite-plus-package-optional +➤ YN0000: · Yarn +➤ YN0000: ┌ Resolution step +➤ YN0085: │ - test-vite-plus-package-optional@npm:1.0.0 +➤ YN0000: └ Completed +➤ YN0000: ┌ Fetch step +➤ YN0000: └ Completed +➤ YN0000: ┌ Link step +➤ YN0000: └ Completed +➤ YN0000: · Done in ms ms +{ + "name": "command-remove-yarn4", + "version": "1.0.0", + "packageManager": "yarn@" +} + +> vp remove -g testnpm2 -- --dry-run && cat package.json # support remove global package with dry-run +Running: npm uninstall --global --dry-run testnpm2 + +up to date in ms +{ + "name": "command-remove-yarn4", + "version": "1.0.0", + "packageManager": "yarn@" +} diff --git a/packages/global/snap-tests/command-remove-yarn4/steps.json b/packages/global/snap-tests/command-remove-yarn4/steps.json new file mode 100644 index 0000000000..306a2e0955 --- /dev/null +++ b/packages/global/snap-tests/command-remove-yarn4/steps.json @@ -0,0 +1,12 @@ +{ + "env": { + "VITE_DISABLE_AUTO_INSTALL": "1" + }, + "commands": [ + "vp remove testnpm2 -D && cat package.json # should error when remove not exists package", + "vp add testnpm2 && vp add -D test-vite-plus-install && vp add -O test-vite-plus-package-optional && cat package.json # should add packages to dependencies", + "vp remove testnpm2 test-vite-plus-install && cat package.json # should remove packages from dependencies", + "vp remove -D test-vite-plus-package-optional && cat package.json # support ignore -O flag and remove package from optional dependencies", + "vp remove -g testnpm2 -- --dry-run && cat package.json # support remove global package with dry-run" + ] +} diff --git a/rfcs/add-remove-package-commands.md b/rfcs/add-remove-package-commands.md index f7d1185c02..c485c02136 100644 --- a/rfcs/add-remove-package-commands.md +++ b/rfcs/add-remove-package-commands.md @@ -166,16 +166,20 @@ vite remove -g typescript # Remove global package #### Remove Command Mapping -| Vite+ Flag | pnpm | yarn | npm | Description | -| ---------------------- | --------------------------- | ------------------------ | ----------------------------- | ---------------------------------------------- | -| `` | `remove ` | `remove ` | `uninstall ` | Remove packages | -| `-D, --save-dev` | `-D` | `--dev` / `-D` | `--save-dev` / `-D` | Only remove from `devDependencies` | -| `-O, --save-optional` | `-O` | `--optional` / `-O` | `--save-optional` / `-O` | Only remove from `optionalDependencies` | -| `-P, --save-prod` | `-P` | `--save-prod` / `-P` | `--save-prod` / `-P` | Only remove from `dependencies` | -| `--filter ` | `--filter remove` | `workspace remove` | `uninstall --workspace ` | Target specific workspace package(s) | -| `-w, --workspace-root` | `-w` | (default) | (default) | Remove from workspace root | -| `-r, --recursive` | `-r` | `--recursive` / `-r` | `--recursive` / `-r` | Remove recursively from all workspace packages | -| `-g, --global` | `-g` | `global remove` | `--global` / `-g` | Remove global packages | +- https://pnpm.io/cli/remove#options +- https://yarnpkg.com/cli/remove#options +- https://docs.npmjs.com/cli/v11/commands/npm-uninstall#description + +| Vite+ Flag | pnpm | yarn | npm | Description | +| ---------------------- | --------------------------- | -------------------------------------------------- | --------------------------------- | ---------------------------------------------- | +| `` | `remove ` | `remove ` | `uninstall ` | Remove packages | +| `-D, --save-dev` | `-D` | N/A | `--save-dev` / `-D` | Only remove from `devDependencies` | +| `-O, --save-optional` | `-O` | N/A | `--save-optional` / `-O` | Only remove from `optionalDependencies` | +| `-P, --save-prod` | `-P` | N/A | `--save-prod` / `-P` | Only remove from `dependencies` | +| `--filter ` | `--filter remove` | `workspaces foreach -A --include remove` | `uninstall --workspace ` | Target specific workspace package(s) | +| `-w, --workspace-root` | `-w` | N/A | `--include-workspace-root` | Remove from workspace root | +| `-r, --recursive` | `-r, --recursive` | `-A, --all` | `--workspaces` | Remove recursively from all workspace packages | +| `-g, --global` | `-g` | N/A | `--global` / `-g` | Remove global packages | **Note**: Similar to add, `--filter` must precede the command for pnpm.