Skip to content
Open
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
73 changes: 54 additions & 19 deletions crates/vite_global_cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -844,16 +844,30 @@ fn prompt_reinstall_node_mismatches(
.unwrap_or(false)
}

/// The subcommand as the user wrote it, taken from `argv` before any rewriting.
///
/// Parsing resolves a subcommand to one clap variant, which does not record the
/// spelling used, so it is read straight from the command line instead.
#[must_use]
pub fn raw_subcommand(argv: &[String]) -> Option<&str> {
argv.iter().skip(1).map(String::as_str).find(|arg| !arg.starts_with('-'))
}

/// Run the CLI command.
pub async fn run_command(cwd: AbsolutePathBuf, args: Args) -> Result<ExitStatus, Error> {
run_command_with_options(cwd, args, RenderOptions::default()).await
pub async fn run_command(
cwd: AbsolutePathBuf,
args: Args,
raw_subcommand: Option<&str>,
) -> Result<ExitStatus, Error> {
run_command_with_options(cwd, args, RenderOptions::default(), raw_subcommand).await
}

/// Run the CLI command with rendering options.
pub async fn run_command_with_options(
cwd: AbsolutePathBuf,
args: Args,
render_options: RenderOptions,
raw_subcommand: Option<&str>,
) -> Result<ExitStatus, Error> {
// Handle --version flag (Category B: delegates to JS)
if args.version {
Expand Down Expand Up @@ -887,37 +901,37 @@ pub async fn run_command_with_options(
}

// Category B: JS Script Commands
Commands::Create { args } => commands::create::execute(cwd, &args).await,
Commands::Create { args } => commands::create::execute(cwd, &args, raw_subcommand).await,

Commands::Migrate { args } => commands::migrate::execute(cwd, &args).await,

Commands::Config { args } => commands::config::execute(cwd, &args).await,
Commands::Config { args } => commands::config::execute(cwd, &args, raw_subcommand).await,

Commands::Staged { args } => commands::staged::execute(cwd, &args).await,
Commands::Staged { args } => commands::staged::execute(cwd, &args, raw_subcommand).await,

// Category C: Local CLI Delegation (stubs)
Commands::Dev { args } => {
if help::maybe_print_unified_delegate_help("dev", &args, render_options.show_header) {
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
commands::delegate::execute(cwd, "dev", &args).await
commands::delegate::execute(cwd, "dev", &args, raw_subcommand).await
}

Commands::Build { args } => {
if help::maybe_print_unified_delegate_help("build", &args, render_options.show_header) {
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
commands::delegate::execute(cwd, "build", &args).await
commands::delegate::execute(cwd, "build", &args, raw_subcommand).await
}

Commands::Test { args } => {
if help::maybe_print_unified_delegate_help("test", &args, render_options.show_header) {
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
commands::delegate::execute(cwd, "test", &args).await
commands::delegate::execute(cwd, "test", &args, raw_subcommand).await
}

Commands::Lint { args } => {
Expand All @@ -926,9 +940,9 @@ pub async fn run_command_with_options(
}
maybe_print_runtime_header("lint", &args, render_options.show_header);
if should_force_global_delegate("lint", &args) {
commands::delegate::execute_global(cwd, "lint", &args).await
commands::delegate::execute_global(cwd, "lint", &args, raw_subcommand).await
} else {
commands::delegate::execute(cwd, "lint", &args).await
commands::delegate::execute(cwd, "lint", &args, raw_subcommand).await
}
}

Expand All @@ -938,9 +952,9 @@ pub async fn run_command_with_options(
}
maybe_print_runtime_header("fmt", &args, render_options.show_header);
if should_force_global_delegate("fmt", &args) {
commands::delegate::execute_global(cwd, "fmt", &args).await
commands::delegate::execute_global(cwd, "fmt", &args, raw_subcommand).await
} else {
commands::delegate::execute(cwd, "fmt", &args).await
commands::delegate::execute(cwd, "fmt", &args, raw_subcommand).await
}
}

Expand All @@ -949,31 +963,31 @@ pub async fn run_command_with_options(
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
commands::delegate::execute(cwd, "check", &args).await
commands::delegate::execute(cwd, "check", &args, raw_subcommand).await
}

Commands::Pack { args } => {
if help::maybe_print_unified_delegate_help("pack", &args, render_options.show_header) {
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
commands::delegate::execute(cwd, "pack", &args).await
commands::delegate::execute(cwd, "pack", &args, raw_subcommand).await
}

Commands::Run { args } => {
if help::maybe_print_unified_delegate_help("run", &args, render_options.show_header) {
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
commands::delegate::execute(cwd, "run", &args).await
commands::delegate::execute(cwd, "run", &args, raw_subcommand).await
}

Commands::Exec { args } => {
if help::maybe_print_unified_delegate_help("exec", &args, render_options.show_header) {
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
commands::delegate::execute(cwd, "exec", &args).await
commands::delegate::execute(cwd, "exec", &args, raw_subcommand).await
}

Commands::Preview { args } => {
Expand All @@ -982,15 +996,15 @@ pub async fn run_command_with_options(
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
commands::delegate::execute(cwd, "preview", &args).await
commands::delegate::execute(cwd, "preview", &args, raw_subcommand).await
}

Commands::Cache { args } => {
if help::maybe_print_unified_delegate_help("cache", &args, render_options.show_header) {
return Ok(ExitStatus::default());
}
print_runtime_header(render_options.show_header);
commands::delegate::execute(cwd, "cache", &args).await
commands::delegate::execute(cwd, "cache", &args, raw_subcommand).await
}

Commands::Env(args) => commands::env::execute(cwd, args).await,
Expand Down Expand Up @@ -1086,10 +1100,31 @@ pub fn try_parse_args_from_with_options(
#[cfg(test)]
mod tests {
use super::{
display_node_version, has_flag_before_terminator, is_same_node_version,
display_node_version, has_flag_before_terminator, is_same_node_version, raw_subcommand,
should_force_global_delegate, should_suppress_header_for_subcommand,
};

fn argv(args: &[&str]) -> Vec<String> {
args.iter().map(|arg| (*arg).to_string()).collect()
}

#[test]
fn raw_subcommand_is_the_token_as_written() {
assert_eq!(raw_subcommand(&argv(&["vp", "fmt", "src/"])), Some("fmt"));
assert_eq!(raw_subcommand(&argv(&["vp", "format", "src/"])), Some("format"));
}

#[test]
fn raw_subcommand_skips_leading_flags() {
assert_eq!(raw_subcommand(&argv(&["vp", "--silent", "install"])), Some("install"));
}

#[test]
fn raw_subcommand_is_none_without_a_subcommand() {
assert_eq!(raw_subcommand(&argv(&["vp"])), None);
assert_eq!(raw_subcommand(&argv(&["vp", "--version"])), None);
}

#[test]
fn detects_global_update_node_version_mismatch() {
assert!(is_same_node_version("21.0.0", "v21.0.0"));
Expand Down
8 changes: 6 additions & 2 deletions crates/vite_global_cli/src/commands/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ use vite_path::AbsolutePathBuf;
use crate::error::Error;

/// Execute the `config` command by delegating to local or global vite-plus.
pub async fn execute(cwd: AbsolutePathBuf, args: &[String]) -> Result<ExitStatus, Error> {
super::delegate::execute(cwd, "config", args).await
pub async fn execute(
cwd: AbsolutePathBuf,
args: &[String],
raw_subcommand: Option<&str>,
) -> Result<ExitStatus, Error> {
super::delegate::execute(cwd, "config", args, raw_subcommand).await
}
8 changes: 6 additions & 2 deletions crates/vite_global_cli/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ use vite_path::AbsolutePathBuf;
use crate::error::Error;

/// Execute the `create` command by delegating to local or global vite-plus.
pub async fn execute(cwd: AbsolutePathBuf, args: &[String]) -> Result<ExitStatus, Error> {
super::delegate::execute(cwd, "create", args).await
pub async fn execute(
cwd: AbsolutePathBuf,
args: &[String],
raw_subcommand: Option<&str>,
) -> Result<ExitStatus, Error> {
super::delegate::execute(cwd, "create", args, raw_subcommand).await
}

#[cfg(test)]
Expand Down
11 changes: 9 additions & 2 deletions crates/vite_global_cli/src/commands/delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ use vite_path::AbsolutePathBuf;
use crate::{error::Error, js_executor::JsExecutor};

/// Execute a command by delegating to the local `vite-plus` CLI.
///
/// `raw_subcommand` is the subcommand as the user wrote it, which the local CLI
/// cannot recover from `command` alone once parsing has resolved it.
pub async fn execute(
cwd: AbsolutePathBuf,
command: &str,
args: &[String],
raw_subcommand: Option<&str>,
) -> Result<ExitStatus, Error> {
let mut executor = JsExecutor::new(None);
let mut executor = JsExecutor::new(None).with_raw_subcommand(raw_subcommand);
let mut full_args = vec![command.to_string()];
full_args.extend(args.iter().cloned());
executor.delegate_to_local_cli(&cwd, &full_args).await
Expand All @@ -31,12 +35,15 @@ pub async fn execute_output(
}

/// Execute a command by delegating to the global `vite-plus` CLI.
///
/// `raw_subcommand` is the subcommand as the user wrote it; see [`execute`].
pub async fn execute_global(
cwd: AbsolutePathBuf,
command: &str,
args: &[String],
raw_subcommand: Option<&str>,
) -> Result<ExitStatus, Error> {
let mut executor = JsExecutor::new(None);
let mut executor = JsExecutor::new(None).with_raw_subcommand(raw_subcommand);
let mut full_args = vec![command.to_string()];
full_args.extend(args.iter().cloned());
executor.delegate_to_global_cli(&cwd, &full_args).await
Expand Down
8 changes: 6 additions & 2 deletions crates/vite_global_cli/src/commands/staged.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ use vite_path::AbsolutePathBuf;
use crate::error::Error;

/// Execute the `staged` command by delegating to local or global vite-plus.
pub async fn execute(cwd: AbsolutePathBuf, args: &[String]) -> Result<ExitStatus, Error> {
super::delegate::execute(cwd, "staged", args).await
pub async fn execute(
cwd: AbsolutePathBuf,
args: &[String],
raw_subcommand: Option<&str>,
) -> Result<ExitStatus, Error> {
super::delegate::execute(cwd, "staged", args, raw_subcommand).await
}
3 changes: 2 additions & 1 deletion crates/vite_global_cli/src/commands/vpr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ pub async fn execute_vpr(args: &[String], cwd: &AbsolutePath) -> i32 {
}

let cwd_buf = cwd.to_absolute_path_buf();
match super::delegate::execute(cwd_buf, "run", args).await {
// `vpr` is a shim, not a subcommand, so no subcommand was written.
match super::delegate::execute(cwd_buf, "run", args, None).await {
Ok(status) => status.code().unwrap_or(1),
Err(e) => {
output::error(&e.to_string());
Expand Down
16 changes: 15 additions & 1 deletion crates/vite_global_cli/src/js_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub struct JsExecutor {
project_runtime: Option<JsRuntime>,
/// Directory containing JS scripts (from `VITE_GLOBAL_CLI_JS_SCRIPTS_DIR`)
scripts_dir: Option<AbsolutePathBuf>,
/// Subcommand as the user wrote it, forwarded to the CLI this one runs
raw_subcommand: Option<String>,
}

impl JsExecutor {
Expand All @@ -41,7 +43,16 @@ impl JsExecutor {
/// If not provided, will be auto-detected from the binary location.
#[must_use]
pub const fn new(scripts_dir: Option<AbsolutePathBuf>) -> Self {
Self { cli_runtime: None, project_runtime: None, scripts_dir }
Self { cli_runtime: None, project_runtime: None, scripts_dir, raw_subcommand: None }
}

/// Forward the subcommand as the user wrote it to the CLI this one runs.
///
/// A command runs under its canonical name, so the spelling the user used is
/// otherwise lost on the way down.
pub fn with_raw_subcommand(mut self, raw_subcommand: Option<&str>) -> Self {
self.raw_subcommand = raw_subcommand.map(ToOwned::to_owned);
self
}

/// Get the JS scripts directory.
Expand Down Expand Up @@ -340,6 +351,9 @@ impl JsExecutor {

let mut cmd = Self::create_js_command(node_binary, bin_prefix);
cmd.arg(entry_point.as_path()).args(args).current_dir(project_path.as_path());
if let Some(raw_subcommand) = &self.raw_subcommand {
cmd.env(vite_shared::env_vars::VP_RAW_SUBCOMMAND, raw_subcommand);
}
Ok(cmd)
}

Expand Down
18 changes: 12 additions & 6 deletions crates/vite_global_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use vite_shared::output;

pub use crate::cli::try_parse_args_from;
use crate::cli::{
RenderOptions, command_with_help, run_command, run_command_with_options,
RenderOptions, command_with_help, raw_subcommand, run_command, run_command_with_options,
try_parse_args_from_with_options,
};

Expand Down Expand Up @@ -224,9 +224,11 @@ fn clap_error_to_exit_code(e: &clap::Error) -> ExitCode {

async fn run_corrected_args(cwd: &vite_path::AbsolutePathBuf, raw_args: &[String]) -> ExitCode {
let render_options = RenderOptions { show_header: false };
let args_with_program = std::iter::once("vp".to_string()).chain(raw_args.iter().cloned());
let normalized_args = normalize_args(args_with_program.collect());

let args_with_program: Vec<String> =
std::iter::once("vp".to_string()).chain(raw_args.iter().cloned()).collect();
// The subcommand as written, taken before `normalize_args` can rewrite it.
let raw_subcommand = raw_subcommand(&args_with_program).map(str::to_owned);
let normalized_args = normalize_args(args_with_program);
let parsed = match try_parse_args_from_with_options(normalized_args, render_options) {
Ok(args) => args,
Err(e) => {
Expand All @@ -235,7 +237,9 @@ async fn run_corrected_args(cwd: &vite_path::AbsolutePathBuf, raw_args: &[String
}
};

match run_command_with_options(cwd.clone(), parsed, render_options).await {
match run_command_with_options(cwd.clone(), parsed, render_options, raw_subcommand.as_deref())
.await
{
Ok(exit_status) => exit_status_to_exit_code(exit_status),
Err(e) => {
if e.is_user_message() {
Expand Down Expand Up @@ -353,6 +357,8 @@ async fn main() -> ExitCode {

// Capture user args (excluding argv0) before normalization.
let raw_args = args[1..].to_vec();
// The subcommand as written, taken before `normalize_args` can rewrite it.
let raw_subcommand = raw_subcommand(&args).map(str::to_owned);

// Normalize arguments (list/ls aliases, help rewriting)
let normalized_args = normalize_args(args);
Expand Down Expand Up @@ -427,7 +433,7 @@ async fn main() -> ExitCode {
clap_error_to_exit_code(&e)
}
}
Ok(args) => match run_command(cwd.clone(), args).await {
Ok(args) => match run_command(cwd.clone(), args, raw_subcommand.as_deref()).await {
Ok(exit_status) => exit_status_to_exit_code(exit_status),
Err(e) => {
if e.is_user_message() {
Expand Down
7 changes: 7 additions & 0 deletions crates/vite_shared/src/env_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ pub const VP_SHIM_TOOL: &str = "VP_SHIM_TOOL";
/// before forwarding to the actual tool.
pub const VP_SHIM_WRAPPER: &str = "VP_SHIM_WRAPPER";

/// The subcommand as the user wrote it, passed from the global CLI to the local
/// one.
///
/// A command runs under its canonical name (`vp format` runs `fmt`), which loses
/// the spelling. This carries the original alongside it.
pub const VP_RAW_SUBCOMMAND: &str = "VP_RAW_SUBCOMMAND";

/// Path to the vp binary, passed to JS scripts so they can invoke CLI commands.
pub const VP_CLI_BIN: &str = "VP_CLI_BIN";

Expand Down
Loading