Add vite add and vite remove commands that automatically adapt to the detected package manager (pnpm/yarn/npm) for adding and removing packages, with support for multiple packages, common flags, and workspace-aware operations based on pnpm's API design.
Currently, developers must manually use package manager-specific commands:
pnpm add react
yarn add react
npm install reactThis creates friction in monorepo workflows and requires remembering different syntaxes. A unified interface would:
- Simplify workflows: One command works across all package managers
- Auto-detection: Automatically uses the correct package manager
- Consistency: Same syntax regardless of underlying tool
- Integration: Works seamlessly with existing vite+ features
# Developer needs to know which package manager is used
pnpm add -D typescript # pnpm project
yarn add --dev typescript # yarn project
npm install --save-dev typescript # npm project
# Different remove commands
pnpm remove lodash
yarn remove lodash
npm uninstall lodash# Works for all package managers
vite add typescript -D
vite remove lodash
# Multiple packages
vite add react react-dom
vite remove axios lodash
# Workspace operations
vite add react --filter app
vite add @myorg/utils --workspace --filter app
vite add lodash -w # Add to workspace rootvite add <PACKAGES>... [OPTIONS]Examples:
# Add production dependency
vite add react react-dom
# Add dev dependency
vite add -D typescript @types/react
# Add with exact version
vite add react -E
# Add peer dependency
vite add --save-peer react
# Add optional dependency
vite add -O sharp
# Workspace operations
vite add react --filter app # Add to specific package
vite add @myorg/utils --workspace --filter app # Add workspace dependency
vite add lodash -w # Add to workspace root
vite add react --filter "app*" # Add to multiple packages (pattern)
vite add utils --filter "!@myorg/core" # Exclude packagesTo accommodate the user habits and experience of npm install <PACKAGES>…, vite install <PACKAGES>... will be specially treated as an alias for the add command.
The following commands will be automatically converted to the add command for processing:
vite install <PACKAGES>... [OPTIONS]
-> vite add <PACKAGES>... [OPTIONS]For global packages, we will use npm cli only.
Because yarn do not support global packages install on version>=2.x, and pnpm global install has some bugs like
wrong bin fileissues.
vite install -g <PACKAGES>...
vite add -g <PACKAGES>...
-> npm install -g <PACKAGES>...vite remove <PACKAGES>... [OPTIONS]
vite rm <PACKAGES>... [OPTIONS] # AliasExamples:
# Remove packages
vite remove lodash axios
# Remove dev dependency
vite rm typescript
# Alias support
vite rm old-package
# Workspace operations
vite remove lodash --filter app # Remove from specific package
vite rm utils --filter "app*" # Remove from multiple packages
vite remove -g typescript # Remove global package- https://pnpm.io/cli/add#options
- https://yarnpkg.com/cli/add#options
- https://docs.npmjs.com/cli/v11/commands/npm-install#description
| Vite+ Flag | pnpm | yarn | npm | Description |
|---|---|---|---|---|
<packages> |
add <packages> |
add <packages> |
install <packages> |
Add packages |
--filter <pattern> |
--filter <pattern> add |
workspaces foreach -A --include <pattern> add |
install --workspace <pattern> |
Target specific workspace package(s) |
-w, --workspace-root |
-w |
-W for v1, v2+ N/A |
--include-workspace-root |
Add to workspace root (ignore-workspace-root-check) |
--workspace |
--workspace |
N/A | N/A | Only add if package exists in workspace (pnpm-specific) |
-P, --save-prod |
--save-prod / -P |
N/A | --save-prod / -P |
Save to dependencies. The default behavior |
-D, --save-dev |
-D |
--dev / -D |
--save-dev / -D |
Save to devDependencies |
--save-peer |
--save-peer |
--peer / -P |
--save-peer |
Save to peerDependencies and devDependencies |
-O, --save-optional |
-O |
--optional / -O |
--save-optional / -O |
Save to optionalDependencies |
-E, --save-exact |
-E |
--exact / -E |
--save-exact / -E |
Save exact version |
-g, --global |
-g |
global add |
--global / -g |
Install globally |
--save-catalog |
pnpm@10+ only | N/A | N/A | Save the new dependency to the default catalog |
--save-catalog-name <catalog_name> |
pnpm@10+ only | N/A | N/A | Save the new dependency to the specified catalog |
--allow-build <names> |
pnpm@10+ only | N/A | N/A | A list of package names allowed to run postinstall |
Note: For pnpm, --filter must come before the command (e.g., pnpm --filter app add react). For yarn/npm, it's integrated into the command structure.
- 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 |
|---|---|---|---|---|
<packages> |
remove <packages> |
remove <packages> |
uninstall <packages> |
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 <pattern> |
--filter <pattern> remove |
workspaces foreach -A --include <pattern> remove |
uninstall --workspace <pattern> |
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.
Aliases:
vite rm=vite removevite un=vite removevite uninstall=vite remove
Based on pnpm's filter syntax:
| Pattern | Description | Example |
|---|---|---|
<pkg-name> |
Exact package name | --filter app |
<pattern>* |
Wildcard match | --filter "app*" matches app, app-web |
@<scope>/* |
Scope match | --filter "@myorg/*" |
!<pattern> |
Exclude pattern | --filter "!test*" excludes test packages |
<pkg>... |
Package and dependencies | --filter "app..." |
...<pkg> |
Package and dependents | --filter "...utils" |
Multiple Filters:
vite add react --filter app --filter web # Add to both app and web
vite add react --filter "app*" --filter "!app-test" # Add to app* except app-testAdditional parameters not covered by Vite+ can all be handled through pass-through arguments.
All arguments after -- will be passed through to the package manager.
vite add react --allow-build=react,napi -- --use-stderr
-> pnpm add --allow-build=react,napi --use-stderr react
-> yarn add --use-stderr react
-> npm install --use-stderr reactFile: crates/vite_task/src/lib.rs
Add new command variants:
#[derive(Subcommand, Debug)]
pub enum Commands {
// ... existing commands
/// Add packages to dependencies
#[command(disable_help_flag = true)]
Add {
/// Packages to add
packages: Vec<String>,
/// Filter packages in monorepo (can be used multiple times)
#[arg(long, value_name = "PATTERN")]
filter: Vec<String>,
/// Add to workspace root (ignore-workspace-root-check)
#[arg(short = 'w', long)]
workspace_root: bool,
/// Only add if package exists in workspace
#[arg(long)]
workspace: bool,
/// Arguments to pass to package manager
#[arg(allow_hyphen_values = true, trailing_var_arg = true)]
args: Vec<String>,
},
/// Remove packages from dependencies
#[command(disable_help_flag = true, alias = "rm", alias = "un", alias = "uninstall")]
Remove {
/// Packages to remove
packages: Vec<String>,
/// Filter packages in monorepo (can be used multiple times)
#[arg(long, value_name = "PATTERN")]
filter: Vec<String>,
/// Remove from workspace root
#[arg(short = 'w', long)]
workspace_root: bool,
/// Arguments to pass to package manager
#[arg(allow_hyphen_values = true, trailing_var_arg = true)]
args: Vec<String>,
},
}File: crates/vite_package_manager/src/package_manager.rs
Add methods to translate commands:
impl PackageManager {
/// Resolve add command for the package manager
pub fn resolve_add_command(&self) -> &'static str {
match self.client {
PackageManagerType::Pnpm => "add",
PackageManagerType::Yarn => "add",
PackageManagerType::Npm => "install",
}
}
/// Resolve remove command for the package manager
pub fn resolve_remove_command(&self) -> &'static str {
match self.client {
PackageManagerType::Pnpm => "remove",
PackageManagerType::Yarn => "remove",
PackageManagerType::Npm => "uninstall",
}
}
/// Build command arguments with workspace support
pub fn build_add_args(
&self,
packages: &[String],
filters: &[String],
workspace_root: bool,
workspace_only: bool,
extra_args: &[String],
) -> Vec<String> {
let mut args = Vec::new();
match self.client {
PackageManagerType::Pnpm => {
// pnpm: --filter must come before command
for filter in filters {
args.push("--filter".to_string());
args.push(filter.clone());
}
args.push("add".to_string());
args.extend_from_slice(packages);
if workspace_root {
args.push("-w".to_string());
}
if workspace_only {
args.push("--workspace".to_string());
}
args.extend_from_slice(extra_args);
}
PackageManagerType::Yarn => {
// yarn: workspace <pkg> add
if !filters.is_empty() {
// yarn workspace <name> add
for filter in filters {
args.push("workspace".to_string());
args.push(filter.clone());
}
}
args.push("add".to_string());
args.extend_from_slice(packages);
if workspace_root {
args.push("-W".to_string());
}
args.extend_from_slice(extra_args);
}
PackageManagerType::Npm => {
// npm: --workspace must come before install
if !filters.is_empty() {
for filter in filters {
args.push("--workspace".to_string());
args.push(filter.clone());
}
}
args.push("install".to_string());
args.extend_from_slice(packages);
if workspace_root {
args.push("-w".to_string());
}
args.extend_from_slice(extra_args);
}
}
args
}
/// Build remove command arguments with workspace support
pub fn build_remove_args(
&self,
packages: &[String],
filters: &[String],
workspace_root: bool,
extra_args: &[String],
) -> Vec<String> {
let mut args = Vec::new();
match self.client {
PackageManagerType::Pnpm => {
for filter in filters {
args.push("--filter".to_string());
args.push(filter.clone());
}
args.push("remove".to_string());
args.extend_from_slice(packages);
if workspace_root {
args.push("-w".to_string());
}
args.extend_from_slice(extra_args);
}
PackageManagerType::Yarn => {
if !filters.is_empty() {
for filter in filters {
args.push("workspace".to_string());
args.push(filter.clone());
}
}
args.push("remove".to_string());
args.extend_from_slice(packages);
args.extend_from_slice(extra_args);
}
PackageManagerType::Npm => {
if !filters.is_empty() {
for filter in filters {
args.push("--workspace".to_string());
args.push(filter.clone());
}
}
args.push("uninstall".to_string());
args.extend_from_slice(packages);
args.extend_from_slice(extra_args);
}
}
args
}
}File: crates/vite_task/src/add.rs (new file)
pub struct AddCommand {
workspace_root: AbsolutePathBuf,
}
impl AddCommand {
pub fn new(workspace_root: AbsolutePathBuf) -> Self {
Self { workspace_root }
}
pub async fn execute(
self,
packages: Vec<String>,
filters: Vec<String>,
workspace_root: bool,
workspace_only: bool,
extra_args: Vec<String>,
) -> Result<ExecutionSummary, Error> {
let package_manager = PackageManager::builder(&self.workspace_root).build().await?;
let workspace = Workspace::partial_load(self.workspace_root)?;
let resolve_command = package_manager.resolve_command();
// Build command with workspace support
let full_args = package_manager.build_add_args(
&packages,
&filters,
workspace_root,
workspace_only,
&extra_args,
);
let resolved_task = ResolvedTask::resolve_from_builtin_with_command_result(
&workspace,
"add",
full_args.iter().map(String::as_str),
ResolveCommandResult {
bin_path: resolve_command.bin_path,
envs: resolve_command.envs,
},
false,
)?;
let mut task_graph: StableGraph<ResolvedTask, ()> = Default::default();
task_graph.add_node(resolved_task);
let summary = ExecutionPlan::plan(task_graph, false)?.execute(&workspace).await?;
workspace.unload().await?;
Ok(summary)
}
}File: crates/vite_task/src/remove.rs (new file)
pub struct RemoveCommand {
workspace_root: AbsolutePathBuf,
}
impl RemoveCommand {
pub fn new(workspace_root: AbsolutePathBuf) -> Self {
Self { workspace_root }
}
pub async fn execute(
self,
packages: Vec<String>,
filters: Vec<String>,
workspace_root: bool,
extra_args: Vec<String>,
) -> Result<ExecutionSummary, Error> {
let package_manager = PackageManager::builder(&self.workspace_root).build().await?;
let workspace = Workspace::partial_load(self.workspace_root)?;
let resolve_command = package_manager.resolve_command();
// Build command with workspace support
let full_args = package_manager.build_remove_args(
&packages,
&filters,
workspace_root,
&extra_args,
);
let resolved_task = ResolvedTask::resolve_from_builtin_with_command_result(
&workspace,
"remove",
full_args.iter().map(String::as_str),
ResolveCommandResult {
bin_path: resolve_command.bin_path,
envs: resolve_command.envs,
},
false,
)?;
let mut task_graph: StableGraph<ResolvedTask, ()> = Default::default();
task_graph.add_node(resolved_task);
let summary = ExecutionPlan::plan(task_graph, false)?.execute(&workspace).await?;
workspace.unload().await?;
Ok(summary)
}
}Yarn requires different command structure for global operations:
// pnpm/npm: <bin> add -g <package>
// yarn: <bin> global add <package>
fn handle_global_flag(args: &[String], pm_type: PackageManagerType) -> (Vec<String>, bool) {
let has_global = args.contains(&"-g".to_string()) || args.contains(&"--global".to_string());
let filtered_args: Vec<String> = args.iter()
.filter(|a| *a != "-g" && *a != "--global")
.cloned()
.collect();
(filtered_args, has_global)
}pnpm uses --filter before command, yarn/npm use different approaches:
fn build_workspace_command(
pm_type: PackageManagerType,
filters: &[String],
operation: &str,
packages: &[String],
) -> Vec<String> {
match pm_type {
PackageManagerType::Pnpm => {
// pnpm --filter <pkg> add <deps>
let mut args = Vec::new();
for filter in filters {
args.push("--filter".to_string());
args.push(filter.clone());
}
args.push(operation.to_string());
args.extend_from_slice(packages);
args
}
PackageManagerType::Yarn => {
// yarn workspace <pkg> add <deps>
let mut args = Vec::new();
if let Some(filter) = filters.first() {
args.push("workspace".to_string());
args.push(filter.clone());
}
args.push(operation.to_string());
args.extend_from_slice(packages);
args
}
PackageManagerType::Npm => {
// npm install <deps> --workspace <pkg>
let mut args = vec![operation.to_string()];
args.extend_from_slice(packages);
for filter in filters {
args.push("--workspace".to_string());
args.push(filter.clone());
}
args
}
}
}When adding workspace dependencies with --workspace flag:
# pnpm: Adds with workspace: protocol
vite add @myorg/utils --workspace --filter app
# → pnpm --filter app add @myorg/utils --workspace
# → Adds: "@myorg/utils": "workspace:*"
# Without --workspace: Tries to install from registry
vite add @myorg/utils --filter app
# → pnpm --filter app add @myorg/utils
# → Tries npm registry (may fail if not published)Decision: Do not cache add/remove operations.
Rationale:
- These commands modify package.json and lockfiles
- Side effects make caching inappropriate
- Each execution should run fresh
- Similar to how
vite installworks
Implementation: Set cacheable: false or skip cache entirely.
Decision: Pass all arguments after packages directly to package manager.
Rationale:
- Package managers have many flags (40+ for npm)
- Maintaining complete flag mapping is error-prone
- Pass-through allows accessing all features
- Only translate critical command name differences
Example:
vite add react --save-exact
# → pnpm add react --save-exact
# → yarn add react --save-exact
# → npm install react --save-exactDecision: Only explicitly support most common flags with automatic translation.
Common Flags:
-D, --save-dev- universally supported-g, --global- needs special handling for yarn-E, --save-exact- universally supported-P, --save-peer- universally supported-O, --save-optional- universally supported
Advanced Flags: Pass through as-is
Decision: Support multiple aliases for remove command.
Aliases:
vite remove(primary)vite rm(short)vite un(short, matches pnpm)vite uninstall(explicit, matches npm)
Rationale: Matches user expectations from other tools.
Decision: Allow specifying multiple packages in single command.
Example:
vite add react react-dom @types/react -D
vite remove lodash axios underscoreImplementation: Packages are positional arguments before flags.
$ vite add
Error: No packages specified
Usage: vite add <PACKAGES>... [OPTIONS]$ vite add react
Error: No package manager detected
Please run one of:
- vite install (to set up package manager)
- Add packageManager field to package.jsonLet the underlying package manager handle validation and provide clear errors.
$ vite add react react-dom
Detected package manager: pnpm@10.15.0
Running: pnpm add react react-dom
WARN deprecated inflight@1.0.6: ...
Packages: +2
++
Progress: resolved 150, reused 140, downloaded 10, added 2, done
dependencies:
+ react 18.3.1
+ react-dom 18.3.1
Done in 2.3s$ vite add invalid-package-that-does-not-exist
Detected package manager: pnpm@10.15.0
Running: pnpm add invalid-package-that-does-not-exist
ERR_PNPM_FETCH_404 GET https://registry.npmjs.org/invalid-package-that-does-not-exist: Not Found - 404
This error happened while installing the dependencies of undefined@undefined
Error: Command failed with exit code 1Translate all flags to package manager-specific equivalents:
vite add react --dev
# → pnpm add react -D
# → yarn add react --dev
# → npm install react --save-devRejected because:
- Maintenance burden (40+ npm flags)
- Package managers evolve with new flags
- Pass-through is simpler and more flexible
- Users can use native flags directly
vite pnpm:add react
vite yarn:add react
vite npm:install reactRejected because:
- Defeats purpose of unified interface
- More verbose
- Doesn't leverage auto-detection
Prompt for packages and options interactively:
$ vite add
? Which packages to add? react
? Add as dev dependency? YesRejected for initial version:
- Slower for experienced users
- Not scriptable
- Can be added later as optional mode
- Add
AddandRemovecommand variants toCommandsenum - Create
add.rsandremove.rsmodules - Implement package manager command resolution
- Add basic error handling
- Handle yarn global commands differently
- Validate package names (optional)
- Support workspace-specific operations
- Unit tests for command resolution
- Integration tests with mock package managers
- Manual testing with real package managers
- Update CLI documentation
- Add examples to README
- Document flag compatibility matrix
#[test]
fn test_add_command_resolution() {
let pm = PackageManager::mock(PackageManagerType::Pnpm);
assert_eq!(pm.resolve_add_command(), "add");
let pm = PackageManager::mock(PackageManagerType::Npm);
assert_eq!(pm.resolve_add_command(), "install");
}
#[test]
fn test_remove_command_resolution() {
let pm = PackageManager::mock(PackageManagerType::Pnpm);
assert_eq!(pm.resolve_remove_command(), "remove");
let pm = PackageManager::mock(PackageManagerType::Npm);
assert_eq!(pm.resolve_remove_command(), "uninstall");
}
#[test]
fn test_build_add_args_pnpm() {
let pm = PackageManager::mock(PackageManagerType::Pnpm);
let args = pm.build_add_args(
&["react".to_string()],
&["app".to_string()],
false,
false,
&[],
);
assert_eq!(args, vec!["--filter", "app", "add", "react"]);
}
#[test]
fn test_build_add_args_with_workspace_root() {
let pm = PackageManager::mock(PackageManagerType::Pnpm);
let args = pm.build_add_args(
&["typescript".to_string()],
&[],
true, // workspace_root
false,
&["-D".to_string()],
);
assert_eq!(args, vec!["add", "typescript", "-w", "-D"]);
}
#[test]
fn test_build_add_args_yarn_workspace() {
let pm = PackageManager::mock(PackageManagerType::Yarn);
let args = pm.build_add_args(
&["react".to_string()],
&["app".to_string()],
false,
false,
&[],
);
assert_eq!(args, vec!["workspace", "app", "add", "react"]);
}
#[test]
fn test_build_remove_args_with_filter() {
let pm = PackageManager::mock(PackageManagerType::Pnpm);
let args = pm.build_remove_args(
&["lodash".to_string()],
&["utils".to_string()],
false,
&[],
);
assert_eq!(args, vec!["--filter", "utils", "remove", "lodash"]);
}Create fixtures for testing with each package manager:
fixtures/add-remove-test/
pnpm-workspace.yaml
package.json
packages/
app/
package.json
utils/
package.json
test-steps.json
Test cases:
- Add single package
- Add multiple packages
- Add with -D flag
- Add with --filter to specific package
- Add with --filter wildcard pattern
- Add to workspace root with -w
- Add workspace dependency with --workspace
- Remove single package
- Remove multiple packages
- Remove with --filter
- Error handling for invalid packages
- Error handling for incompatible filters on yarn/npm
$ vite add --help
Add packages to dependencies
Usage: vite add <PACKAGES>... [OPTIONS]
Arguments:
<PACKAGES>... Packages to add
Options:
--filter <PATTERN> Filter packages in monorepo (can be used multiple times)
-w, --workspace-root Add to workspace root (ignore-workspace-root-check)
--workspace Only add if package exists in workspace
-D, --save-dev Add as dev dependency
-P, --save-peer Add as peer dependency
-O, --save-optional Add as optional dependency
-E, --save-exact Save exact version
-g, --global Install globally
-h, --help Print help
Filter Patterns:
<name> Exact package name match
<pattern>* Wildcard match (pnpm only)
@<scope>/* Scope match (pnpm only)
!<pattern> Exclude pattern (pnpm only)
<pkg>... Package and dependencies (pnpm only)
...<pkg> Package and dependents (pnpm only)
Examples:
vite add react react-dom
vite add -D typescript @types/react
vite add react --filter app
vite add react --filter "app*" --filter "!app-test"
vite add @myorg/utils --workspace --filter web
vite add lodash -w$ vite remove --help
Remove packages from dependencies
Usage: vite remove <PACKAGES>... [OPTIONS]
Aliases: rm, un, uninstall
Arguments:
<PACKAGES>... Packages to remove
Options:
--filter <PATTERN> Filter packages in monorepo (can be used multiple times)
-w, --workspace-root Remove from workspace root
-g, --global Remove global packages
-h, --help Print help
Filter Patterns:
<name> Exact package name match
<pattern>* Wildcard match (pnpm only)
@<scope>/* Scope match (pnpm only)
!<pattern> Exclude pattern (pnpm only)
Examples:
vite remove lodash
vite remove axios underscore lodash
vite rm lodash --filter app
vite remove utils --filter "app*"
vite rm old-package- No Caching: Operations run directly without cache overhead
- Single Execution: Unlike task runner, these are one-off operations
- Pass-Through: Minimal processing, just command translation
- Auto-Detection: Reuses existing package manager detection (already cached)
- Package Name Validation: Let package manager handle validation
- Lockfile Integrity: Package manager ensures integrity
- No Code Execution: Just passes through to trusted package manager
- Audit Flags: Users can add
--auditvia pass-through
This is a new feature with no breaking changes:
- Existing commands unaffected
- New commands are additive
- No changes to task configuration
- No changes to caching behavior
Users can start using immediately:
# Old way
pnpm add react
# New way (works with any package manager)
vite add reactAdd to:
- CLI help output
- Documentation
- VSCode extension suggestions
- Shell completions
Add to CLI documentation:
### Adding Packages
```bash
vite add <packages>... [OPTIONS]
```Automatically uses the detected package manager (pnpm/yarn/npm).
Basic Examples:
vite add react- Add production dependencyvite add -D typescript- Add dev dependencyvite add react react-dom- Add multiple packages
Workspace Examples:
vite add react --filter app- Add to specific packagevite add react --filter "app*"- Add to multiple packages (pnpm)vite add @myorg/utils --workspace --filter web- Add workspace dependencyvite add lodash -w- Add to workspace root
Common Options:
--filter <pattern>- Target specific workspace package(s)-w, --workspace-root- Add to workspace root--workspace- Add workspace dependency (pnpm)-D, --save-dev- Add as dev dependency-E, --save-exact- Save exact version-P, --save-peer- Add as peer dependency-O, --save-optional- Add as optional dependency-g, --global- Install globally
vite remove <packages>... [OPTIONS]
vite rm <packages>... [OPTIONS]Aliases: rm, un, uninstall
Basic Examples:
vite remove lodash- Remove packagevite rm axios underscore- Remove multiple packages
Workspace Examples:
vite remove lodash --filter app- Remove from specific packagevite rm utils --filter "app*"- Remove from multiple packages (pnpm)vite remove -g typescript- Remove global package
Options:
--filter <pattern>- Target specific workspace package(s)-w, --workspace-root- Remove from workspace root-g, --global- Remove global packages
### Package Manager Compatibility
Document flag support matrix:
| Flag | pnpm | yarn | npm |
|------|------|------|-----|
| `-D` | ✅ | ✅ | ✅ |
| `-E` | ✅ | ✅ | ✅ |
| `-P` | ✅ | ✅ | ✅ |
| `-O` | ✅ | ✅ | ✅ |
| `-g` | ✅ | ⚠️ (use global) | ✅ |
## Workspace Operations Deep Dive
### Filter Patterns (pnpm-inspired)
Following pnpm's filter API:
**Exact Match:**
```bash
vite add react --filter app
# → pnpm --filter app add react
Wildcard Patterns:
vite add react --filter "app*"
# → pnpm --filter "app*" add react
# Matches: app, app-web, app-mobileScope Patterns:
vite add lodash --filter "@myorg/*"
# → pnpm --filter "@myorg/*" add lodash
# Matches all packages in @myorg scopeExclusion Patterns:
vite add react --filter "!test*"
# → pnpm --filter "!test*" add react
# Adds to all packages EXCEPT those starting with testMultiple Filters:
vite add react --filter app --filter web
# → pnpm --filter app --filter web add react
# Adds to both app AND web packagesDependency Selectors:
# Add to package and all its dependencies
vite add lodash --filter "app..."
# → pnpm --filter "app..." add lodash
# Add to package and all its dependents
vite add utils --filter "...core"
# → pnpm --filter "...core" add utilsAdd dependencies to workspace root (requires special flag):
vite add -D typescript -w
# → pnpm add -D typescript -w (pnpm)
# → yarn add -D typescript -W (yarn)
# → npm install -D typescript -w (npm)Why needed: By default, package managers prevent adding to workspace root to encourage proper package structure.
For internal monorepo dependencies:
# Add workspace dependency with workspace: protocol
vite add @myorg/utils --workspace --filter app
# → pnpm --filter app add @myorg/utils --workspace
# → Adds: "@myorg/utils": "workspace:*"
# Specify version
vite add "@myorg/utils@workspace:^" --filter app
# → Adds: "@myorg/utils": "workspace:^"| Feature | pnpm | yarn | npm | Notes |
|---|---|---|---|---|
--filter <pattern> |
✅ Native | workspace <name> |
--workspace <name> |
Syntax differs |
| Multiple filters | ✅ Repeatable flag | ❌ Single only | pnpm most flexible | |
| Wildcard patterns | ✅ Full support | ❌ No wildcards | pnpm best | |
Exclusion ! |
✅ Supported | ❌ Not supported | ❌ Not supported | pnpm only |
Dependency selectors ... |
✅ Supported | ❌ Not supported | ❌ Not supported | pnpm only |
-w (root) |
✅ -w |
✅ -W |
✅ -w |
Slightly different flags |
--workspace protocol |
✅ Supported | ❌ Manual | ❌ Manual | pnpm feature |
Graceful Degradation:
- Advanced pnpm features (wildcard, exclusion, selectors) will error on yarn/npm with helpful message
- Basic
--filter <exact-name>works across all package managers
Implement wildcard translation for yarn/npm:
vite add react --filter "app*"
# → For yarn: Run `yarn workspace app add react` for each matching package
# → For npm: Run `npm install react --workspace app` for each matching packageReferer to ni's interactive mode https://github.com/antfu-collective/ni
$ vite add --interactive
? Select for package > tsdown
❯ tsdown v0.15.7 - git+https://github.com/rolldown/tsdown.git
tsdown-config-silverwind v1.4.0 - git+https://github.com/silverwind/tsdown-config-silverwind.git
@storm-software/tsdown v0.45.0 - git+https://github.com/storm-software/storm-ops.git
create-tsdown v0.15.7 - git+https://github.com/rolldown/tsdown.git
shadcn-auv v0.0.1 - git+https://github.com/ohojs/shadcn-auv.git
ts-build-wizard v1.0.3 - git+https://github.com/Alireza-Tabatabaeian/react-app-registry.git
vite-plugin-shadcn-registry v0.0.6 - git+https://github.com/myshkouski/vite-plugin-shadcn-registry.git
@qds.dev/tools v0.3.3 - https://www.npmjs.com/package/@qds.dev/tools
feishu-bot-notify v0.1.3 - git+https://github.com/duowb/feishu-bot-notify.git
@memo28.pro/bundler v0.0.2 - https://www.npmjs.com/package/@memo28.pro/bundler
tsdown-jsr-exports-lint v0.1.4 - git+https://github.com/kazupon/tsdown-jsr-exports-lint.git
@miloas/tsdown v0.13.0 - git+https://github.com/rolldown/tsdown.git
@socket-synced-state/server v0.0.9 - https://www.npmjs.com/package/@socket-synced-state/server
@gamedev-sensei/tsdown-config v2.0.1 - git+ssh://git@github.com/gamedev-sensei/package-extras.git
↓ 0xpresc-test v0.1.0 - https://www.npmjs.com/package/0xpresc-test
? install tsdown as › - Use arrow-keys. Return to submit.
❯ prod
dev
peervite upgrade react
vite upgrade --latest
vite upgrade --interactive$ vite add react
Adding react...
💡 Suggestion: Install @types/react for TypeScript support?
Run: vite add -D @types/react$ vite add react
Analyzing dependency impact...
Will add:
react@18.3.1 (85KB)
+ scheduler@0.23.0 (5KB)
Total size: 90KB
Proceed? (Y/n)-
Should we warn about peer dependency conflicts?
- Proposed: Let package manager handle warnings
- Can be enhanced later with custom warnings
-
Should we support version specifiers?
- Proposed: Yes, pass through to package manager
- Example:
vite add react@18.2.0
-
Should we support scoped package shortcuts?
- Proposed: No special handling, pass through as-is
- Example:
vite add @types/reactworks naturally
-
Should we prevent adding to wrong dependency types?
- Proposed: No validation, trust package manager
- Package managers handle this well already
-
How to handle pnpm-specific filter features on yarn/npm?
- Proposed: For wildcards/exclusions on yarn/npm:
- Option A: Error with clear message explaining pnpm-only feature
- Option B: Resolve wildcard ourselves and run command for each package
- Recommendation: Start with Option A, add Option B later
- Proposed: For wildcards/exclusions on yarn/npm:
-
Should we support workspace protocol configuration?
- Proposed: Pass through to pnpm, document in .npmrc for users
- Example:
save-workspace-protocol=rollingin .npmrc - vite+ doesn't need to handle this explicitly
-
Should we validate that filtered packages exist?
- Proposed: Let package manager validate
- Clearer error messages from native tools
- Avoids duplicating workspace parsing logic
- Adoption: % of users using
vite add/removevs direct package manager - Error Rate: Track command failures vs package manager direct usage
- User Feedback: Survey/issues about command ergonomics
- Performance: Measure overhead vs direct package manager calls (<100ms target)
- Week 1: Core implementation (command parsing, package manager adapter)
- Week 2: Testing (unit tests, integration tests)
- Week 3: Documentation and examples
- Week 4: Review, polish, and release
None required - leverages existing:
vite_package_manager- package manager detectionclap- command parsing- Existing task execution infrastructure
crates/vite_task/src/lib.rs- Add command enum variantscrates/vite_task/src/add.rs- New filecrates/vite_task/src/remove.rs- New filecrates/vite_package_manager/src/package_manager.rs- Add command resolution methodsdocs/cli.md- Documentation updates
- ✅ Basic add/remove without filters
- ✅ Multiple package support
- ✅ Auto package manager detection
- ✅ Common flags (-D, -E, -P, -O)
- ✅
--filter <exact-name>for all package managers - ✅
-wflag for workspace root - ✅
--workspaceflag for workspace dependencies (pnpm) - ✅ Wildcard patterns
*(pnpm only, error on others) - ✅ Scope patterns
@scope/*(pnpm only)
- Exclusion patterns
!<pattern>(pnpm only) - Dependency selectors
...(pnpm only) - Multiple filter support
- Graceful degradation for yarn/npm
- Wildcard resolution for yarn/npm
- Run filtered command for each matching package
- Unified behavior across all package managers
# Add React to all frontend packages
vite add react react-dom --filter "@myorg/app-*"
# Add testing library to all packages
vite add -D vitest --filter "*"
# Add shared utils to app packages (workspace dependency)
vite add @myorg/shared-utils --workspace --filter "@myorg/app-*"
# Remove deprecated package from all packages
vite remove moment --filter "*"
# Add TypeScript to workspace root (shared config)
vite add -D typescript @types/node -w# Clone new monorepo
git clone <repo>
vite install
# Add new feature dependencies to web app
cd packages/web
vite add axios react-query
# Add development tool to specific package
vite add -D webpack-bundle-analyzer --filter web
# Remove unused dependencies from utils package
vite rm lodash underscore --filter utils
# Add workspace package as dependency
vite add @myorg/ui-components --workspace --filter web# Before (package manager specific)
pnpm --filter app add react
yarn workspace app add react
npm install react --workspace app
# After (unified)
vite add react --filter appThis RFC proposes adding vite add and vite remove commands to provide a unified interface for package management across pnpm/yarn/npm. The design:
- ✅ Automatically adapts to detected package manager
- ✅ Supports multiple packages in single command
- ✅ Full workspace support following pnpm's API design
- ✅ Filter patterns for targeting specific packages
- ✅ Workspace root and workspace protocol support
- ✅ Uses pass-through for maximum flexibility
- ✅ No caching overhead (as requested)
- ✅ Simple implementation leveraging existing infrastructure
- ✅ Graceful degradation for package manager-specific features
- ✅ Extensible for future enhancements
The implementation follows pnpm's battle-tested workspace API design while providing graceful degradation for yarn/npm users. This provides immediate value to monorepo developers with a unified, intuitive interface.