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
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,27 @@ name = "command_outdated_global"
vp = "global"
skip-platforms = ["windows"]
steps = [
{ argv = ["vp", "install", "-g", "testnpm2@1.0.0"], comment = "should prepare global outdated package", snapshot = false, continue-on-failure = true },
{ argv = ["vp", "install", "-g", "testnpm2@1.0.0"], comment = "should prepare a version-pinned global package", snapshot = false, continue-on-failure = true },
{ argv = ["vp", "outdated", "definitely-not-installed-vite-plus-snap-pkg", "-g", "--format", "json"], comment = "should support empty global json output", continue-on-failure = true },
{ argv = ["vp", "outdated", "testnpm2", "-g", "--format", "json"], comment = "should report a pinned package without a wanted update", continue-on-failure = true },
{ argv = ["vp", "outdated", "testnpm2", "-g", "--format", "list"], comment = "should render the newer latest as a hint in list format", continue-on-failure = true },
{ tty = false, argv = ["vp", "update", "-g"], comment = "should not move a pinned package to latest", continue-on-failure = true },
{ argv = ["vpt", "json-edit", "$VP_HOME/packages/testnpm2.json", "versionSpec", "no-such-tag"], comment = "should warn and skip when the recorded version spec no longer resolves", snapshot = false },
{ tty = false, argv = ["vp", "update", "-g"], continue-on-failure = true },
{ argv = ["vpt", "json-edit", "$VP_HOME/packages/testnpm2.json", "versionSpec", "null"], comment = "should follow latest again once the recorded version spec is cleared", snapshot = false },
{ argv = ["vp", "outdated", "testnpm2", "-g", "--format", "json"], comment = "should support global json output", continue-on-failure = true },
{ argv = ["vp", "outdated", "testnpm2", "-g", "--format", "list", "--concurrency", "5"], comment = "should support global list output", continue-on-failure = true },
{ argv = ["vpt", "json-edit", "$VP_HOME/packages/testnpm2.json", "versionSpec", "1.0.0"], comment = "should override a recorded version spec with --latest", snapshot = false },
{ tty = false, argv = ["vp", "update", "-g", "--latest"], continue-on-failure = true },
{ argv = ["vpt", "grep-file", "$VP_HOME/packages/testnpm2.json", "versionSpec"], comment = "should clear the recorded version spec after --latest (grep-file prints missing)", continue-on-failure = true },
{ argv = ["vpt", "json-edit", "$VP_HOME/packages/testnpm2.json", "versionSpec", "1.0.1"], comment = "should clear a recorded version spec with --latest even without a reinstall", snapshot = false },
{ tty = false, argv = ["vp", "update", "-g", "--latest"], continue-on-failure = true },
{ argv = ["vpt", "grep-file", "$VP_HOME/packages/testnpm2.json", "versionSpec"], comment = "should have removed the pin from the up-to-date package (grep-file prints missing)", continue-on-failure = true },
{ argv = ["vpt", "json-edit", "$VP_HOME/packages/testnpm2.json", "versionSpec", "1.0.0"], comment = "should persist an explicit spec switch without a reinstall", snapshot = false },
{ tty = false, argv = ["vp", "update", "-g", "testnpm2@1.0.1"], continue-on-failure = true },
{ argv = ["vpt", "grep-file", "$VP_HOME/packages/testnpm2.json", 'versionSpec": "1.0.1'], continue-on-failure = true },
{ tty = false, argv = ["vp", "update", "-g", "testnpm2@no-such-tag"], comment = "should not persist an explicit spec that fails to resolve", continue-on-failure = true },
{ argv = ["vpt", "grep-file", "$VP_HOME/packages/testnpm2.json", 'versionSpec": "1.0.1'], continue-on-failure = true },
]
after = [
{ argv = ["vp", "remove", "-g", "testnpm2"], continue-on-failure = true },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## `vp install -g testnpm2@1.0.0`

should prepare global outdated package
should prepare a version-pinned global package


## `vp outdated definitely-not-installed-vite-plus-snap-pkg -g --format json`
Expand All @@ -13,6 +13,62 @@ should support empty global json output
{}
```

## `vp outdated testnpm2 -g --format json`

should report a pinned package without a wanted update

**Exit code:** 1

```
{
"testnpm2": {
"current": "1.0.0",
"wanted": "1.0.0",
"latest": "1.0.1",
"dependent": "global",
"location": "<home>/.vite-plus/packages/testnpm2#<uuid>/lib/node_modules/testnpm2"
}
}
```

## `vp outdated testnpm2 -g --format list`

should render the newer latest as a hint in list format

**Exit code:** 1

```
testnpm2 (global)
1.0.0 => 1.0.0 (latest: 1.0.1)
```

## `vp update -g`

should not move a pinned package to latest

```
All global packages are up to date.
```

## `vpt json-edit $VP_HOME/packages/testnpm2.json versionSpec no-such-tag`

should warn and skip when the recorded version spec no longer resolves


## `vp update -g`

**Exit code:** 1

```
All global packages are up to date.
warn: npm view failed for testnpm2@no-such-tag: npm error code E404; skipping
```

## `vpt json-edit $VP_HOME/packages/testnpm2.json versionSpec null`

should follow latest again once the recorded version spec is cleared


## `vp outdated testnpm2 -g --format json`

should support global json output
Expand Down Expand Up @@ -41,3 +97,82 @@ should support global list output
testnpm2 (global)
1.0.0 => 1.0.1
```

## `vpt json-edit $VP_HOME/packages/testnpm2.json versionSpec 1.0.0`

should override a recorded version spec with --latest


## `vp update -g --latest`

```
info: Updating 1 global package with Node.js <version>
✓ Updated testnpm2 to 1.0.1
```

## `vpt grep-file $VP_HOME/packages/testnpm2.json versionSpec`

should clear the recorded version spec after --latest (grep-file prints missing)

**Exit code:** 1

```
<home>/.vite-plus/packages/testnpm2.json: missing "versionSpec"
pattern not found
```

## `vpt json-edit $VP_HOME/packages/testnpm2.json versionSpec 1.0.1`

should clear a recorded version spec with --latest even without a reinstall


## `vp update -g --latest`

```
All global packages are up to date.
```

## `vpt grep-file $VP_HOME/packages/testnpm2.json versionSpec`

should have removed the pin from the up-to-date package (grep-file prints missing)

**Exit code:** 1

```
<home>/.vite-plus/packages/testnpm2.json: missing "versionSpec"
pattern not found
```

## `vpt json-edit $VP_HOME/packages/testnpm2.json versionSpec 1.0.0`

should persist an explicit spec switch without a reinstall


## `vp update -g testnpm2@1.0.1`

```
All global packages are up to date.
```

## `vpt grep-file $VP_HOME/packages/testnpm2.json 'versionSpec": "1.0.1'`

```
<home>/.vite-plus/packages/testnpm2.json: found "versionSpec\": \"1.0.1"
```

## `vp update -g testnpm2@no-such-tag`

should not persist an explicit spec that fails to resolve

**Exit code:** 1

```
All global packages are up to date.
warn: npm view failed for testnpm2@no-such-tag: npm error code E404; skipping
```

## `vpt grep-file $VP_HOME/packages/testnpm2.json 'versionSpec": "1.0.1'`

```
<home>/.vite-plus/packages/testnpm2.json: found "versionSpec\": \"1.0.1"
```
94 changes: 84 additions & 10 deletions crates/vite_global_cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ async fn run_package_manager_command(
PackageManagerCommand::Update {
global: true,
ref packages,
latest,
concurrency,
reinstall_node_mismatch,
ignore_node_mismatch,
Expand All @@ -592,8 +593,14 @@ async fn run_package_manager_command(
);
return Ok(exit_status(1));
}
managed_update(packages, concurrency, reinstall_node_mismatch, ignore_node_mismatch)
.await
managed_update(
packages,
latest,
concurrency,
reinstall_node_mismatch,
ignore_node_mismatch,
)
.await
}

PackageManagerCommand::Outdated {
Expand Down Expand Up @@ -684,13 +691,22 @@ struct NodeMismatchPackage {

async fn managed_update(
packages: &[String],
latest: bool,
concurrency: Option<usize>,
reinstall_node_mismatch: bool,
ignore_node_mismatch: bool,
) -> Result<ExitStatus, Error> {
let concurrency = concurrency.unwrap_or(DEFAULT_GLOBAL_INSTALL_CONCURRENCY);
let mut to_update: Vec<String> = Vec::new();
let mut node_mismatches: Vec<NodeMismatchPackage> = Vec::new();
// Recorded version-spec changes this update implies: `--latest` clears
// specs, an explicit `pkg@spec` argument replaces the stored one.
// Reinstalls record the new spec on their own, but packages already at
// the target version are not reinstalled and must be rewritten here.
// Entries are `(package name, new spec, registry query spec)`; the query
// spec ties each rewrite to its lookup so failed resolutions never
// persist a policy the update could not act on.
let mut spec_rewrites: Vec<(String, Option<String>, String)> = Vec::new();
let current_node_version;

let packages = if packages.is_empty() {
Expand All @@ -702,10 +718,13 @@ async fn managed_update(
current_node_version = get_current_node_version().await?;

for metadata in &all {
if latest && metadata.version_spec.is_some() {
spec_rewrites.push((metadata.name.clone(), None, metadata.name.clone()));
}
if !is_same_node_version(&metadata.platform.node, &current_node_version) {
node_mismatches.push(NodeMismatchPackage {
name: metadata.name.clone(),
spec: metadata.name.clone(),
spec: if latest { metadata.name.clone() } else { metadata.update_spec() },
installed_node: metadata.platform.node.clone(),
});
}
Expand All @@ -724,12 +743,32 @@ async fn managed_update(
}

// It is not a local package, so `parse_package_spec` there won't return `Err()`
let (package_name, _) = global::parse_package_spec(package).unwrap();
let (package_name, version_spec) = global::parse_package_spec(package).unwrap();
if let Some(metadata) = PackageMetadata::load(&package_name).await? {
if version_spec.is_some() {
// An explicit spec replaces the recorded one even when
// the installed version already satisfies it.
let new_spec = global::update_version_spec(package);
if new_spec != metadata.version_spec {
spec_rewrites.push((package_name.clone(), new_spec, package.clone()));
}
} else if latest && metadata.version_spec.is_some() {
// `--latest` applies to bare names only; explicit specs win.
spec_rewrites.push((package_name.clone(), None, package_name.clone()));
}
if !is_same_node_version(&metadata.platform.node, &current_node_version) {
// Match the spec `get_outdated_packages` resolves for this
// package, so the dedup against outdated results holds.
let spec = if version_spec.is_some() {
package.clone()
} else if latest {
package_name.clone()
} else {
metadata.update_spec()
};
node_mismatches.push(NodeMismatchPackage {
name: package_name,
spec: package.clone(),
spec,
installed_node: metadata.platform.node,
});
}
Expand All @@ -742,15 +781,50 @@ async fn managed_update(
Some(managed_specs)
};

let outdated = global::outdated::get_outdated_packages(
let report = global::outdated::get_outdated_packages(
&packages.unwrap_or_default(),
concurrency * 3,
true,
latest,
global::outdated::LookupMode::WantedOnly,
)
.await?;
to_update.extend(outdated.into_iter().map(|package| package.spec.unwrap_or(package.name)));
for (_, message) in &report.failures {
output::warn(&format!("{message}; skipping"));
}
// Skipped lookups make the update incomplete; keep going but exit
// nonzero so scripts can tell.
let incomplete = !report.failures.is_empty();
to_update.extend(
report
.outdated
.into_iter()
// A newer `latest` alone (e.g. a version-pinned package) is not
// updatable; only a newer wanted version is.
.filter(|package| package.wanted != package.current)
.map(|package| package.spec.unwrap_or(package.name)),
Comment thread
TheAlexLichter marked this conversation as resolved.
);

let to_update_set = to_update.iter().map(String::as_str).collect::<HashSet<_>>();

// Persist rewrites only for packages that resolved but need no reinstall:
// reinstalled packages record their spec through the successful install
// (and keep the old one if it fails), and a failed lookup must not
// persist a policy the update could not act on.
let failed_specs =
report.failures.iter().map(|(spec, _)| spec.as_str()).collect::<HashSet<_>>();
for (package_name, new_spec, query_spec) in &spec_rewrites {
if failed_specs.contains(query_spec.as_str()) || to_update_set.contains(query_spec.as_str())
{
continue;
}
if let Some(mut metadata) = PackageMetadata::load(package_name).await? {
if metadata.version_spec != *new_spec {
metadata.version_spec = new_spec.clone();
metadata.save().await?;
}
}
}

node_mismatches.retain(|package| !to_update_set.contains(package.spec.as_str()));

if should_reinstall_node_mismatches(
Expand All @@ -764,7 +838,7 @@ async fn managed_update(

if to_update.is_empty() {
vite_shared::output::raw("All global packages are up to date.");
return Ok(ExitStatus::default());
return Ok(if incomplete { exit_status(1) } else { ExitStatus::default() });
}

// Call reinstall logic
Expand All @@ -786,7 +860,7 @@ async fn managed_update(
));
return Ok(exit_status(1));
}
Ok(ExitStatus::default())
Ok(if incomplete { exit_status(1) } else { ExitStatus::default() })
}

async fn get_current_node_version() -> Result<String, Error> {
Expand Down
Loading
Loading