Add vp install command (alias: vp i) that automatically adapts to the detected package manager (pnpm/yarn/npm/bun) for installing all dependencies in a project, with support for common flags and workspace-aware operations based on pnpm's API design.
Currently, developers must manually use package manager-specific commands:
pnpm install
yarn install
npm installThis 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 install --frozen-lockfile # pnpm project
yarn install --frozen-lockfile # yarn project (v1) or --immutable (v2+)
npm ci # npm project (clean install)
bun install --frozen-lockfile # bun project
# Different flags for production install
pnpm install --prod
yarn install --production
npm install --omit=dev# Works for all package managers
vp install
vp i
# With flags
vp install --frozen-lockfile
vp install --prod
vp install --ignore-scripts
# Workspace operations
vp install --filter appvp install [OPTIONS]
vp i [OPTIONS]Examples:
# Install all dependencies
vp install
vp i
# Production install (no devDependencies)
vp install --prod
vp install -P
# Frozen lockfile (CI mode)
vp install --frozen-lockfile
# Prefer offline (use cache when available)
vp install --prefer-offline
# Force reinstall
vp install --force
# Ignore scripts
vp install --ignore-scripts
# Workspace operations
vp install --filter app # Install for specific package| Option | Short | Description |
|---|---|---|
--prod |
-P |
Do not install devDependencies |
--dev |
-D |
Only install devDependencies |
--no-optional |
Do not install optionalDependencies | |
--frozen-lockfile |
Fail if lockfile needs to be updated | |
--no-frozen-lockfile |
Allow lockfile updates (opposite of --frozen-lockfile) | |
--lockfile-only |
Only update lockfile, don't install | |
--prefer-offline |
Use cached packages when available | |
--offline |
Only use packages already in cache | |
--force |
-f |
Force reinstall all dependencies |
--ignore-scripts |
Do not run lifecycle scripts | |
--no-lockfile |
Don't read or generate lockfile | |
--fix-lockfile |
Fix broken lockfile entries | |
--shamefully-hoist |
Create flat node_modules (pnpm) | |
--resolution-only |
Re-run resolution for peer dependency analysis | |
--silent |
Suppress output (silent mode) | |
--filter <pattern> |
Filter packages in monorepo | |
--workspace-root |
-w |
Install in workspace root only |
--save-exact |
-E |
Save exact version (only when adding packages) |
--save-peer |
Save to peerDependencies (only when adding packages) | |
--save-optional |
-O |
Save to optionalDependencies (only when adding packages) |
--save-catalog |
Save to default catalog (only when adding packages) | |
--global |
-g |
Install globally (only when adding packages) |
- https://pnpm.io/cli/install
- https://yarnpkg.com/cli/install
- https://classic.yarnpkg.com/en/docs/cli/install
- https://docs.npmjs.com/cli/v11/commands/npm-install
- https://bun.sh/docs/cli/install
| Vite+ Flag | pnpm | yarn@1 | yarn@2+ | npm | bun | Description |
|---|---|---|---|---|---|---|
vp install |
pnpm install |
yarn install |
yarn install |
npm install |
bun install |
Install all dependencies |
--prod, -P |
--prod |
--production |
N/A (use .yarnrc.yml) |
--omit=dev |
--production |
Skip devDependencies |
--dev, -D |
--dev |
N/A | N/A | --include=dev --omit=prod |
N/A | Only devDependencies |
--no-optional |
--no-optional |
--ignore-optional |
N/A | --omit=optional |
--omit optional |
Skip optionalDependencies |
--frozen-lockfile |
--frozen-lockfile |
--frozen-lockfile |
--immutable |
ci (use npm ci) |
--frozen-lockfile |
Fail if lockfile outdated |
--no-frozen-lockfile |
--no-frozen-lockfile |
--no-frozen-lockfile |
--no-immutable |
install (not ci) |
--no-frozen-lockfile |
Allow lockfile updates |
--lockfile-only |
--lockfile-only |
N/A | --mode update-lockfile |
--package-lock-only |
--lockfile-only |
Only update lockfile |
--prefer-offline |
--prefer-offline |
--prefer-offline |
N/A | --prefer-offline |
N/A | Prefer cached packages |
--offline |
--offline |
--offline |
N/A | --offline |
N/A | Only use cache |
--force, -f |
--force |
--force |
N/A | --force |
--force |
Force reinstall |
--ignore-scripts |
--ignore-scripts |
--ignore-scripts |
--mode skip-build |
--ignore-scripts |
--ignore-scripts |
Skip lifecycle scripts |
--no-lockfile |
--no-lockfile |
--no-lockfile |
N/A | --no-package-lock |
N/A | Skip lockfile |
--fix-lockfile |
--fix-lockfile |
N/A | --refresh-lockfile |
N/A | N/A | Fix broken lockfile entries |
--shamefully-hoist |
--shamefully-hoist |
N/A | N/A | N/A | N/A (hoisted by default) | Flat node_modules (pnpm) |
--resolution-only |
--resolution-only |
N/A | N/A | N/A | N/A | Re-run resolution only (pnpm) |
--silent |
--silent |
--silent |
N/A (use env var) | --loglevel silent |
--silent |
Suppress output |
--filter <pattern> |
--filter <pattern> |
N/A | workspaces foreach -A --include <pattern> |
--workspace <pattern> |
--filter <pattern> |
Target specific workspace package(s) |
-w, --workspace-root |
-w |
-W |
N/A | --include-workspace-root |
N/A | Install in root only |
Notes:
--frozen-lockfile: For npm, this maps tonpm cicommand instead ofnpm install--no-frozen-lockfile: Takes higher priority over--frozen-lockfilewhen both are specified. Passed through to the actual package manager (pnpm:--no-frozen-lockfile, yarn@1:--no-frozen-lockfile, yarn@2+:--no-immutable, npm: usesnpm installinstead ofnpm ci)--prod: yarn@2+ requires configuration in.yarnrc.ymlinstead of CLI flag--ignore-scripts: For yarn@2+, this maps to--mode skip-build--fix-lockfile: Automatically fixes broken lockfile entries (pnpm and yarn@2+ only, npm does not support)--resolution-only: Re-runs dependency resolution without installing packages. Useful for peer dependency analysis (pnpm only)--shamefully-hoist: pnpm-specific, creates flat node_modules like npm/yarn--ignore-scripts: For bun, use--ignore-scriptsto skip lifecycle scripts.--silent: Suppresses output. For yarn@2+, useYARN_ENABLE_PROGRESS=falseenvironment variable instead. For npm, maps to--loglevel silent
Add Package Mode:
When packages are provided as arguments (e.g., vp install react), the command acts as an alias for vp add:
--save-exact, -E: Save exact version rather than semver range--save-peer: Save to peerDependencies (and devDependencies)--save-optional, -O: Save to optionalDependencies--save-catalog: Save to the default catalog (pnpm only)--global, -g: Install globally
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:
vp install --filter app --filter web # Install for both app and web
vp install --filter "app*" --filter "!app-test" # app* except app-testNote: For pnpm, --filter must come before the command (e.g., pnpm --filter app install). For yarn/npm, it's integrated into the command structure.
Additional parameters not covered by Vite+ can be handled through pass-through arguments.
All arguments after -- will be passed through to the package manager.
vp install -- --use-stderr
-> pnpm install --use-stderr
-> yarn install --use-stderr
-> npm install --use-stderrFile: crates/vite_global/src/lib.rs
Add new command variant:
#[derive(Subcommand, Debug)]
pub enum Commands {
// ... existing commands
/// Install all dependencies
#[command(disable_help_flag = true, alias = "i")]
Install {
/// Do not install devDependencies
#[arg(short = 'P', long)]
prod: bool,
/// Only install devDependencies
#[arg(short = 'D', long)]
dev: bool,
/// Do not install optionalDependencies
#[arg(long)]
no_optional: bool,
/// Fail if lockfile needs to be updated (CI mode)
#[arg(long)]
frozen_lockfile: bool,
/// Only update lockfile, don't install
#[arg(long)]
lockfile_only: bool,
/// Use cached packages when available
#[arg(long)]
prefer_offline: bool,
/// Only use packages already in cache
#[arg(long)]
offline: bool,
/// Force reinstall all dependencies
#[arg(short = 'f', long)]
force: bool,
/// Do not run lifecycle scripts
#[arg(long)]
ignore_scripts: bool,
/// Don't read or generate lockfile
#[arg(long)]
no_lockfile: bool,
/// Fix broken lockfile entries
#[arg(long)]
fix_lockfile: bool,
/// Create flat node_modules (pnpm only)
#[arg(long)]
shamefully_hoist: bool,
/// Re-run resolution for peer dependency analysis
#[arg(long)]
resolution_only: bool,
/// Filter packages in monorepo (can be used multiple times)
#[arg(long, value_name = "PATTERN")]
filter: Vec<String>,
/// Install in workspace root only
#[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/commands/install.rs
Add methods to translate commands:
impl PackageManager {
/// Build install command arguments
pub fn build_install_args(&self, options: &InstallOptions) -> InstallCommandResult {
let mut args = Vec::new();
let mut use_ci = false;
match self.client {
PackageManagerType::Pnpm => {
// pnpm: --filter must come before command
for filter in &options.filters {
args.push("--filter".to_string());
args.push(filter.clone());
}
args.push("install".to_string());
if options.prod {
args.push("--prod".to_string());
}
if options.dev {
args.push("--dev".to_string());
}
if options.no_optional {
args.push("--no-optional".to_string());
}
if options.frozen_lockfile {
args.push("--frozen-lockfile".to_string());
}
if options.lockfile_only {
args.push("--lockfile-only".to_string());
}
if options.prefer_offline {
args.push("--prefer-offline".to_string());
}
if options.offline {
args.push("--offline".to_string());
}
if options.force {
args.push("--force".to_string());
}
if options.ignore_scripts {
args.push("--ignore-scripts".to_string());
}
if options.no_lockfile {
args.push("--no-lockfile".to_string());
}
if options.fix_lockfile {
args.push("--fix-lockfile".to_string());
}
if options.shamefully_hoist {
args.push("--shamefully-hoist".to_string());
}
if options.resolution_only {
args.push("--resolution-only".to_string());
}
if options.workspace_root {
args.push("-w".to_string());
}
}
PackageManagerType::Yarn => {
args.push("install".to_string());
if self.is_yarn_berry() {
// yarn@2+ (Berry)
if options.frozen_lockfile {
args.push("--immutable".to_string());
}
if options.lockfile_only {
args.push("--mode".to_string());
args.push("update-lockfile".to_string());
}
if options.fix_lockfile {
args.push("--refresh-lockfile".to_string());
}
if options.ignore_scripts {
args.push("--mode".to_string());
args.push("skip-build".to_string());
}
if options.resolution_only {
eprintln!("Warning: yarn@2+ does not support --resolution-only");
}
// Note: yarn@2+ uses .yarnrc.yml for prod
if options.prod {
eprintln!("Warning: yarn@2+ requires configuration in .yarnrc.yml for --prod behavior");
}
// yarn@2+ filter is handled differently - needs workspaces foreach
if !options.filters.is_empty() {
// For yarn@2+, we need to use: yarn workspaces foreach -A --include <pattern> install
// This requires restructuring the command
args.clear();
args.push("workspaces".to_string());
args.push("foreach".to_string());
args.push("-A".to_string());
for filter in &options.filters {
args.push("--include".to_string());
args.push(filter.clone());
}
args.push("install".to_string());
}
} else {
// yarn@1 (Classic)
if options.prod {
args.push("--production".to_string());
}
if options.no_optional {
args.push("--ignore-optional".to_string());
}
if options.frozen_lockfile {
args.push("--frozen-lockfile".to_string());
}
if options.prefer_offline {
args.push("--prefer-offline".to_string());
}
if options.offline {
args.push("--offline".to_string());
}
if options.force {
args.push("--force".to_string());
}
if options.ignore_scripts {
args.push("--ignore-scripts".to_string());
}
if options.no_lockfile {
args.push("--no-lockfile".to_string());
}
if options.fix_lockfile {
eprintln!("Warning: yarn@1 does not support --fix-lockfile");
}
if options.resolution_only {
eprintln!("Warning: yarn@1 does not support --resolution-only");
}
if options.workspace_root {
args.push("-W".to_string());
}
}
}
PackageManagerType::Npm => {
// npm: Use `npm ci` for frozen-lockfile
if options.frozen_lockfile {
args.push("ci".to_string());
use_ci = true;
} else {
args.push("install".to_string());
}
if options.prod {
args.push("--omit=dev".to_string());
}
if options.dev && !use_ci {
args.push("--include=dev".to_string());
args.push("--omit=prod".to_string());
}
if options.no_optional {
args.push("--omit=optional".to_string());
}
if options.lockfile_only && !use_ci {
args.push("--package-lock-only".to_string());
}
if options.prefer_offline {
args.push("--prefer-offline".to_string());
}
if options.offline {
args.push("--offline".to_string());
}
if options.force && !use_ci {
args.push("--force".to_string());
}
if options.ignore_scripts {
args.push("--ignore-scripts".to_string());
}
if options.no_lockfile && !use_ci {
args.push("--no-package-lock".to_string());
}
if options.fix_lockfile {
eprintln!("Warning: npm does not support --fix-lockfile");
}
if options.resolution_only {
eprintln!("Warning: npm does not support --resolution-only");
}
if options.workspace_root {
args.push("--include-workspace-root".to_string());
}
for filter in &options.filters {
args.push("--workspace".to_string());
args.push(filter.clone());
}
}
}
// Pass through extra args
args.extend_from_slice(&options.extra_args);
InstallCommandResult {
command: if use_ci { "ci".to_string() } else { "install".to_string() },
args,
}
}
fn is_yarn_berry(&self) -> bool {
// yarn@2+ is called "Berry"
!self.version.starts_with("1.")
}
}
pub struct InstallOptions {
pub prod: bool,
pub dev: bool,
pub no_optional: bool,
pub frozen_lockfile: bool,
pub lockfile_only: bool,
pub prefer_offline: bool,
pub offline: bool,
pub force: bool,
pub ignore_scripts: bool,
pub no_lockfile: bool,
pub fix_lockfile: bool,
pub shamefully_hoist: bool,
pub resolution_only: bool,
pub filters: Vec<String>,
pub workspace_root: bool,
pub extra_args: Vec<String>,
}
pub struct InstallCommandResult {
pub command: String,
pub args: Vec<String>,
}File: crates/vite_global/src/install.rs (new file)
use vite_error::Error;
use vite_path::AbsolutePathBuf;
use vite_package_manager::{PackageManager, InstallOptions};
pub struct InstallCommand {
workspace_root: AbsolutePathBuf,
}
impl InstallCommand {
pub fn new(workspace_root: AbsolutePathBuf) -> Self {
Self { workspace_root }
}
pub async fn execute(self, options: InstallOptions) -> Result<(), Error> {
let package_manager = PackageManager::builder(&self.workspace_root).build().await?;
let resolve_command = package_manager.resolve_command();
let install_result = package_manager.build_install_args(&options);
let status = package_manager
.run_command(&install_result.args, &self.workspace_root)
.await?;
if !status.success() {
return Err(Error::CommandFailed {
command: format!("install"),
exit_code: status.code(),
});
}
Ok(())
}
}Decision: Do not cache install operations.
Rationale:
- Install commands modify node_modules and lockfiles
- Side effects make caching inappropriate
- Each execution should run fresh
- Package managers have their own caching mechanisms
Decision: Map --frozen-lockfile to npm ci for npm.
Rationale:
npm ciis the recommended way to do clean installs in CI- It's faster than
npm install --frozen-lockfile - Automatically removes existing node_modules
- Better aligns with CI best practices
Decision: Pass all arguments after -- 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 differences
Decision: Support workspace filtering with --filter flag.
Rationale:
- Monorepo workflows need selective installation
- pnpm's filter syntax is most powerful
- Graceful degradation for other package managers
- Consistent with other Vite+ commands
Decision: Support vp i as alias for vp install.
Rationale:
- Matches npm/yarn/pnpm convention (
npm i,yarn,pnpm i) - Faster to type
- Familiar to developers
$ vp install
Error: No package manager detected
Please run one of:
- vp install (after adding packageManager to package.json)
- Add packageManager field to package.json$ vp install --frozen-lockfile
Detected package manager: pnpm@10.15.0
Running: pnpm install --frozen-lockfile
ERR_PNPM_OUTDATED_LOCKFILE Cannot install with "frozen-lockfile" because pnpm-lock.yaml is not up to date with package.json
Error: Command failed with exit code 1$ vp install --offline
Detected package manager: npm@11.0.0
Running: npm install --offline
npm ERR! code E404
npm ERR! 404 Not Found - GET https://registry.npmjs.org/some-package - Package not found in cache
Error: Command failed with exit code 1$ vp install
Detected package manager: pnpm@10.15.0
Running: pnpm install
Lockfile is up to date, resolution step is skipped
Packages: +150
+++++++++++++++++++++++++++++++++++
Progress: resolved 150, reused 150, downloaded 0, added 150, done
Done in 1.2s$ vp install --frozen-lockfile
Detected package manager: npm@11.0.0
Running: npm ci
added 150 packages in 2.3s
Done in 2.3s$ vp install --prod
Detected package manager: pnpm@10.15.0
Running: pnpm install --prod
Packages: +80
++++++++++++++++++++
Progress: resolved 80, reused 80, downloaded 0, added 80, done
Done in 0.8s$ vp install --filter app
Detected package manager: pnpm@10.15.0
Running: pnpm --filter app install
Scope: 1 of 5 workspace projects
Packages: +50
++++++++++++++
Progress: resolved 50, reused 50, downloaded 0, added 50, done
Done in 0.5s# Let user call package manager directly
pnpm install
yarn install
npm installRejected because:
- No abstraction benefit
- Scripts not portable
- Requires knowing package manager
- Inconsistent developer experience
Implement our own dependency resolution and installation:
// Custom dependency resolver
let deps = resolve_dependencies(&package_json)?;
download_packages(&deps)?;
link_packages(&deps)?;Rejected because:
- Enormous complexity
- Package managers are well-tested
- Would miss PM-specific optimizations
- Maintenance burden
# Detect package manager from environment
VITE_PM=pnpm vp installRejected because:
- Less convenient than auto-detection
- Requires extra configuration
- Not portable across machines
- Existing lockfile detection works well
- Add
Installcommand variant toCommandsenum - Create
install.rsmodule - Implement package manager command resolution
- Add basic flag translation
- Implement workspace filtering
- Add
--frozen-lockfiletonpm cimapping - Handle yarn@1 vs yarn@2+ differences
- Add pass-through argument support
- Unit tests for command resolution
- Integration tests with mock package managers
- Manual testing with real package managers
- CI workflow testing
- Update CLI documentation
- Add examples to README
- Document flag compatibility matrix
- Add troubleshooting guide
#[test]
fn test_pnpm_basic_install() {
let pm = PackageManager::mock(PackageManagerType::Pnpm, "10.0.0");
let options = InstallOptions::default();
let result = pm.build_install_args(&options);
assert_eq!(result.args, vec!["install"]);
}
#[test]
fn test_pnpm_prod_install() {
let pm = PackageManager::mock(PackageManagerType::Pnpm, "10.0.0");
let options = InstallOptions { prod: true, ..Default::default() };
let result = pm.build_install_args(&options);
assert_eq!(result.args, vec!["install", "--prod"]);
}
#[test]
fn test_npm_frozen_lockfile_uses_ci() {
let pm = PackageManager::mock(PackageManagerType::Npm, "11.0.0");
let options = InstallOptions { frozen_lockfile: true, ..Default::default() };
let result = pm.build_install_args(&options);
assert_eq!(result.command, "ci");
}
#[test]
fn test_yarn_berry_frozen_lockfile() {
let pm = PackageManager::mock(PackageManagerType::Yarn, "4.0.0");
let options = InstallOptions { frozen_lockfile: true, ..Default::default() };
let result = pm.build_install_args(&options);
assert_eq!(result.args, vec!["install", "--immutable"]);
}
#[test]
fn test_pnpm_filter() {
let pm = PackageManager::mock(PackageManagerType::Pnpm, "10.0.0");
let options = InstallOptions {
filters: vec!["app".to_string()],
..Default::default()
};
let result = pm.build_install_args(&options);
assert_eq!(result.args, vec!["--filter", "app", "install"]);
}
#[test]
fn test_npm_workspace_filter() {
let pm = PackageManager::mock(PackageManagerType::Npm, "11.0.0");
let options = InstallOptions {
filters: vec!["app".to_string()],
..Default::default()
};
let result = pm.build_install_args(&options);
assert_eq!(result.args, vec!["install", "--workspace", "app"]);
}
#[test]
fn test_pnpm_fix_lockfile() {
let pm = PackageManager::mock(PackageManagerType::Pnpm, "10.0.0");
let options = InstallOptions { fix_lockfile: true, ..Default::default() };
let result = pm.build_install_args(&options);
assert_eq!(result.args, vec!["install", "--fix-lockfile"]);
}
#[test]
fn test_yarn_berry_fix_lockfile() {
let pm = PackageManager::mock(PackageManagerType::Yarn, "4.0.0");
let options = InstallOptions { fix_lockfile: true, ..Default::default() };
let result = pm.build_install_args(&options);
assert_eq!(result.args, vec!["install", "--refresh-lockfile"]);
}
#[test]
fn test_yarn_berry_ignore_scripts() {
let pm = PackageManager::mock(PackageManagerType::Yarn, "4.0.0");
let options = InstallOptions { ignore_scripts: true, ..Default::default() };
let result = pm.build_install_args(&options);
assert_eq!(result.args, vec!["install", "--mode", "skip-build"]);
}
#[test]
fn test_pnpm_resolution_only() {
let pm = PackageManager::mock(PackageManagerType::Pnpm, "10.0.0");
let options = InstallOptions { resolution_only: true, ..Default::default() };
let result = pm.build_install_args(&options);
assert_eq!(result.args, vec!["install", "--resolution-only"]);
}
#[test]
fn test_yarn_berry_filter() {
let pm = PackageManager::mock(PackageManagerType::Yarn, "4.0.0");
let options = InstallOptions {
filters: vec!["app".to_string()],
..Default::default()
};
let result = pm.build_install_args(&options);
assert_eq!(result.args, vec!["workspaces", "foreach", "-A", "--include", "app", "install"]);
}Create fixtures for testing with each package manager:
fixtures/install-test/
pnpm-workspace.yaml
package.json
packages/
app/
package.json
utils/
package.json
test-steps.json
Test cases:
- Basic install
- Production install
- Frozen lockfile install
- Workspace filter install
- Recursive install
- Offline install
- Force reinstall
- Ignore scripts install
$ vp install --help
Install all dependencies, or add packages if package names are provided
Usage: vp install [OPTIONS] [PACKAGES]...
Aliases: i
Options:
-P, --prod Do not install devDependencies
-D, --dev Only install devDependencies (install) / Save to devDependencies (add)
--no-optional Do not install optionalDependencies
--frozen-lockfile Fail if lockfile needs to be updated (CI mode)
--no-frozen-lockfile Allow lockfile updates (opposite of --frozen-lockfile)
--lockfile-only Only update lockfile, don't install
--prefer-offline Use cached packages when available
--offline Only use packages already in cache
-f, --force Force reinstall all dependencies
--ignore-scripts Do not run lifecycle scripts
--no-lockfile Don't read or generate lockfile
--fix-lockfile Fix broken lockfile entries
--shamefully-hoist Create flat node_modules (pnpm only)
--resolution-only Re-run resolution for peer dependency analysis
--silent Suppress output (silent mode)
--filter <PATTERN> Filter packages in monorepo (can be used multiple times)
-w, --workspace-root Install in workspace root only
-E, --save-exact Save exact version (only when adding packages)
--save-peer Save to peerDependencies (only when adding packages)
-O, --save-optional Save to optionalDependencies (only when adding packages)
--save-catalog Save to default catalog (only when adding packages)
-g, --global Install globally (only when adding packages)
-h, --help Print help
Examples:
vp install # Install all dependencies
vp i # Short alias
vp install --prod # Production install
vp install --frozen-lockfile # CI mode (strict lockfile)
vp install --filter app # Install for specific package
vp install --silent # Silent install
vp install react # Add react (alias for vp add)
vp install -D typescript # Add typescript as devDependency
vp install --save-peer react # Add react as peerDependency- Delegate to Package Manager: Leverage PM's built-in optimizations
- No Additional Overhead: Minimal processing before running PM command
- Cache Utilization: Support
--prefer-offlineand--offlineflags - Parallel Installation: Package managers handle parallelization
- Script Execution:
--ignore-scriptsprevents untrusted script execution - Lockfile Integrity:
--frozen-lockfileensures reproducible installs - Network Security: Package managers handle registry authentication
- Pass-Through Safety: Arguments are passed through safely
This is a new feature with no breaking changes:
- Existing commands unaffected
- New command is additive
- No changes to task configuration
- No changes to caching behavior
| Feature | pnpm | yarn@1 | yarn@2+ | npm | bun | Notes |
|---|---|---|---|---|---|---|
| Basic install | ✅ | ✅ | ✅ | ✅ | ✅ | All supported |
--prod |
✅ | ✅ | ✅ | ✅ | yarn@2+ needs .yarnrc.yml | |
--dev |
✅ | ❌ | ❌ | ✅ | ❌ | Limited support |
--no-optional |
✅ | ✅ | ✅ | ✅ | yarn@2+ needs .yarnrc.yml | |
--frozen-lockfile |
✅ | ✅ | ✅ --immutable |
✅ ci |
✅ | npm uses npm ci |
--no-frozen-lockfile |
✅ | ✅ | ✅ --no-immutable |
✅ install |
✅ | Pass through to PM |
--lockfile-only |
✅ | ❌ | ✅ | ✅ | ✅ | yarn@1 not supported |
--prefer-offline |
✅ | ✅ | ❌ | ✅ | ❌ | yarn@2+, bun not supported |
--offline |
✅ | ✅ | ❌ | ✅ | ❌ | yarn@2+, bun not supported |
--force |
✅ | ✅ | ❌ | ✅ | ✅ | yarn@2+ not supported |
--ignore-scripts |
✅ | ✅ | ✅ --mode skip-build |
✅ | ✅ | |
--no-lockfile |
✅ | ✅ | ❌ | ✅ | ❌ | yarn@2+, bun not supported |
--fix-lockfile |
✅ | ❌ | ✅ --refresh-lockfile |
❌ | ❌ | pnpm and yarn@2+ only |
--shamefully-hoist |
✅ | ❌ | ❌ | ❌ | ❌ (hoisted by default) | pnpm only |
--resolution-only |
✅ | ❌ | ❌ | ❌ | ❌ | pnpm only |
--silent |
✅ | ✅ | ✅ --loglevel |
✅ | yarn@2+ use env var | |
--filter |
✅ | ❌ | ✅ workspaces foreach |
✅ | ✅ | yarn@1 not supported |
$ vp install --interactive
? Select packages to install:
[x] dependencies (150 packages)
[ ] devDependencies (80 packages)
[x] optionalDependencies (5 packages)$ vp install --progress
Installing dependencies...
[============================] 100% | 150/150 packages$ vp install --analyze
Installing dependencies...
Added packages:
react@18.3.1 (85KB)
react-dom@18.3.1 (120KB)
Total: 150 packages, 12.3MB
Done in 2.3s$ vp install --update react
# Install and update specific package# .github/workflows/ci.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: vp install --frozen-lockfile
- name: Build
run: vp buildFROM node:20-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
# Production install only
RUN npm install -g @voidzero/global && \
vp install --prod --frozen-lockfile
COPY . .
RUN vp build# Install dependencies for specific package
vp install --filter @myorg/web-app
# Force reinstall after branch switch
vp install --force# Populate cache first
vp install
# Later, work offline
vp install --offline-
Should we support
--checkflag?- Proposed: Add
--checkto verify lockfile without installing - Similar to
pnpm install --lockfile-onlybut without writing
- Proposed: Add
-
Should we auto-detect CI environment?
- Proposed: Auto-enable
--frozen-lockfilein CI (like pnpm) - Could check
CIenvironment variable
- Proposed: Auto-enable
-
Should we support package manager version pinning?
- Proposed: Respect
packageManagerfield in package.json - Already implemented in package manager detection
- Proposed: Respect
-
How to handle conflicting flags?
- Proposed: Let package manager handle conflicts
- Example:
--prodand--devtogether
This RFC proposes adding vp install command to provide a unified interface for installing dependencies across pnpm/yarn/npm/bun. The design:
- ✅ Automatically adapts to detected package manager
- ✅ Supports common installation flags
- ✅ Full workspace support following pnpm's API design
- ✅ Uses pass-through for maximum flexibility
- ✅ No caching overhead (delegates to package manager)
- ✅ Simple implementation leveraging existing infrastructure
- ✅ CI-friendly with
--frozen-lockfilesupport - ✅ Extensible for future enhancements
The implementation follows the same patterns as other package management commands (add, remove, update) while providing a unified, intuitive interface for dependency installation.