1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-18 13:22:55 -05:00

fix(outdated): ensure "Latest" version is greater than "Update" version (#27390)

Fixes #27038.

Previously, for NPM packages the latest version was the version with the
"latest" tag. For JSR packages, the latest version was the greatest
version that matched a `*` version requirement. Unfortunately, that
doesn't work well with pre-release versions.

This PR changes it so that the latest version is always > the currently
requested version.
For NPM: if "latest" tag > current then "latest" tag; otherwise the
greatest version that is >= current
For JSR: greatest version >= current

This is the most reasonable behavior I could come up with. For example,

```
versions:
2.0.0-beta.2
2.0.0-beta.1
1.0.0 => "latest" tag

with a version req `^2.0.0-beta.1`

previously:
"Update" column => 2.0.0-beta.2
"Latest" column => 1.0.0

now:
"Update" column => 2.0.0-beta.2
"Latest" column => 2.0.0-beta.2
```
This commit is contained in:
Nathan Whitaker 2024-12-17 15:56:03 -08:00 committed by GitHub
parent 2820ba1e22
commit 9d7174e434
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 198 additions and 26 deletions

View file

@ -3,7 +3,6 @@
use std::borrow::Cow;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use deno_ast::ModuleSpecifier;
@ -28,6 +27,7 @@ use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageNv;
use deno_semver::package::PackageReq;
use deno_semver::package::PackageReqReference;
use deno_semver::Version;
use deno_semver::VersionReq;
use import_map::ImportMap;
use import_map::ImportMapWithDiagnostics;
@ -42,6 +42,7 @@ use crate::jsr::JsrFetchResolver;
use crate::module_loader::ModuleLoadPreparer;
use crate::npm::CliNpmResolver;
use crate::npm::NpmFetchResolver;
use crate::util::sync::AtomicFlag;
use super::ConfigUpdater;
@ -447,7 +448,7 @@ pub struct DepManager {
pending_changes: Vec<Change>,
dependencies_resolved: AtomicBool,
dependencies_resolved: AtomicFlag,
module_load_preparer: Arc<ModuleLoadPreparer>,
// TODO(nathanwhit): probably shouldn't be pub
pub(crate) jsr_fetch_resolver: Arc<JsrFetchResolver>,
@ -489,7 +490,7 @@ impl DepManager {
resolved_versions: Vec::new(),
latest_versions: Vec::new(),
jsr_fetch_resolver,
dependencies_resolved: AtomicBool::new(false),
dependencies_resolved: AtomicFlag::lowered(),
module_load_preparer,
npm_fetch_resolver,
npm_resolver,
@ -530,10 +531,7 @@ impl DepManager {
}
async fn run_dependency_resolution(&self) -> Result<(), AnyError> {
if self
.dependencies_resolved
.load(std::sync::atomic::Ordering::Relaxed)
{
if self.dependencies_resolved.is_raised() {
return Ok(());
}
@ -556,9 +554,7 @@ impl DepManager {
}
DepKind::Jsr => graph.packages.mappings().contains_key(&dep.req),
}) {
self
.dependencies_resolved
.store(true, std::sync::atomic::Ordering::Relaxed);
self.dependencies_resolved.raise();
graph_permit.commit();
return Ok(());
}
@ -613,6 +609,7 @@ impl DepManager {
)
.await?;
self.dependencies_resolved.raise();
graph_permit.commit();
Ok(())
@ -655,10 +652,6 @@ impl DepManager {
if self.latest_versions.len() == self.deps.len() {
return Ok(self.latest_versions.clone());
}
let latest_tag_req = deno_semver::VersionReq::from_raw_text_and_inner(
"latest".into(),
deno_semver::RangeSetOrTag::Tag("latest".into()),
);
let mut latest_versions = Vec::with_capacity(self.deps.len());
let npm_sema = Semaphore::new(32);
@ -670,14 +663,25 @@ impl DepManager {
DepKind::Npm => futs.push_back(
async {
let semver_req = &dep.req;
let latest_req = PackageReq {
name: dep.req.name.clone(),
version_req: latest_tag_req.clone(),
};
let _permit = npm_sema.acquire().await;
let semver_compatible =
self.npm_fetch_resolver.req_to_nv(semver_req).await;
let latest = self.npm_fetch_resolver.req_to_nv(&latest_req).await;
let info =
self.npm_fetch_resolver.package_info(&semver_req.name).await;
let latest = info
.and_then(|info| {
let latest_tag = info.dist_tags.get("latest")?;
let lower_bound = &semver_compatible.as_ref()?.version;
if latest_tag > lower_bound {
Some(latest_tag.clone())
} else {
latest_version(Some(latest_tag), info.versions.keys())
}
})
.map(|version| PackageNv {
name: semver_req.name.clone(),
version,
});
PackageLatestVersion {
latest,
semver_compatible,
@ -688,14 +692,29 @@ impl DepManager {
DepKind::Jsr => futs.push_back(
async {
let semver_req = &dep.req;
let latest_req = PackageReq {
name: dep.req.name.clone(),
version_req: deno_semver::WILDCARD_VERSION_REQ.clone(),
};
let _permit = jsr_sema.acquire().await;
let semver_compatible =
self.jsr_fetch_resolver.req_to_nv(semver_req).await;
let latest = self.jsr_fetch_resolver.req_to_nv(&latest_req).await;
let info =
self.jsr_fetch_resolver.package_info(&semver_req.name).await;
let latest = info
.and_then(|info| {
let lower_bound = &semver_compatible.as_ref()?.version;
latest_version(
Some(lower_bound),
info.versions.iter().filter_map(|(version, version_info)| {
if !version_info.yanked {
Some(version)
} else {
None
}
}),
)
})
.map(|version| PackageNv {
name: semver_req.name.clone(),
version,
});
PackageLatestVersion {
latest,
semver_compatible,
@ -893,3 +912,18 @@ fn parse_req_reference(
DepKind::Jsr => JsrPackageReqReference::from_str(input)?.into_inner(),
})
}
fn latest_version<'a>(
start: Option<&Version>,
versions: impl IntoIterator<Item = &'a Version>,
) -> Option<Version> {
let mut best = start;
for version in versions {
match best {
Some(best_version) if version > best_version => best = Some(version),
None => best = Some(version),
_ => {}
}
}
best.cloned()
}

View file

@ -0,0 +1 @@
export const foo = 1;

View file

@ -0,0 +1,5 @@
{
"exports": {
".": "mod.ts"
}
}

View file

@ -0,0 +1 @@
export const foo = 1;

View file

@ -0,0 +1,5 @@
{
"exports": {
".": "mod.ts"
}
}

View file

@ -0,0 +1,6 @@
{
"versions": {
"2.0.0-beta.1": {},
"2.0.0-beta.2": {}
}
}

View file

@ -0,0 +1 @@
export const foo = 1;

View file

@ -0,0 +1,3 @@
{
"exports": {}
}

View file

@ -0,0 +1 @@
export const foo = 1;

View file

@ -0,0 +1,5 @@
{
"exports": {
".": "mod.ts"
}
}

View file

@ -0,0 +1 @@
export const foo = 1;

View file

@ -0,0 +1,5 @@
{
"exports": {
".": "mod.ts"
}
}

View file

@ -0,0 +1,7 @@
{
"versions": {
"1.0.0": {},
"2.0.0-beta.1": {},
"2.0.0-beta.2": {}
}
}

View file

@ -0,0 +1,7 @@
{
"name": "@denotest/has-pre-release",
"version": "1.0.0",
"publishConfig": {
"tag": "latest"
}
}

View file

@ -0,0 +1,4 @@
{
"name": "@denotest/has-pre-release",
"version": "2.0.0-beta.1"
}

View file

@ -0,0 +1,4 @@
{
"name": "@denotest/has-pre-release",
"version": "2.0.0-beta.2"
}

View file

@ -0,0 +1,21 @@
{
"tempDir": true,
"steps": [
{
"args": "i",
"output": "[WILDCARD]"
},
{
"args": "outdated",
"output": "outdated.out"
},
{
"args": "outdated --compatible",
"output": "outdated.out"
},
{
"args": "outdated --update --latest",
"output": "update.out"
}
]
}

View file

@ -0,0 +1,7 @@
{
"imports": {
"@denotest/npm-has-pre-release": "npm:@denotest/has-pre-release@^2.0.0-beta.1",
"@denotest/jsr-has-pre-release": "jsr:@denotest/has-pre-release@^2.0.0-beta.1",
"@denotest/has-only-pre-release": "jsr:@denotest/has-only-pre-release@^2.0.0-beta.1"
}
}

View file

@ -0,0 +1,28 @@
{
"version": "4",
"specifiers": {
"jsr:@denotest/has-only-pre-release@^2.0.0-beta.1": "2.0.0-beta.1",
"jsr:@denotest/has-pre-release@^2.0.0-beta.1": "2.0.0-beta.1",
"npm:@denotest/has-pre-release@^2.0.0-beta.1": "2.0.0-beta.1"
},
"jsr": {
"@denotest/has-only-pre-release@2.0.0-beta.1": {
"integrity": "43fd680ea94bb5db5fe1a2d86101c47d0e2cc77323b881755cea9a0372e49537"
},
"@denotest/has-pre-release@2.0.0-beta.1": {
"integrity": "43fd680ea94bb5db5fe1a2d86101c47d0e2cc77323b881755cea9a0372e49537"
}
},
"npm": {
"@denotest/has-pre-release@2.0.0-beta.1": {
"integrity": "sha512-K1fHe1L2EUSLgijtzzALNpkkIO0SrX3z+IXvVjjOIE8HKd4T7lkpzDdoUp+WllwS3KXmuJh+9vIfY5lFp38pew=="
}
},
"workspace": {
"dependencies": [
"jsr:@denotest/has-only-pre-release@^2.0.0-beta.1",
"jsr:@denotest/has-pre-release@^2.0.0-beta.1",
"npm:@denotest/has-pre-release@^2.0.0-beta.1"
]
}
}

View file

@ -0,0 +1,11 @@
┌────────────────────────────────────┬──────────────┬──────────────┬──────────────┐
│ Package │ Current │ Update │ Latest │
├────────────────────────────────────┼──────────────┼──────────────┼──────────────┤
│ jsr:@denotest/has-only-pre-release │ 2.0.0-beta.1 │ 2.0.0-beta.2 │ 2.0.0-beta.2 │
├────────────────────────────────────┼──────────────┼──────────────┼──────────────┤
│ jsr:@denotest/has-pre-release │ 2.0.0-beta.1 │ 2.0.0-beta.2 │ 2.0.0-beta.2 │
├────────────────────────────────────┼──────────────┼──────────────┼──────────────┤
│ npm:@denotest/has-pre-release │ 2.0.0-beta.1 │ 2.0.0-beta.2 │ 2.0.0-beta.2 │
└────────────────────────────────────┴──────────────┴──────────────┴──────────────┘
[WILDCARD]

View file

@ -0,0 +1,5 @@
[WILDCARD]
Updated 3 dependencies:
- jsr:@denotest/has-only-pre-release 2.0.0-beta.1 -> 2.0.0-beta.2
- jsr:@denotest/has-pre-release 2.0.0-beta.1 -> 2.0.0-beta.2
- npm:@denotest/has-pre-release 2.0.0-beta.1 -> 2.0.0-beta.2

View file

@ -267,6 +267,7 @@ fn get_npm_package(
let mut tarballs = HashMap::new();
let mut versions = serde_json::Map::new();
let mut latest_version = semver::Version::parse("0.0.0").unwrap();
let mut dist_tags = serde_json::Map::new();
for entry in fs::read_dir(&package_folder)? {
let entry = entry?;
let file_type = entry.file_type()?;
@ -345,6 +346,14 @@ fn get_npm_package(
}
}
if let Some(publish_config) = version_info.get("publishConfig") {
if let Some(tag) = publish_config.get("tag") {
if let Some(tag) = tag.as_str() {
dist_tags.insert(tag.to_string(), version.clone().into());
}
}
}
versions.insert(version.clone(), version_info.into());
let version = semver::Version::parse(&version)?;
if version.cmp(&latest_version).is_gt() {
@ -352,8 +361,9 @@ fn get_npm_package(
}
}
let mut dist_tags = serde_json::Map::new();
dist_tags.insert("latest".to_string(), latest_version.to_string().into());
if !dist_tags.contains_key("latest") {
dist_tags.insert("latest".to_string(), latest_version.to_string().into());
}
// create the registry file for this package
let mut registry_file = serde_json::Map::new();