mirror of
https://github.com/denoland/deno.git
synced 2025-01-12 09:03:42 -05:00
feat(unstable/npm): support providing npm dist-tag in npm package specifier (#16293)
This commit is contained in:
parent
4c9dd33e27
commit
698ae4bfed
6 changed files with 112 additions and 52 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -2894,9 +2894,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "monch"
|
name = "monch"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "350537f27a69018269e534582e2f1ec532ea7078b06485fdd4db0509bd70feb8"
|
checksum = "c5e2e282addadb529bb31700f7d184797382fa2eb18384986aad78d117eaf0c4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "naga"
|
name = "naga"
|
||||||
|
|
|
@ -85,7 +85,7 @@ jsonc-parser = { version = "=0.21.0", features = ["serde"] }
|
||||||
libc = "=0.2.126"
|
libc = "=0.2.126"
|
||||||
log = { version = "=0.4.17", features = ["serde"] }
|
log = { version = "=0.4.17", features = ["serde"] }
|
||||||
mitata = "=0.0.7"
|
mitata = "=0.0.7"
|
||||||
monch = "=0.2.0"
|
monch = "=0.2.1"
|
||||||
notify = "=5.0.0"
|
notify = "=5.0.0"
|
||||||
once_cell = "=1.14.0"
|
once_cell = "=1.14.0"
|
||||||
os_pipe = "=1.0.1"
|
os_pipe = "=1.0.1"
|
||||||
|
|
|
@ -25,8 +25,8 @@ use super::semver::SpecifierVersionReq;
|
||||||
/// The version matcher used for npm schemed urls is more strict than
|
/// The version matcher used for npm schemed urls is more strict than
|
||||||
/// the one used by npm packages and so we represent either via a trait.
|
/// the one used by npm packages and so we represent either via a trait.
|
||||||
pub trait NpmVersionMatcher {
|
pub trait NpmVersionMatcher {
|
||||||
|
fn tag(&self) -> Option<&str>;
|
||||||
fn matches(&self, version: &NpmVersion) -> bool;
|
fn matches(&self, version: &NpmVersion) -> bool;
|
||||||
fn is_latest(&self) -> bool;
|
|
||||||
fn version_text(&self) -> String;
|
fn version_text(&self) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,15 +124,24 @@ impl std::fmt::Display for NpmPackageReq {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NpmVersionMatcher for NpmPackageReq {
|
impl NpmVersionMatcher for NpmPackageReq {
|
||||||
fn matches(&self, version: &NpmVersion) -> bool {
|
fn tag(&self) -> Option<&str> {
|
||||||
match &self.version_req {
|
match &self.version_req {
|
||||||
Some(req) => req.matches(version),
|
Some(version_req) => version_req.tag(),
|
||||||
None => version.pre.is_empty(),
|
None => Some("latest"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_latest(&self) -> bool {
|
fn matches(&self, version: &NpmVersion) -> bool {
|
||||||
self.version_req.is_none()
|
match self.version_req.as_ref() {
|
||||||
|
Some(req) => {
|
||||||
|
assert_eq!(self.tag(), None);
|
||||||
|
match req.range() {
|
||||||
|
Some(range) => range.satisfies(version),
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => version.pre.is_empty(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn version_text(&self) -> String {
|
fn version_text(&self) -> String {
|
||||||
|
@ -365,10 +374,10 @@ impl NpmResolution {
|
||||||
|
|
||||||
pub async fn add_package_reqs(
|
pub async fn add_package_reqs(
|
||||||
&self,
|
&self,
|
||||||
mut packages: Vec<NpmPackageReq>,
|
mut package_reqs: Vec<NpmPackageReq>,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
// multiple packages are resolved in alphabetical order
|
// multiple packages are resolved in alphabetical order
|
||||||
packages.sort_by(|a, b| a.name.cmp(&b.name));
|
package_reqs.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
|
||||||
// only allow one thread in here at a time
|
// only allow one thread in here at a time
|
||||||
let _permit = self.update_sempahore.acquire().await.unwrap();
|
let _permit = self.update_sempahore.acquire().await.unwrap();
|
||||||
|
@ -377,29 +386,29 @@ impl NpmResolution {
|
||||||
|
|
||||||
// go over the top level packages first, then down the
|
// go over the top level packages first, then down the
|
||||||
// tree one level at a time through all the branches
|
// tree one level at a time through all the branches
|
||||||
for package_ref in packages {
|
for package_req in package_reqs {
|
||||||
if snapshot.package_reqs.contains_key(&package_ref) {
|
if snapshot.package_reqs.contains_key(&package_req) {
|
||||||
// skip analyzing this package, as there's already a matching top level package
|
// skip analyzing this package, as there's already a matching top level package
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// inspect the list of current packages
|
// inspect the list of current packages
|
||||||
if let Some(version) =
|
if let Some(version) =
|
||||||
snapshot.resolve_best_package_version(&package_ref.name, &package_ref)
|
snapshot.resolve_best_package_version(&package_req.name, &package_req)
|
||||||
{
|
{
|
||||||
snapshot.package_reqs.insert(package_ref, version);
|
snapshot.package_reqs.insert(package_req, version);
|
||||||
continue; // done, no need to continue
|
continue; // done, no need to continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// no existing best version, so resolve the current packages
|
// no existing best version, so resolve the current packages
|
||||||
let info = self.api.package_info(&package_ref.name).await?;
|
let info = self.api.package_info(&package_req.name).await?;
|
||||||
let version_and_info = get_resolved_package_version_and_info(
|
let version_and_info = get_resolved_package_version_and_info(
|
||||||
&package_ref.name,
|
&package_req.name,
|
||||||
&package_ref,
|
&package_req,
|
||||||
info,
|
info,
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
let id = NpmPackageId {
|
let id = NpmPackageId {
|
||||||
name: package_ref.name.clone(),
|
name: package_req.name.clone(),
|
||||||
version: version_and_info.version.clone(),
|
version: version_and_info.version.clone(),
|
||||||
};
|
};
|
||||||
let dependencies = version_and_info
|
let dependencies = version_and_info
|
||||||
|
@ -418,12 +427,12 @@ impl NpmResolution {
|
||||||
);
|
);
|
||||||
snapshot
|
snapshot
|
||||||
.packages_by_name
|
.packages_by_name
|
||||||
.entry(package_ref.name.clone())
|
.entry(package_req.name.clone())
|
||||||
.or_default()
|
.or_default()
|
||||||
.push(version_and_info.version.clone());
|
.push(version_and_info.version.clone());
|
||||||
snapshot
|
snapshot
|
||||||
.package_reqs
|
.package_reqs
|
||||||
.insert(package_ref, version_and_info.version);
|
.insert(package_req, version_and_info.version);
|
||||||
}
|
}
|
||||||
|
|
||||||
// now go down through the dependencies by tree depth
|
// now go down through the dependencies by tree depth
|
||||||
|
@ -573,8 +582,8 @@ fn get_resolved_package_version_and_info(
|
||||||
parent: Option<&NpmPackageId>,
|
parent: Option<&NpmPackageId>,
|
||||||
) -> Result<VersionAndInfo, AnyError> {
|
) -> Result<VersionAndInfo, AnyError> {
|
||||||
let mut maybe_best_version: Option<VersionAndInfo> = None;
|
let mut maybe_best_version: Option<VersionAndInfo> = None;
|
||||||
if version_matcher.is_latest() {
|
if let Some(tag) = version_matcher.tag() {
|
||||||
if let Some(version) = info.dist_tags.get("latest") {
|
if let Some(version) = info.dist_tags.get(tag) {
|
||||||
match info.versions.get(version) {
|
match info.versions.get(version) {
|
||||||
Some(info) => {
|
Some(info) => {
|
||||||
return Ok(VersionAndInfo {
|
return Ok(VersionAndInfo {
|
||||||
|
@ -584,26 +593,29 @@ fn get_resolved_package_version_and_info(
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
bail!(
|
bail!(
|
||||||
"Could not find version '{}' referenced in dist-tag 'latest'.",
|
"Could not find version '{}' referenced in dist-tag '{}'.",
|
||||||
version,
|
version,
|
||||||
|
tag,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
bail!("Could not find dist-tag '{}'.", tag,)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
for (_, version_info) in info.versions.into_iter() {
|
||||||
for (_, version_info) in info.versions.into_iter() {
|
let version = NpmVersion::parse(&version_info.version)?;
|
||||||
let version = NpmVersion::parse(&version_info.version)?;
|
if version_matcher.matches(&version) {
|
||||||
if version_matcher.matches(&version) {
|
let is_best_version = maybe_best_version
|
||||||
let is_best_version = maybe_best_version
|
.as_ref()
|
||||||
.as_ref()
|
.map(|best_version| best_version.version.cmp(&version).is_lt())
|
||||||
.map(|best_version| best_version.version.cmp(&version).is_lt())
|
.unwrap_or(true);
|
||||||
.unwrap_or(true);
|
if is_best_version {
|
||||||
if is_best_version {
|
maybe_best_version = Some(VersionAndInfo {
|
||||||
maybe_best_version = Some(VersionAndInfo {
|
version,
|
||||||
version,
|
info: version_info,
|
||||||
info: version_info,
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,12 +174,12 @@ pub struct NpmVersionReq {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NpmVersionMatcher for NpmVersionReq {
|
impl NpmVersionMatcher for NpmVersionReq {
|
||||||
fn matches(&self, version: &NpmVersion) -> bool {
|
fn tag(&self) -> Option<&str> {
|
||||||
self.satisfies(version)
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_latest(&self) -> bool {
|
fn matches(&self, version: &NpmVersion) -> bool {
|
||||||
false
|
self.satisfies(version)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn version_text(&self) -> String {
|
fn version_text(&self) -> String {
|
||||||
|
|
|
@ -10,13 +10,18 @@ use super::errors::with_failure_handling;
|
||||||
use super::range::Partial;
|
use super::range::Partial;
|
||||||
use super::range::VersionRange;
|
use super::range::VersionRange;
|
||||||
use super::range::XRange;
|
use super::range::XRange;
|
||||||
use super::NpmVersion;
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
enum SpecifierVersionReqInner {
|
||||||
|
Range(VersionRange),
|
||||||
|
Tag(String),
|
||||||
|
}
|
||||||
|
|
||||||
/// Version requirement found in npm specifiers.
|
/// Version requirement found in npm specifiers.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct SpecifierVersionReq {
|
pub struct SpecifierVersionReq {
|
||||||
raw_text: String,
|
raw_text: String,
|
||||||
range: VersionRange,
|
inner: SpecifierVersionReqInner,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for SpecifierVersionReq {
|
impl std::fmt::Display for SpecifierVersionReq {
|
||||||
|
@ -32,15 +37,46 @@ impl SpecifierVersionReq {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn matches(&self, version: &NpmVersion) -> bool {
|
pub fn range(&self) -> Option<&VersionRange> {
|
||||||
self.range.satisfies(version)
|
match &self.inner {
|
||||||
|
SpecifierVersionReqInner::Range(range) => Some(range),
|
||||||
|
SpecifierVersionReqInner::Tag(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tag(&self) -> Option<&str> {
|
||||||
|
match &self.inner {
|
||||||
|
SpecifierVersionReqInner::Range(_) => None,
|
||||||
|
SpecifierVersionReqInner::Tag(tag) => Some(tag.as_str()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_npm_specifier(input: &str) -> ParseResult<SpecifierVersionReq> {
|
fn parse_npm_specifier(input: &str) -> ParseResult<SpecifierVersionReq> {
|
||||||
map(version_range, |range| SpecifierVersionReq {
|
map_res(version_range, |result| {
|
||||||
raw_text: input.to_string(),
|
let (new_input, range_result) = match result {
|
||||||
range,
|
Ok((input, range)) => (input, Ok(range)),
|
||||||
|
// use an empty string because we'll consider the tag
|
||||||
|
Err(err) => ("", Err(err)),
|
||||||
|
};
|
||||||
|
Ok((
|
||||||
|
new_input,
|
||||||
|
SpecifierVersionReq {
|
||||||
|
raw_text: input.to_string(),
|
||||||
|
inner: match range_result {
|
||||||
|
Ok(range) => SpecifierVersionReqInner::Range(range),
|
||||||
|
Err(err) => {
|
||||||
|
// npm seems to be extremely lax on what it supports for a dist-tag (any non-valid semver range),
|
||||||
|
// so just make any error here be a dist tag unless it starts or ends with whitespace
|
||||||
|
if input.trim() != input {
|
||||||
|
return Err(err);
|
||||||
|
} else {
|
||||||
|
SpecifierVersionReqInner::Tag(input.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
))
|
||||||
})(input)
|
})(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,6 +200,8 @@ fn part(input: &str) -> ParseResult<&str> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::npm::semver::NpmVersion;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
struct VersionReqTester(SpecifierVersionReq);
|
struct VersionReqTester(SpecifierVersionReq);
|
||||||
|
@ -174,7 +212,11 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches(&self, version: &str) -> bool {
|
fn matches(&self, version: &str) -> bool {
|
||||||
self.0.matches(&NpmVersion::parse(version).unwrap())
|
self
|
||||||
|
.0
|
||||||
|
.range()
|
||||||
|
.map(|r| r.satisfies(&NpmVersion::parse(version).unwrap()))
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,4 +292,10 @@ mod tests {
|
||||||
assert!(!tester.matches("0.1.0"));
|
assert!(!tester.matches("0.1.0"));
|
||||||
assert!(!tester.matches("1.0.0"));
|
assert!(!tester.matches("1.0.0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_tag() {
|
||||||
|
let latest_tag = SpecifierVersionReq::parse("latest").unwrap();
|
||||||
|
assert_eq!(latest_tag.tag().unwrap(), "latest");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
4
cli/tests/testdata/npm/dual_cjs_esm/main.ts
vendored
4
cli/tests/testdata/npm/dual_cjs_esm/main.ts
vendored
|
@ -1,5 +1,5 @@
|
||||||
import { getKind } from "npm:@denotest/dual-cjs-esm";
|
import { getKind } from "npm:@denotest/dual-cjs-esm@latest"; // test out @latest dist tag
|
||||||
import * as cjs from "npm:@denotest/dual-cjs-esm/cjs/main.cjs";
|
import * as cjs from "npm:@denotest/dual-cjs-esm@latest/cjs/main.cjs";
|
||||||
|
|
||||||
console.log(getKind());
|
console.log(getKind());
|
||||||
console.log(cjs.getKind());
|
console.log(cjs.getKind());
|
||||||
|
|
Loading…
Reference in a new issue