Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ vite-plus/
├── crates/vp_command/ # Shared command execution helpers
├── crates/vp_error/ # Shared error types
├── crates/vp_global_cli/ # Standalone global vp binary and top-level command routing
├── crates/vp_global_only_cli/ # clap definitions for global-only commands (env, upgrade, implode)
├── crates/vp_installer/ # Installer binary support
├── crates/vp_js_runtime/ # Managed Node.js runtime support
├── crates/vp_migration/ # Rust migration helpers
Expand All @@ -59,7 +60,7 @@ install is present. Call sites must not construct category paths or read

- **JS-backed CLI behavior**: start at `packages/cli/src/bin.ts` and nearby `packages/cli/src/**` files.
- **Local CLI / NAPI-backed behavior**: start at `packages/cli/binding/src/lib.rs` and `packages/cli/binding/src/cli/mod.rs`.
- **Global `vp` routing, aliases, and runtime bootstrap**: start at `crates/vp_global_cli/src/main.rs` and `crates/vp_global_cli/src/cli.rs`.
- **Global `vp` routing, aliases, and runtime bootstrap**: start at `crates/vp_global_cli/src/main.rs` and `crates/vp_global_cli/src/cli.rs`; the `env`, `upgrade`, and `implode` argument definitions live in `crates/vp_global_only_cli/`.
- **Package-manager behavior**: start at `crates/vp_pm_cli/`.
- **Managed Node runtime / shims**: start at `crates/vp_js_runtime/`.
- **Static `vite.config.ts` extraction**: start at `crates/vp_static_config/README.md` and `packages/cli/src/resolve-vite-config.ts`.
Expand Down
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ uuid = "1.17.0"
vfs = "0.13.0"
vp_command = { path = "crates/vp_command" }
vp_error = { path = "crates/vp_error" }
vp_global_only_cli = { path = "crates/vp_global_only_cli" }
vp_js_runtime = { path = "crates/vp_js_runtime" }
vp_migration = { path = "crates/vp_migration" }
vp_pm_cli = { path = "crates/vp_pm_cli" }
Expand Down
1 change: 1 addition & 0 deletions crates/vp_global_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ indexmap = { workspace = true }
indicatif = { workspace = true }
indoc = { workspace = true }
vp_error = { workspace = true }
vp_global_only_cli = { workspace = true }
vp_js_runtime = { workspace = true }
vp_pm_cli = { workspace = true }
vt_path = { workspace = true }
Expand Down
292 changes: 14 additions & 278 deletions crates/vp_global_cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use clap_complete::ArgValueCompleter;
use dialoguer::{Confirm, theme::ColorfulTheme};
use owo_colors::OwoColorize;
use tokio::runtime::Runtime;
use vp_global_only_cli::GlobalOnlyCommand;
pub use vp_global_only_cli::{EnvArgs, EnvSubcommands, PinTarget, SortingMethod};
use vp_pm_cli::{ManagedGlobalCommand, PackageManagerCommand};
use vp_shared::output;
use vt_path::AbsolutePathBuf;
Expand Down Expand Up @@ -217,53 +219,12 @@ pub enum Commands {
global: bool,
},

/// Manage Node.js versions
Env(EnvArgs),

// =========================================================================
// Self-Management
// Category D: Global-only commands (env, upgrade, implode)
// (clap-flattened from the shared `vp_global_only_cli::GlobalOnlyCommand`.)
// =========================================================================
/// Update vp itself to the latest version
#[command(name = "upgrade")]
Upgrade {
/// Target version (e.g., "0.2.0"). Defaults to latest.
version: Option<String>,

/// npm dist-tag to install (default: "latest", also: "alpha")
#[arg(long, default_value = "latest")]
tag: String,

/// Check for updates without installing
#[arg(long)]
check: bool,

/// Revert to the previously active version
#[arg(long)]
rollback: bool,

/// Force reinstall even if already on the target version
#[arg(long)]
force: bool,

/// Suppress output
#[arg(long)]
silent: bool,

/// Custom npm registry URL
#[arg(long)]
registry: Option<String>,

/// Refresh the cached update status without producing output
#[arg(long, hide = true)]
background_check: bool,
},

/// Remove vp and all related data
Implode {
/// Skip confirmation prompt
#[arg(long, short = 'y')]
yes: bool,
},
#[command(flatten)]
GlobalOnly(GlobalOnlyCommand),
}

impl Commands {
Expand All @@ -273,239 +234,12 @@ impl Commands {
match self {
Self::PackageManager(pm) => pm.is_quiet_or_machine_readable(),
Self::Toolchain { json, .. } => *json,
Self::Upgrade { silent, .. } => *silent,
Self::Env(args) => {
args.command.as_ref().is_some_and(|sub| sub.is_quiet_or_machine_readable())
}
_ => false,
}
}
}

/// Arguments for the `env` command
#[derive(clap::Args, Debug)]
pub struct EnvArgs {
/// Subcommand (e.g., 'default', 'setup', 'doctor', 'which')
#[command(subcommand)]
pub command: Option<EnvSubcommands>,
}

/// Subcommands for the `env` command
#[derive(clap::Subcommand, Debug)]
pub enum EnvSubcommands {
/// Show current environment information
Current {
/// Output in JSON format
#[arg(long)]
json: bool,
},

/// Print shell snippet to set environment for current session
Print,

/// Set or show the global default Node.js version
#[command(after_long_help = "\
Examples:
vp env default # Show the current default
vp env default lts # Set the default")]
Default {
/// Version to set as default (e.g., "20.18.0", "lts", "latest").
/// If omitted, prints the current default.
version: Option<String>,
},

/// Enable managed mode - shims always use vite-plus managed Node.js
On,

/// Enable system-first mode - shims prefer system Node.js, fallback to managed
Off,

/// Create or update shims in VP_HOME/bin
Setup {
/// Force refresh shims even if they exist
#[arg(long)]
refresh: bool,
/// Only create env files (skip shims and instructions)
#[arg(long)]
env_only: bool,
},

/// Run diagnostics and show environment status
Doctor,

/// Show path to the tool that would be executed
Which {
/// Tool name (node, npm, or npx)
tool: String,
},

/// Pin a Node.js version in the current directory
/// (updates .node-version or package.json#devEngines.runtime)
#[command(after_long_help = "\
Examples:
vp env pin lts # Pin to latest LTS
vp env pin --unpin # Remove the pin
vp env pin \"^20.0.0\" --force # Overwrite existing pin
vp env pin 24 --target node-version # Force the .node-version file

The write target follows the compatibility-first rule: an existing .node-version
keeps being updated; otherwise the pin is written to package.json#devEngines.runtime;
.node-version is only created when the directory has no package.json.")]
Pin {
/// Version to pin (e.g., "20.18.0", "lts", "latest", "^20.0.0").
/// If omitted, prints the currently pinned version.
version: Option<String>,

/// Remove the pin from the current directory
#[arg(long)]
unpin: bool,

/// Skip pre-downloading the pinned version
#[arg(long)]
no_install: bool,

/// Overwrite an existing pin without confirmation
#[arg(long)]
force: bool,

/// Explicitly choose the write target (overrides the default selection)
#[arg(long, value_enum)]
target: Option<PinTarget>,
},

/// Remove the Node.js pin from current directory (alias for `pin --unpin`)
Unpin {
/// Explicitly choose which pin source to remove
#[arg(long, value_enum)]
target: Option<PinTarget>,
},

/// List locally installed Node.js versions
#[command(visible_alias = "ls")]
List {
/// Output as JSON
#[arg(long)]
json: bool,
},

/// List available Node.js versions from the registry
#[command(name = "list-remote", visible_alias = "ls-remote")]
ListRemote {
/// Filter versions by pattern (e.g., "20" for 20.x versions)
pattern: Option<String>,

/// Show only LTS versions
#[arg(long)]
lts: bool,

/// Show all versions (not just recent)
#[arg(long)]
all: bool,

/// Output as JSON
#[arg(long)]
json: bool,

/// Version sorting order
#[arg(long, value_enum, default_value_t = SortingMethod::Asc)]
sort: SortingMethod,
},

/// Execute a command with a specific Node.js version
#[command(
visible_alias = "run",
after_long_help = "\
Examples:
vp env exec --node lts npm install # Pin version for this invocation
vp env exec node -v # Shim mode: version auto-resolved"
)]
Exec {
/// Node.js version to use (e.g., "20.18.0", "lts", "^20.0.0").
/// If omitted and command is node/npm/npx or a global package binary,
/// version is resolved automatically (same as shim behavior).
#[arg(long)]
node: Option<String>,

/// npm version to use (optional, defaults to bundled)
#[arg(long)]
npm: Option<String>,

/// Command and arguments to run
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
command: Vec<String>,
},

/// Uninstall a Node.js version
#[command(visible_alias = "uni")]
Uninstall {
/// Version to uninstall (e.g., "20.18.0")
#[arg(required = true)]
version: String,
},

/// Remove unused managed runtimes and package manager caches
Clean,

/// Install a Node.js version
#[command(visible_alias = "i")]
Install {
/// Version to install (e.g., "20", "20.18.0", "lts", "latest")
/// If not provided, installs the version from .node-version, package.json, or .nvmrc
version: Option<String>,
},

/// Use a specific Node.js version for this shell session
#[command(after_long_help = "\
Examples:
vp env use lts # Override session with latest LTS
vp env use --unset # Clear the session override")]
Use {
/// Version to use (e.g., "20", "20.18.0", "lts", "latest").
/// If omitted, reads from .node-version, package.json, or .nvmrc.
version: Option<String>,

/// Remove session override (revert to file-based resolution)
#[arg(long)]
unset: bool,

/// Skip auto-installation if version not present
#[arg(long)]
no_install: bool,

/// Suppress output if version is already active
#[arg(long)]
silent_if_unchanged: bool,
},
}

impl EnvSubcommands {
fn is_quiet_or_machine_readable(&self) -> bool {
match self {
Self::Current { json } | Self::List { json } | Self::ListRemote { json, .. } => *json,
Self::GlobalOnly(cmd) => cmd.is_quiet_or_machine_readable(),
_ => false,
}
}
}

/// Write target for `vp env pin` / `vp env unpin` (see rfcs/dev-engines.md)
#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
pub enum PinTarget {
/// Pin via the .node-version file
NodeVersion,
/// Pin via package.json#devEngines.runtime
DevEngines,
}

/// Version sorting order for list-remote command
#[derive(clap::ValueEnum, Clone, Debug, Default)]
pub enum SortingMethod {
/// Sort versions in ascending order (earliest to latest)
#[default]
Asc,
/// Sort versions in descending order (latest to earliest)
Desc,
}

fn has_flag_before_terminator(args: &[String], flag: &str) -> bool {
for arg in args {
if arg == "--" {
Expand Down Expand Up @@ -1093,10 +827,12 @@ pub async fn run_command_with_options(
commands::toolchain::execute(cwd, tools, json, global, raw_subcommand).await
}

Commands::Env(args) => commands::env::execute(cwd, args).await,
// Category D: Global-only commands
Commands::GlobalOnly(GlobalOnlyCommand::Env(args)) => {
commands::env::execute(cwd, args).await
}

// Self-Management
Commands::Upgrade {
Commands::GlobalOnly(GlobalOnlyCommand::Upgrade {
version,
tag,
check,
Expand All @@ -1105,7 +841,7 @@ pub async fn run_command_with_options(
silent,
registry,
background_check,
} => {
}) => {
commands::upgrade::execute(commands::upgrade::UpgradeOptions {
version,
tag,
Expand All @@ -1118,7 +854,7 @@ pub async fn run_command_with_options(
})
.await
}
Commands::Implode { yes } => commands::implode::execute(yes),
Commands::GlobalOnly(GlobalOnlyCommand::Implode { yes }) => commands::implode::execute(yes),
}
}

Expand Down
Loading
Loading