Skip to content
Draft
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
70 changes: 70 additions & 0 deletions crates/vite_global_cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,32 @@ pub enum Commands {
},
}

impl Commands {
/// Whether the command was invoked with flags that request quiet or
/// machine-readable output (--silent, -s, --json, --parseable, --format json/list).
pub fn is_quiet_or_machine_readable(&self) -> bool {
match self {
Self::Install { silent, .. }
| Self::Dlx { silent, .. }
| Self::Upgrade { silent, .. } => *silent,

Self::Outdated { format, .. } => {
matches!(format, Some(Format::Json | Format::List))
}

Self::Why { json, parseable, .. } => *json || *parseable,
Self::Info { json, .. } => *json,

Self::Pm(sub) => sub.is_quiet_or_machine_readable(),
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)]
#[command(after_help = "\
Expand Down Expand Up @@ -877,6 +903,15 @@ pub enum EnvSubcommands {
},
}

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

/// Version sorting order for list-remote command
#[derive(clap::ValueEnum, Clone, Debug, Default)]
pub enum SortingMethod {
Expand Down Expand Up @@ -1240,6 +1275,23 @@ pub enum PmCommands {
},
}

impl PmCommands {
fn is_quiet_or_machine_readable(&self) -> bool {
match self {
Self::List { json, parseable, .. } => *json || *parseable,
Self::Pack { json, .. }
| Self::View { json, .. }
| Self::Publish { json, .. }
| Self::Audit { json, .. }
| Self::Search { json, .. }
| Self::Fund { json, .. } => *json,
Self::Config(sub) => sub.is_quiet_or_machine_readable(),
Self::Token(sub) => sub.is_quiet_or_machine_readable(),
_ => false,
}
}
}

/// Configuration subcommands
#[derive(Subcommand, Debug, Clone)]
pub enum ConfigCommands {
Expand Down Expand Up @@ -1312,6 +1364,15 @@ pub enum ConfigCommands {
},
}

impl ConfigCommands {
fn is_quiet_or_machine_readable(&self) -> bool {
match self {
Self::List { json, .. } | Self::Get { json, .. } | Self::Set { json, .. } => *json,
_ => false,
}
}
}

/// Owner subcommands
#[derive(Subcommand, Debug, Clone)]
pub enum OwnerCommands {
Expand Down Expand Up @@ -1408,6 +1469,15 @@ pub enum TokenCommands {
},
}

impl TokenCommands {
fn is_quiet_or_machine_readable(&self) -> bool {
match self {
Self::List { json, .. } | Self::Create { json, .. } => *json,
_ => false,
}
}
}

/// Distribution tag subcommands
#[derive(Subcommand, Debug, Clone)]
pub enum DistTagCommands {
Expand Down
2 changes: 1 addition & 1 deletion crates/vite_global_cli/src/commands/upgrade/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
mod install;
mod integrity;
mod platform;
mod registry;
pub(crate) mod registry;

use std::process::ExitStatus;

Expand Down
49 changes: 38 additions & 11 deletions crates/vite_global_cli/src/commands/upgrade/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,33 +34,46 @@ const MAIN_PACKAGE_NAME: &str = "vite-plus";
const PLATFORM_PACKAGE_SCOPE: &str = "@voidzero-dev";
const CLI_PACKAGE_NAME_PREFIX: &str = "vite-plus-cli";

/// Resolve a version from the npm registry.
/// Resolve a version string from the npm registry.
///
/// Makes two HTTP calls:
/// 1. Main package metadata to resolve version tags (e.g., "latest" → "1.2.3")
/// 2. CLI platform package metadata to get tarball URL and integrity
pub async fn resolve_version(
/// Single HTTP call to resolve a version or tag (e.g., "latest" → "1.2.3").
/// Does NOT verify the platform-specific package exists.
pub async fn resolve_version_string(
version_or_tag: &str,
platform_suffix: &str,
registry_override: Option<&str>,
) -> Result<ResolvedVersion, Error> {
) -> Result<String, Error> {
let default_registry = npm_registry();
let registry_raw = registry_override.unwrap_or(&default_registry);
let registry = registry_raw.trim_end_matches('/');
let client = HttpClient::new();

// Step 1: Fetch main package metadata to resolve version
let main_url = format!("{registry}/{MAIN_PACKAGE_NAME}/{version_or_tag}");
tracing::debug!("Fetching main package metadata: {}", main_url);

let main_meta: PackageVersionMetadata = client.get_json(&main_url).await.map_err(|e| {
Error::Upgrade(format!("Failed to fetch package metadata from {main_url}: {e}").into())
})?;

// Step 2: Query CLI platform package directly
Ok(main_meta.version)
}

/// Resolve the platform-specific package metadata for a given version.
///
/// Single HTTP call to fetch the tarball URL and integrity hash for the
/// platform-specific CLI binary package.
pub async fn resolve_platform_package(
version: &str,
platform_suffix: &str,
registry_override: Option<&str>,
) -> Result<ResolvedVersion, Error> {
let default_registry = npm_registry();
let registry_raw = registry_override.unwrap_or(&default_registry);
let registry = registry_raw.trim_end_matches('/');
let client = HttpClient::new();

let cli_package_name =
format!("{PLATFORM_PACKAGE_SCOPE}/{CLI_PACKAGE_NAME_PREFIX}-{platform_suffix}");
let cli_url = format!("{registry}/{cli_package_name}/{}", main_meta.version);
let cli_url = format!("{registry}/{cli_package_name}/{version}");
tracing::debug!("Fetching CLI package metadata: {}", cli_url);

let cli_meta: PackageVersionMetadata = client.get_json(&cli_url).await.map_err(|e| {
Expand All @@ -74,12 +87,26 @@ pub async fn resolve_version(
})?;

Ok(ResolvedVersion {
version: main_meta.version,
version: version.to_owned(),
platform_tarball_url: cli_meta.dist.tarball,
platform_integrity: cli_meta.dist.integrity,
})
}

/// Resolve a version from the npm registry with platform package verification.
///
/// Makes two HTTP calls:
/// 1. Main package metadata to resolve version tags (e.g., "latest" → "1.2.3")
/// 2. CLI platform package metadata to get tarball URL and integrity
pub async fn resolve_version(
version_or_tag: &str,
platform_suffix: &str,
registry_override: Option<&str>,
) -> Result<ResolvedVersion, Error> {
let version = resolve_version_string(version_or_tag, registry_override).await?;
resolve_platform_package(&version, platform_suffix, registry_override).await
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
21 changes: 20 additions & 1 deletion crates/vite_global_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod help;
mod js_executor;
mod shim;
mod tips;
mod upgrade_check;

use std::{
io::{IsTerminal, Write},
Expand Down Expand Up @@ -280,7 +281,17 @@ async fn main() -> ExitCode {
}

// Parse CLI arguments (using custom help formatting)
let exit_code = match try_parse_args_from(normalized_args) {
let parse_result = try_parse_args_from(normalized_args);

// Spawn background upgrade check for eligible commands
let upgrade_handle = match &parse_result {
Ok(args) if upgrade_check::should_run_for_command(args) => {
Some(tokio::spawn(upgrade_check::check_for_update()))
}
_ => None,
};

let exit_code = match parse_result {
Err(e) => {
use clap::error::ErrorKind;

Expand Down Expand Up @@ -355,6 +366,14 @@ async fn main() -> ExitCode {
},
};

// Display upgrade notice if a newer version is available
if let Some(handle) = upgrade_handle
&& let Ok(Ok(Some(result))) =
tokio::time::timeout(std::time::Duration::from_millis(500), handle).await
{
upgrade_check::display_upgrade_notice(&result);
}

tip_context.exit_code = if exit_code == ExitCode::SUCCESS { 0 } else { 1 };

if let Some(tip) = tips::get_tip(&tip_context) {
Expand Down
Loading
Loading