Skip to content
137 changes: 50 additions & 87 deletions crates/vite_global_cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@ use vite_pm_cli::PackageManagerCommand;
use vite_shared::output;

use crate::{
commands::{
self,
env::{global_install, package_metadata::PackageMetadata},
},
commands::{self, env::package_metadata::PackageMetadata, global},
error::Error,
help,
};

const DEFAULT_GLOBAL_INSTALL_CONCURRENCY: usize = 5;
const DEFAULT_GLOBAL_VIEW_CONCURRENCY: usize = 3 * DEFAULT_GLOBAL_INSTALL_CONCURRENCY;

#[derive(Clone, Copy, Debug)]
pub struct RenderOptions {
Expand Down Expand Up @@ -533,7 +531,7 @@ fn run_tasks_completions(current: &OsStr) -> Vec<clap_complete::CompletionCandid
/// Handle a parsed package-manager command.
///
/// `Install`/`Add`/`Update`/`Remove` invoked with `-g`/`--global` are routed
/// through the vite-plus-managed Node.js install store (`commands::env`).
/// through the vite-plus-managed Node.js install store (`commands::global`).
/// Everything else is forwarded to `vite_pm_cli::dispatch`, which executes
/// the underlying package manager (pnpm/npm/yarn/bun).
async fn run_package_manager_command(
Expand Down Expand Up @@ -562,13 +560,30 @@ async fn run_package_manager_command(
managed_update(packages, concurrency).await
}

PackageManagerCommand::Outdated {
global: true,
ref packages,
long,
format,
concurrency,
..
} => {
global::outdated::execute(
packages,
long,
format,
concurrency.unwrap_or(DEFAULT_GLOBAL_VIEW_CONCURRENCY),
)
.await
}

// `pm list -g` lists vite-plus-managed globals, not the underlying PM's.
PackageManagerCommand::Pm(vite_pm_cli::cli::PmCommands::List {
global: true,
json,
ref pattern,
..
}) => crate::commands::env::packages::execute(json, pattern.as_deref()).await,
}) => global::packages::execute(json, pattern.as_deref()).await,

cmd => {
commands::prepend_js_runtime_to_path_env(&cwd).await?;
Expand All @@ -583,7 +598,7 @@ async fn managed_install(
force: bool,
concurrency: Option<usize>,
) -> Result<ExitStatus, Error> {
if let Err((package_name, error)) = crate::commands::env::global_install::install(
if let Err((package_name, error)) = global::install::install(
packages,
node,
force,
Expand All @@ -604,106 +619,64 @@ async fn managed_install(

async fn managed_uninstall(packages: &[String], dry_run: bool) -> Result<ExitStatus, Error> {
for package in packages {
if let Err(e) = crate::commands::env::global_install::uninstall(package, dry_run).await {
if let Err(e) = global::install::uninstall(package, dry_run).await {
vite_shared::output::raw_stderr(&format!("Failed to uninstall {package}: {e}"));
return Ok(exit_status(1));
}
}
Ok(ExitStatus::default())
}

fn is_global_package_up_to_date(installed_version: &str, registry_version: &str) -> bool {
installed_version.trim() == registry_version.trim()
}

async fn managed_update(
packages: &[String],
concurrency: Option<usize>,
) -> Result<ExitStatus, Error> {
let all_packages = if packages.is_empty() {
let concurrency = concurrency.unwrap_or(DEFAULT_GLOBAL_INSTALL_CONCURRENCY);
let mut to_update: Vec<String> = Vec::new();

let packages = if packages.is_empty() {
let all = PackageMetadata::list_all().await?;
if all.is_empty() {
vite_shared::output::raw("No global packages installed.");
return Ok(ExitStatus::default());
}
Some(all)
} else {
None
};

let mut to_update: Vec<String> = Vec::new();
let mut skipped = 0usize;

if let Some(all) = all_packages {
for metadata in all {
match global_install::latest_package_version(&metadata.name).await {
Ok(latest_version)
if is_global_package_up_to_date(&metadata.version, &latest_version) =>
{
vite_shared::output::raw(&format!(
"{} is already up to date (v{}).",
metadata.name, metadata.version
));
skipped += 1;
}
Ok(_) => to_update.push(metadata.name.clone()),
Err(e) => {
vite_shared::output::raw_stderr(&format!(
"Could not check latest version for {}: {e}; updating anyway.",
metadata.name
));
to_update.push(metadata.name.clone());
}
}
}
None
} else {
let mut managed_specs = Vec::new();

for package in packages {
if global_install::is_local_package_spec(package) {
// Always update local packages
if global::is_local_package_spec(package) {
to_update.push(package.clone());
continue;
}

let (package_name, _) = global_install::parse_package_spec(package);
if let Some(metadata) = PackageMetadata::load(&package_name).await? {
match global_install::latest_package_version(package).await {
Ok(latest_version)
if is_global_package_up_to_date(&metadata.version, &latest_version) =>
{
vite_shared::output::raw(&format!(
"{} is already up to date (v{}).",
package_name, metadata.version
));
skipped += 1;
continue;
}
Ok(_) => {}
Err(e) => {
vite_shared::output::raw_stderr(&format!(
"Could not check latest version for {package}: {e}; updating anyway."
));
}
}
let (package_name, _) = global::parse_package_spec(package);
if PackageMetadata::load(&package_name).await?.is_some() {
managed_specs.push(package.clone());
} else {
to_update.push(package.clone());
}
to_update.push(package.clone());
}
}

Some(managed_specs)
};
to_update.extend(
global::outdated::get_outdated_packages(packages.as_deref(), concurrency * 3)
.await?
.into_iter()
.map(|package| package.name),
);

if to_update.is_empty() {
if skipped > 0 {
vite_shared::output::raw("All global packages are up to date.");
}
vite_shared::output::raw("All global packages are up to date.");
return Ok(ExitStatus::default());
}

// Call reinstall logic
if let Err((package_name, error)) = global_install::install(
&to_update,
None,
false,
concurrency.unwrap_or(DEFAULT_GLOBAL_INSTALL_CONCURRENCY),
true,
)
.await
if let Err((package_name, error)) =
global::install::install(&to_update, None, false, concurrency, true).await
{
output::error(&format!(
"Failed to update {}: {error}",
Expand Down Expand Up @@ -956,20 +929,10 @@ pub fn try_parse_args_from_with_options(
#[cfg(test)]
mod tests {
use super::{
has_flag_before_terminator, is_global_package_up_to_date, should_force_global_delegate,
has_flag_before_terminator, should_force_global_delegate,
should_suppress_header_for_subcommand,
};

#[test]
fn skips_global_update_when_registry_and_node_versions_match() {
assert!(is_global_package_up_to_date("5.9.3", "5.9.3"));
}

#[test]
fn updates_global_package_when_registry_version_differs_from_installed_version() {
assert!(!is_global_package_up_to_date("5.9.2", "5.9.3"));
}

#[test]
fn detects_flag_before_option_terminator() {
assert!(has_flag_before_terminator(
Expand Down
4 changes: 2 additions & 2 deletions crates/vite_global_cli/src/commands/env/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ mod current;
mod default;
mod doctor;
mod exec;
pub mod global_install;
mod list;
mod list_remote;
mod off;
mod on;
pub mod package_metadata;
pub mod packages;
mod pin;
mod setup;
mod unpin;
Expand All @@ -24,6 +22,8 @@ mod which;

use std::process::ExitStatus;

#[cfg(windows)]
pub(crate) use setup::{cleanup_legacy_windows_shim, get_trampoline_path, remove_or_rename_to_old};
use vite_path::AbsolutePathBuf;

use crate::{
Expand Down
Loading
Loading