mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 21:50:00 -05:00
refactor(semver): generalize semver related structs (#17605)
- Generalizes the npm version code (ex. `NpmVersion` -> `Version`, `NpmVersionReq` -> `VersionReq`). This is a slow refactor towards extracting out this code for deno specifiers and better usage in deno_graph. - Removes `SpecifierVersionReq`. Consolidates `NpmVersionReq` and `SpecifierVersionReq` to just `VersionReq` - Removes `NpmVersionMatcher`. This now just looks at `VersionReq`. - Paves the way to allow us to create `NpmPackageReference`'s from a package.json's dependencies/dev dependencies (`VersionReq::parse_from_npm`).
This commit is contained in:
parent
e85ca8be0d
commit
600fff79cd
14 changed files with 731 additions and 740 deletions
|
@ -18,6 +18,7 @@ mod npm;
|
||||||
mod ops;
|
mod ops;
|
||||||
mod proc_state;
|
mod proc_state;
|
||||||
mod resolver;
|
mod resolver;
|
||||||
|
mod semver;
|
||||||
mod standalone;
|
mod standalone;
|
||||||
mod tools;
|
mod tools;
|
||||||
mod tsc;
|
mod tsc;
|
||||||
|
|
|
@ -17,13 +17,13 @@ use deno_core::url::Url;
|
||||||
use crate::args::CacheSetting;
|
use crate::args::CacheSetting;
|
||||||
use crate::cache::DenoDir;
|
use crate::cache::DenoDir;
|
||||||
use crate::http_util::HttpClient;
|
use crate::http_util::HttpClient;
|
||||||
|
use crate::semver::Version;
|
||||||
use crate::util::fs::canonicalize_path;
|
use crate::util::fs::canonicalize_path;
|
||||||
use crate::util::fs::hard_link_dir_recursive;
|
use crate::util::fs::hard_link_dir_recursive;
|
||||||
use crate::util::path::root_url_to_safe_local_dirname;
|
use crate::util::path::root_url_to_safe_local_dirname;
|
||||||
use crate::util::progress_bar::ProgressBar;
|
use crate::util::progress_bar::ProgressBar;
|
||||||
|
|
||||||
use super::registry::NpmPackageVersionDistInfo;
|
use super::registry::NpmPackageVersionDistInfo;
|
||||||
use super::semver::NpmVersion;
|
|
||||||
use super::tarball::verify_and_extract_tarball;
|
use super::tarball::verify_and_extract_tarball;
|
||||||
|
|
||||||
/// For some of the tests, we want downloading of packages
|
/// For some of the tests, we want downloading of packages
|
||||||
|
@ -35,7 +35,7 @@ pub fn should_sync_download() -> bool {
|
||||||
const NPM_PACKAGE_SYNC_LOCK_FILENAME: &str = ".deno_sync_lock";
|
const NPM_PACKAGE_SYNC_LOCK_FILENAME: &str = ".deno_sync_lock";
|
||||||
|
|
||||||
pub fn with_folder_sync_lock(
|
pub fn with_folder_sync_lock(
|
||||||
package: (&str, &NpmVersion),
|
package: (&str, &Version),
|
||||||
output_folder: &Path,
|
output_folder: &Path,
|
||||||
action: impl FnOnce() -> Result<(), AnyError>,
|
action: impl FnOnce() -> Result<(), AnyError>,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
@ -108,7 +108,7 @@ pub fn with_folder_sync_lock(
|
||||||
|
|
||||||
pub struct NpmPackageCacheFolderId {
|
pub struct NpmPackageCacheFolderId {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub version: NpmVersion,
|
pub version: Version,
|
||||||
/// Peer dependency resolution may require us to have duplicate copies
|
/// Peer dependency resolution may require us to have duplicate copies
|
||||||
/// of the same package.
|
/// of the same package.
|
||||||
pub copy_index: usize,
|
pub copy_index: usize,
|
||||||
|
@ -202,7 +202,7 @@ impl ReadonlyNpmCache {
|
||||||
pub fn package_folder_for_name_and_version(
|
pub fn package_folder_for_name_and_version(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
version: &NpmVersion,
|
version: &Version,
|
||||||
registry_url: &Url,
|
registry_url: &Url,
|
||||||
) -> PathBuf {
|
) -> PathBuf {
|
||||||
self
|
self
|
||||||
|
@ -305,7 +305,7 @@ impl ReadonlyNpmCache {
|
||||||
};
|
};
|
||||||
Some(NpmPackageCacheFolderId {
|
Some(NpmPackageCacheFolderId {
|
||||||
name,
|
name,
|
||||||
version: NpmVersion::parse(version).ok()?,
|
version: Version::parse_from_npm(version).ok()?,
|
||||||
copy_index,
|
copy_index,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -357,7 +357,7 @@ impl NpmCache {
|
||||||
/// and imports a dynamic import that imports the same package again for example.
|
/// and imports a dynamic import that imports the same package again for example.
|
||||||
fn should_use_global_cache_for_package(
|
fn should_use_global_cache_for_package(
|
||||||
&self,
|
&self,
|
||||||
package: (&str, &NpmVersion),
|
package: (&str, &Version),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
self.cache_setting.should_use_for_npm_package(package.0)
|
self.cache_setting.should_use_for_npm_package(package.0)
|
||||||
|| !self
|
|| !self
|
||||||
|
@ -368,7 +368,7 @@ impl NpmCache {
|
||||||
|
|
||||||
pub async fn ensure_package(
|
pub async fn ensure_package(
|
||||||
&self,
|
&self,
|
||||||
package: (&str, &NpmVersion),
|
package: (&str, &Version),
|
||||||
dist: &NpmPackageVersionDistInfo,
|
dist: &NpmPackageVersionDistInfo,
|
||||||
registry_url: &Url,
|
registry_url: &Url,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
@ -382,7 +382,7 @@ impl NpmCache {
|
||||||
|
|
||||||
async fn ensure_package_inner(
|
async fn ensure_package_inner(
|
||||||
&self,
|
&self,
|
||||||
package: (&str, &NpmVersion),
|
package: (&str, &Version),
|
||||||
dist: &NpmPackageVersionDistInfo,
|
dist: &NpmPackageVersionDistInfo,
|
||||||
registry_url: &Url,
|
registry_url: &Url,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
@ -467,7 +467,7 @@ impl NpmCache {
|
||||||
pub fn package_folder_for_name_and_version(
|
pub fn package_folder_for_name_and_version(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
version: &NpmVersion,
|
version: &Version,
|
||||||
registry_url: &Url,
|
registry_url: &Url,
|
||||||
) -> PathBuf {
|
) -> PathBuf {
|
||||||
self.readonly.package_folder_for_name_and_version(
|
self.readonly.package_folder_for_name_and_version(
|
||||||
|
@ -517,7 +517,7 @@ mod test {
|
||||||
|
|
||||||
use super::ReadonlyNpmCache;
|
use super::ReadonlyNpmCache;
|
||||||
use crate::npm::cache::NpmPackageCacheFolderId;
|
use crate::npm::cache::NpmPackageCacheFolderId;
|
||||||
use crate::npm::semver::NpmVersion;
|
use crate::semver::Version;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_get_package_folder() {
|
fn should_get_package_folder() {
|
||||||
|
@ -530,7 +530,7 @@ mod test {
|
||||||
cache.package_folder_for_id(
|
cache.package_folder_for_id(
|
||||||
&NpmPackageCacheFolderId {
|
&NpmPackageCacheFolderId {
|
||||||
name: "json".to_string(),
|
name: "json".to_string(),
|
||||||
version: NpmVersion::parse("1.2.5").unwrap(),
|
version: Version::parse_from_npm("1.2.5").unwrap(),
|
||||||
copy_index: 0,
|
copy_index: 0,
|
||||||
},
|
},
|
||||||
®istry_url,
|
®istry_url,
|
||||||
|
@ -545,7 +545,7 @@ mod test {
|
||||||
cache.package_folder_for_id(
|
cache.package_folder_for_id(
|
||||||
&NpmPackageCacheFolderId {
|
&NpmPackageCacheFolderId {
|
||||||
name: "json".to_string(),
|
name: "json".to_string(),
|
||||||
version: NpmVersion::parse("1.2.5").unwrap(),
|
version: Version::parse_from_npm("1.2.5").unwrap(),
|
||||||
copy_index: 1,
|
copy_index: 1,
|
||||||
},
|
},
|
||||||
®istry_url,
|
®istry_url,
|
||||||
|
@ -560,7 +560,7 @@ mod test {
|
||||||
cache.package_folder_for_id(
|
cache.package_folder_for_id(
|
||||||
&NpmPackageCacheFolderId {
|
&NpmPackageCacheFolderId {
|
||||||
name: "JSON".to_string(),
|
name: "JSON".to_string(),
|
||||||
version: NpmVersion::parse("2.1.5").unwrap(),
|
version: Version::parse_from_npm("2.1.5").unwrap(),
|
||||||
copy_index: 0,
|
copy_index: 0,
|
||||||
},
|
},
|
||||||
®istry_url,
|
®istry_url,
|
||||||
|
@ -575,7 +575,7 @@ mod test {
|
||||||
cache.package_folder_for_id(
|
cache.package_folder_for_id(
|
||||||
&NpmPackageCacheFolderId {
|
&NpmPackageCacheFolderId {
|
||||||
name: "@types/JSON".to_string(),
|
name: "@types/JSON".to_string(),
|
||||||
version: NpmVersion::parse("2.1.5").unwrap(),
|
version: Version::parse_from_npm("2.1.5").unwrap(),
|
||||||
copy_index: 0,
|
copy_index: 0,
|
||||||
},
|
},
|
||||||
®istry_url,
|
®istry_url,
|
||||||
|
|
|
@ -4,11 +4,8 @@ mod cache;
|
||||||
mod registry;
|
mod registry;
|
||||||
mod resolution;
|
mod resolution;
|
||||||
mod resolvers;
|
mod resolvers;
|
||||||
mod semver;
|
|
||||||
mod tarball;
|
mod tarball;
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub use self::semver::NpmVersion;
|
|
||||||
pub use cache::NpmCache;
|
pub use cache::NpmCache;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub use registry::NpmPackageVersionDistInfo;
|
pub use registry::NpmPackageVersionDistInfo;
|
||||||
|
|
|
@ -25,13 +25,12 @@ use serde::Serialize;
|
||||||
use crate::args::CacheSetting;
|
use crate::args::CacheSetting;
|
||||||
use crate::cache::CACHE_PERM;
|
use crate::cache::CACHE_PERM;
|
||||||
use crate::http_util::HttpClient;
|
use crate::http_util::HttpClient;
|
||||||
|
use crate::semver::Version;
|
||||||
|
use crate::semver::VersionReq;
|
||||||
use crate::util::fs::atomic_write_file;
|
use crate::util::fs::atomic_write_file;
|
||||||
use crate::util::progress_bar::ProgressBar;
|
use crate::util::progress_bar::ProgressBar;
|
||||||
|
|
||||||
use super::cache::NpmCache;
|
use super::cache::NpmCache;
|
||||||
use super::resolution::NpmVersionMatcher;
|
|
||||||
use super::semver::NpmVersion;
|
|
||||||
use super::semver::NpmVersionReq;
|
|
||||||
|
|
||||||
// npm registry docs: https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md
|
// npm registry docs: https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md
|
||||||
|
|
||||||
|
@ -61,11 +60,11 @@ pub struct NpmDependencyEntry {
|
||||||
pub kind: NpmDependencyEntryKind,
|
pub kind: NpmDependencyEntryKind,
|
||||||
pub bare_specifier: String,
|
pub bare_specifier: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub version_req: NpmVersionReq,
|
pub version_req: VersionReq,
|
||||||
/// When the dependency is also marked as a peer dependency,
|
/// When the dependency is also marked as a peer dependency,
|
||||||
/// use this entry to resolve the dependency when it can't
|
/// use this entry to resolve the dependency when it can't
|
||||||
/// be resolved as a peer dependency.
|
/// be resolved as a peer dependency.
|
||||||
pub peer_dep_version_req: Option<NpmVersionReq>,
|
pub peer_dep_version_req: Option<VersionReq>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for NpmDependencyEntry {
|
impl PartialOrd for NpmDependencyEntry {
|
||||||
|
@ -82,7 +81,7 @@ impl Ord for NpmDependencyEntry {
|
||||||
Ordering::Equal => other
|
Ordering::Equal => other
|
||||||
.version_req
|
.version_req
|
||||||
.version_text()
|
.version_text()
|
||||||
.cmp(&self.version_req.version_text()),
|
.cmp(self.version_req.version_text()),
|
||||||
ordering => ordering,
|
ordering => ordering,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,7 +128,7 @@ impl NpmPackageVersionInfo {
|
||||||
(entry.0.clone(), entry.1.clone())
|
(entry.0.clone(), entry.1.clone())
|
||||||
};
|
};
|
||||||
let version_req =
|
let version_req =
|
||||||
NpmVersionReq::parse(&version_req).with_context(|| {
|
VersionReq::parse_from_npm(&version_req).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"error parsing version requirement for dependency: {bare_specifier}@{version_req}"
|
"error parsing version requirement for dependency: {bare_specifier}@{version_req}"
|
||||||
)
|
)
|
||||||
|
@ -217,7 +216,7 @@ pub trait NpmRegistryApi: Clone + Sync + Send + 'static {
|
||||||
fn package_version_info(
|
fn package_version_info(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
version: &NpmVersion,
|
version: &Version,
|
||||||
) -> BoxFuture<'static, Result<Option<NpmPackageVersionInfo>, AnyError>> {
|
) -> BoxFuture<'static, Result<Option<NpmPackageVersionInfo>, AnyError>> {
|
||||||
let api = self.clone();
|
let api = self.clone();
|
||||||
let name = name.to_string();
|
let name = name.to_string();
|
||||||
|
|
|
@ -14,22 +14,25 @@ use deno_core::futures;
|
||||||
use deno_core::parking_lot::Mutex;
|
use deno_core::parking_lot::Mutex;
|
||||||
use deno_core::parking_lot::MutexGuard;
|
use deno_core::parking_lot::MutexGuard;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use crate::npm::cache::should_sync_download;
|
use crate::npm::cache::should_sync_download;
|
||||||
use crate::npm::registry::NpmDependencyEntry;
|
use crate::npm::registry::NpmDependencyEntry;
|
||||||
use crate::npm::registry::NpmDependencyEntryKind;
|
use crate::npm::registry::NpmDependencyEntryKind;
|
||||||
use crate::npm::registry::NpmPackageInfo;
|
use crate::npm::registry::NpmPackageInfo;
|
||||||
use crate::npm::registry::NpmPackageVersionInfo;
|
use crate::npm::registry::NpmPackageVersionInfo;
|
||||||
use crate::npm::semver::NpmVersion;
|
|
||||||
use crate::npm::semver::NpmVersionReq;
|
|
||||||
use crate::npm::NpmRegistryApi;
|
use crate::npm::NpmRegistryApi;
|
||||||
|
use crate::semver::Version;
|
||||||
|
use crate::semver::VersionReq;
|
||||||
|
|
||||||
use super::snapshot::NpmResolutionSnapshot;
|
use super::snapshot::NpmResolutionSnapshot;
|
||||||
use super::snapshot::SnapshotPackageCopyIndexResolver;
|
use super::snapshot::SnapshotPackageCopyIndexResolver;
|
||||||
use super::NpmPackageId;
|
use super::NpmPackageId;
|
||||||
use super::NpmPackageReq;
|
use super::NpmPackageReq;
|
||||||
use super::NpmResolutionPackage;
|
use super::NpmResolutionPackage;
|
||||||
use super::NpmVersionMatcher;
|
|
||||||
|
pub static LATEST_VERSION_REQ: Lazy<VersionReq> =
|
||||||
|
Lazy::new(|| VersionReq::parse_from_specifier("latest").unwrap());
|
||||||
|
|
||||||
/// A memory efficient path of visited name and versions in the graph
|
/// A memory efficient path of visited name and versions in the graph
|
||||||
/// which is used to detect cycles.
|
/// which is used to detect cycles.
|
||||||
|
@ -419,11 +422,11 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
|
||||||
|
|
||||||
fn resolve_best_package_version_and_info<'info>(
|
fn resolve_best_package_version_and_info<'info>(
|
||||||
&self,
|
&self,
|
||||||
version_matcher: &impl NpmVersionMatcher,
|
version_req: &VersionReq,
|
||||||
package_info: &'info NpmPackageInfo,
|
package_info: &'info NpmPackageInfo,
|
||||||
) -> Result<VersionAndInfo<'info>, AnyError> {
|
) -> Result<VersionAndInfo<'info>, AnyError> {
|
||||||
if let Some(version) =
|
if let Some(version) =
|
||||||
self.resolve_best_package_version(package_info, version_matcher)?
|
self.resolve_best_package_version(package_info, version_req)?
|
||||||
{
|
{
|
||||||
match package_info.versions.get(&version.to_string()) {
|
match package_info.versions.get(&version.to_string()) {
|
||||||
Some(version_info) => Ok(VersionAndInfo {
|
Some(version_info) => Ok(VersionAndInfo {
|
||||||
|
@ -440,20 +443,19 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// get the information
|
// get the information
|
||||||
get_resolved_package_version_and_info(version_matcher, package_info, None)
|
get_resolved_package_version_and_info(version_req, package_info, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_best_package_version(
|
fn resolve_best_package_version(
|
||||||
&self,
|
&self,
|
||||||
package_info: &NpmPackageInfo,
|
package_info: &NpmPackageInfo,
|
||||||
version_matcher: &impl NpmVersionMatcher,
|
version_req: &VersionReq,
|
||||||
) -> Result<Option<NpmVersion>, AnyError> {
|
) -> Result<Option<Version>, AnyError> {
|
||||||
let mut maybe_best_version: Option<&NpmVersion> = None;
|
let mut maybe_best_version: Option<&Version> = None;
|
||||||
if let Some(ids) = self.graph.packages_by_name.get(&package_info.name) {
|
if let Some(ids) = self.graph.packages_by_name.get(&package_info.name) {
|
||||||
for version in ids.iter().map(|id| &id.version) {
|
for version in ids.iter().map(|id| &id.version) {
|
||||||
if version_req_satisfies(version_matcher, version, package_info, None)?
|
if version_req_satisfies(version_req, version, package_info, None)? {
|
||||||
{
|
|
||||||
let is_best_version = maybe_best_version
|
let is_best_version = maybe_best_version
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|best_version| (*best_version).cmp(version).is_lt())
|
.map(|best_version| (*best_version).cmp(version).is_lt())
|
||||||
|
@ -478,7 +480,10 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
let (_, node) = self.resolve_node_from_info(
|
let (_, node) = self.resolve_node_from_info(
|
||||||
&package_req.name,
|
&package_req.name,
|
||||||
package_req,
|
package_req
|
||||||
|
.version_req
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&*LATEST_VERSION_REQ),
|
||||||
package_info,
|
package_info,
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
@ -557,12 +562,12 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
|
||||||
fn resolve_node_from_info(
|
fn resolve_node_from_info(
|
||||||
&mut self,
|
&mut self,
|
||||||
pkg_req_name: &str,
|
pkg_req_name: &str,
|
||||||
version_matcher: &impl NpmVersionMatcher,
|
version_req: &VersionReq,
|
||||||
package_info: &NpmPackageInfo,
|
package_info: &NpmPackageInfo,
|
||||||
parent_id: Option<&NpmPackageId>,
|
parent_id: Option<&NpmPackageId>,
|
||||||
) -> Result<(NpmPackageId, Arc<Mutex<Node>>), AnyError> {
|
) -> Result<(NpmPackageId, Arc<Mutex<Node>>), AnyError> {
|
||||||
let version_and_info = self
|
let version_and_info =
|
||||||
.resolve_best_package_version_and_info(version_matcher, package_info)?;
|
self.resolve_best_package_version_and_info(version_req, package_info)?;
|
||||||
let id = NpmPackageId {
|
let id = NpmPackageId {
|
||||||
name: package_info.name.to_string(),
|
name: package_info.name.to_string(),
|
||||||
version: version_and_info.version.clone(),
|
version: version_and_info.version.clone(),
|
||||||
|
@ -575,7 +580,7 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
|
||||||
None => "<package-req>".to_string(),
|
None => "<package-req>".to_string(),
|
||||||
},
|
},
|
||||||
pkg_req_name,
|
pkg_req_name,
|
||||||
version_matcher.version_text(),
|
version_req.version_text(),
|
||||||
id.as_serialized(),
|
id.as_serialized(),
|
||||||
);
|
);
|
||||||
let (created, node) = self.graph.get_or_create_for_id(&id);
|
let (created, node) = self.graph.get_or_create_for_id(&id);
|
||||||
|
@ -996,22 +1001,22 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct VersionAndInfo<'a> {
|
struct VersionAndInfo<'a> {
|
||||||
version: NpmVersion,
|
version: Version,
|
||||||
info: &'a NpmPackageVersionInfo,
|
info: &'a NpmPackageVersionInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_resolved_package_version_and_info<'a>(
|
fn get_resolved_package_version_and_info<'a>(
|
||||||
version_matcher: &impl NpmVersionMatcher,
|
version_req: &VersionReq,
|
||||||
info: &'a NpmPackageInfo,
|
info: &'a NpmPackageInfo,
|
||||||
parent: Option<&NpmPackageId>,
|
parent: Option<&NpmPackageId>,
|
||||||
) -> Result<VersionAndInfo<'a>, AnyError> {
|
) -> Result<VersionAndInfo<'a>, AnyError> {
|
||||||
if let Some(tag) = version_matcher.tag() {
|
if let Some(tag) = version_req.tag() {
|
||||||
tag_to_version_info(info, tag, parent)
|
tag_to_version_info(info, tag, parent)
|
||||||
} else {
|
} else {
|
||||||
let mut maybe_best_version: Option<VersionAndInfo> = None;
|
let mut maybe_best_version: Option<VersionAndInfo> = None;
|
||||||
for version_info in info.versions.values() {
|
for version_info in info.versions.values() {
|
||||||
let version = NpmVersion::parse(&version_info.version)?;
|
let version = Version::parse_from_npm(&version_info.version)?;
|
||||||
if version_matcher.matches(&version) {
|
if version_req.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())
|
||||||
|
@ -1042,7 +1047,7 @@ fn get_resolved_package_version_and_info<'a>(
|
||||||
"Try retrieving the latest npm package information by running with --reload",
|
"Try retrieving the latest npm package information by running with --reload",
|
||||||
),
|
),
|
||||||
info.name,
|
info.name,
|
||||||
version_matcher.version_text(),
|
version_req.version_text(),
|
||||||
match parent {
|
match parent {
|
||||||
Some(id) => format!(" as specified in {}", id.display()),
|
Some(id) => format!(" as specified in {}", id.display()),
|
||||||
None => String::new(),
|
None => String::new(),
|
||||||
|
@ -1053,17 +1058,17 @@ fn get_resolved_package_version_and_info<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn version_req_satisfies(
|
fn version_req_satisfies(
|
||||||
matcher: &impl NpmVersionMatcher,
|
version_req: &VersionReq,
|
||||||
version: &NpmVersion,
|
version: &Version,
|
||||||
package_info: &NpmPackageInfo,
|
package_info: &NpmPackageInfo,
|
||||||
parent: Option<&NpmPackageId>,
|
parent: Option<&NpmPackageId>,
|
||||||
) -> Result<bool, AnyError> {
|
) -> Result<bool, AnyError> {
|
||||||
match matcher.tag() {
|
match version_req.tag() {
|
||||||
Some(tag) => {
|
Some(tag) => {
|
||||||
let tag_version = tag_to_version_info(package_info, tag, parent)?.version;
|
let tag_version = tag_to_version_info(package_info, tag, parent)?.version;
|
||||||
Ok(tag_version == *version)
|
Ok(tag_version == *version)
|
||||||
}
|
}
|
||||||
None => Ok(matcher.matches(version)),
|
None => Ok(version_req.matches(version)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1081,7 +1086,7 @@ fn tag_to_version_info<'a>(
|
||||||
// explicit version.
|
// explicit version.
|
||||||
if tag == "latest" && info.name == "@types/node" {
|
if tag == "latest" && info.name == "@types/node" {
|
||||||
return get_resolved_package_version_and_info(
|
return get_resolved_package_version_and_info(
|
||||||
&NpmVersionReq::parse("18.0.0 - 18.11.18").unwrap(),
|
&VersionReq::parse_from_npm("18.0.0 - 18.11.18").unwrap(),
|
||||||
info,
|
info,
|
||||||
parent,
|
parent,
|
||||||
);
|
);
|
||||||
|
@ -1090,7 +1095,7 @@ fn tag_to_version_info<'a>(
|
||||||
if let Some(version) = info.dist_tags.get(tag) {
|
if let Some(version) = info.dist_tags.get(tag) {
|
||||||
match info.versions.get(version) {
|
match info.versions.get(version) {
|
||||||
Some(info) => Ok(VersionAndInfo {
|
Some(info) => Ok(VersionAndInfo {
|
||||||
version: NpmVersion::parse(version)?,
|
version: Version::parse_from_npm(version)?,
|
||||||
info,
|
info,
|
||||||
}),
|
}),
|
||||||
None => {
|
None => {
|
||||||
|
@ -1128,7 +1133,11 @@ mod test {
|
||||||
)]),
|
)]),
|
||||||
};
|
};
|
||||||
let result = get_resolved_package_version_and_info(
|
let result = get_resolved_package_version_and_info(
|
||||||
&package_ref.req,
|
package_ref
|
||||||
|
.req
|
||||||
|
.version_req
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&*LATEST_VERSION_REQ),
|
||||||
&package_info,
|
&package_info,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
@ -1157,7 +1166,11 @@ mod test {
|
||||||
)]),
|
)]),
|
||||||
};
|
};
|
||||||
let result = get_resolved_package_version_and_info(
|
let result = get_resolved_package_version_and_info(
|
||||||
&package_ref.req,
|
package_ref
|
||||||
|
.req
|
||||||
|
.version_req
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&*LATEST_VERSION_REQ),
|
||||||
&package_info,
|
&package_info,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,6 +11,7 @@ use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::args::Lockfile;
|
use crate::args::Lockfile;
|
||||||
|
use crate::semver::Version;
|
||||||
|
|
||||||
use self::graph::GraphDependencyResolver;
|
use self::graph::GraphDependencyResolver;
|
||||||
use self::snapshot::NpmPackagesPartitioned;
|
use self::snapshot::NpmPackagesPartitioned;
|
||||||
|
@ -19,33 +20,25 @@ use super::cache::should_sync_download;
|
||||||
use super::cache::NpmPackageCacheFolderId;
|
use super::cache::NpmPackageCacheFolderId;
|
||||||
use super::registry::NpmPackageVersionDistInfo;
|
use super::registry::NpmPackageVersionDistInfo;
|
||||||
use super::registry::RealNpmRegistryApi;
|
use super::registry::RealNpmRegistryApi;
|
||||||
use super::semver::NpmVersion;
|
|
||||||
use super::NpmRegistryApi;
|
use super::NpmRegistryApi;
|
||||||
|
|
||||||
mod graph;
|
mod graph;
|
||||||
|
mod reference;
|
||||||
mod snapshot;
|
mod snapshot;
|
||||||
mod specifier;
|
mod specifier;
|
||||||
|
|
||||||
use graph::Graph;
|
use graph::Graph;
|
||||||
|
pub use reference::NpmPackageReference;
|
||||||
|
pub use reference::NpmPackageReq;
|
||||||
pub use snapshot::NpmResolutionSnapshot;
|
pub use snapshot::NpmResolutionSnapshot;
|
||||||
pub use specifier::resolve_graph_npm_info;
|
pub use specifier::resolve_graph_npm_info;
|
||||||
pub use specifier::NpmPackageReference;
|
|
||||||
pub use specifier::NpmPackageReq;
|
|
||||||
|
|
||||||
/// 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.
|
|
||||||
pub trait NpmVersionMatcher {
|
|
||||||
fn tag(&self) -> Option<&str>;
|
|
||||||
fn matches(&self, version: &NpmVersion) -> bool;
|
|
||||||
fn version_text(&self) -> String;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize,
|
Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize,
|
||||||
)]
|
)]
|
||||||
pub struct NpmPackageId {
|
pub struct NpmPackageId {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub version: NpmVersion,
|
pub version: Version,
|
||||||
pub peer_dependencies: Vec<NpmPackageId>,
|
pub peer_dependencies: Vec<NpmPackageId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,14 +96,12 @@ impl NpmPackageId {
|
||||||
if_not_empty(substring(skip_while(|c| c != '_')))(input)
|
if_not_empty(substring(skip_while(|c| c != '_')))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_name_and_version(
|
fn parse_name_and_version(input: &str) -> ParseResult<(String, Version)> {
|
||||||
input: &str,
|
|
||||||
) -> ParseResult<(String, NpmVersion)> {
|
|
||||||
let (input, name) = parse_name(input)?;
|
let (input, name) = parse_name(input)?;
|
||||||
let (input, _) = ch('@')(input)?;
|
let (input, _) = ch('@')(input)?;
|
||||||
let at_version_input = input;
|
let at_version_input = input;
|
||||||
let (input, version) = parse_version(input)?;
|
let (input, version) = parse_version(input)?;
|
||||||
match NpmVersion::parse(version) {
|
match Version::parse_from_npm(version) {
|
||||||
Ok(version) => Ok((input, (name.to_string(), version))),
|
Ok(version) => Ok((input, (name.to_string(), version))),
|
||||||
Err(err) => ParseError::fail(at_version_input, format!("{err:#}")),
|
Err(err) => ParseError::fail(at_version_input, format!("{err:#}")),
|
||||||
}
|
}
|
||||||
|
@ -417,30 +408,30 @@ mod tests {
|
||||||
fn serialize_npm_package_id() {
|
fn serialize_npm_package_id() {
|
||||||
let id = NpmPackageId {
|
let id = NpmPackageId {
|
||||||
name: "pkg-a".to_string(),
|
name: "pkg-a".to_string(),
|
||||||
version: NpmVersion::parse("1.2.3").unwrap(),
|
version: Version::parse_from_npm("1.2.3").unwrap(),
|
||||||
peer_dependencies: vec![
|
peer_dependencies: vec![
|
||||||
NpmPackageId {
|
NpmPackageId {
|
||||||
name: "pkg-b".to_string(),
|
name: "pkg-b".to_string(),
|
||||||
version: NpmVersion::parse("3.2.1").unwrap(),
|
version: Version::parse_from_npm("3.2.1").unwrap(),
|
||||||
peer_dependencies: vec![
|
peer_dependencies: vec![
|
||||||
NpmPackageId {
|
NpmPackageId {
|
||||||
name: "pkg-c".to_string(),
|
name: "pkg-c".to_string(),
|
||||||
version: NpmVersion::parse("1.3.2").unwrap(),
|
version: Version::parse_from_npm("1.3.2").unwrap(),
|
||||||
peer_dependencies: vec![],
|
peer_dependencies: vec![],
|
||||||
},
|
},
|
||||||
NpmPackageId {
|
NpmPackageId {
|
||||||
name: "pkg-d".to_string(),
|
name: "pkg-d".to_string(),
|
||||||
version: NpmVersion::parse("2.3.4").unwrap(),
|
version: Version::parse_from_npm("2.3.4").unwrap(),
|
||||||
peer_dependencies: vec![],
|
peer_dependencies: vec![],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
NpmPackageId {
|
NpmPackageId {
|
||||||
name: "pkg-e".to_string(),
|
name: "pkg-e".to_string(),
|
||||||
version: NpmVersion::parse("2.3.1").unwrap(),
|
version: Version::parse_from_npm("2.3.1").unwrap(),
|
||||||
peer_dependencies: vec![NpmPackageId {
|
peer_dependencies: vec![NpmPackageId {
|
||||||
name: "pkg-f".to_string(),
|
name: "pkg-f".to_string(),
|
||||||
version: NpmVersion::parse("2.3.1").unwrap(),
|
version: Version::parse_from_npm("2.3.1").unwrap(),
|
||||||
peer_dependencies: vec![],
|
peer_dependencies: vec![],
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
|
|
298
cli/npm/resolution/reference.rs
Normal file
298
cli/npm/resolution/reference.rs
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use deno_ast::ModuleSpecifier;
|
||||||
|
use deno_core::anyhow::bail;
|
||||||
|
use deno_core::anyhow::Context;
|
||||||
|
use deno_core::error::generic_error;
|
||||||
|
use deno_core::error::AnyError;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::semver::VersionReq;
|
||||||
|
|
||||||
|
/// A reference to an npm package's name, version constraint, and potential sub path.
|
||||||
|
///
|
||||||
|
/// This contains all the information found in an npm specifier.
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub struct NpmPackageReference {
|
||||||
|
pub req: NpmPackageReq,
|
||||||
|
pub sub_path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NpmPackageReference {
|
||||||
|
pub fn from_specifier(
|
||||||
|
specifier: &ModuleSpecifier,
|
||||||
|
) -> Result<NpmPackageReference, AnyError> {
|
||||||
|
Self::from_str(specifier.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_str(specifier: &str) -> Result<NpmPackageReference, AnyError> {
|
||||||
|
let original_text = specifier;
|
||||||
|
let specifier = match specifier.strip_prefix("npm:") {
|
||||||
|
Some(s) => {
|
||||||
|
// Strip leading slash, which might come from import map
|
||||||
|
s.strip_prefix('/').unwrap_or(s)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// don't allocate a string here and instead use a static string
|
||||||
|
// because this is hit a lot when a url is not an npm specifier
|
||||||
|
return Err(generic_error("Not an npm specifier"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let parts = specifier.split('/').collect::<Vec<_>>();
|
||||||
|
let name_part_len = if specifier.starts_with('@') { 2 } else { 1 };
|
||||||
|
if parts.len() < name_part_len {
|
||||||
|
return Err(generic_error(format!("Not a valid package: {specifier}")));
|
||||||
|
}
|
||||||
|
let name_parts = &parts[0..name_part_len];
|
||||||
|
let req = match NpmPackageReq::parse_from_parts(name_parts) {
|
||||||
|
Ok(pkg_req) => pkg_req,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(generic_error(format!(
|
||||||
|
"Invalid npm specifier '{original_text}'. {err:#}"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let sub_path = if parts.len() == name_parts.len() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let sub_path = parts[name_part_len..].join("/");
|
||||||
|
if sub_path.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(sub_path)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(sub_path) = &sub_path {
|
||||||
|
if let Some(at_index) = sub_path.rfind('@') {
|
||||||
|
let (new_sub_path, version) = sub_path.split_at(at_index);
|
||||||
|
let msg = format!(
|
||||||
|
"Invalid package specifier 'npm:{req}/{sub_path}'. Did you mean to write 'npm:{req}{version}/{new_sub_path}'?"
|
||||||
|
);
|
||||||
|
return Err(generic_error(msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(NpmPackageReference { req, sub_path })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for NpmPackageReference {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
if let Some(sub_path) = &self.sub_path {
|
||||||
|
write!(f, "npm:{}/{}", self.req, sub_path)
|
||||||
|
} else {
|
||||||
|
write!(f, "npm:{}", self.req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The name and version constraint component of an `NpmPackageReference`.
|
||||||
|
#[derive(
|
||||||
|
Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
|
pub struct NpmPackageReq {
|
||||||
|
pub name: String,
|
||||||
|
pub version_req: Option<VersionReq>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for NpmPackageReq {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match &self.version_req {
|
||||||
|
Some(req) => write!(f, "{}@{}", self.name, req),
|
||||||
|
None => write!(f, "{}", self.name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NpmPackageReq {
|
||||||
|
pub fn from_str(text: &str) -> Result<Self, AnyError> {
|
||||||
|
let parts = text.split('/').collect::<Vec<_>>();
|
||||||
|
match NpmPackageReq::parse_from_parts(&parts) {
|
||||||
|
Ok(req) => Ok(req),
|
||||||
|
Err(err) => {
|
||||||
|
let msg = format!("Invalid npm package requirement '{text}'. {err:#}");
|
||||||
|
Err(generic_error(msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_from_parts(name_parts: &[&str]) -> Result<Self, AnyError> {
|
||||||
|
assert!(!name_parts.is_empty()); // this should be provided the result of a string split
|
||||||
|
let last_name_part = &name_parts[name_parts.len() - 1];
|
||||||
|
let (name, version_req) = if let Some(at_index) = last_name_part.rfind('@')
|
||||||
|
{
|
||||||
|
let version = &last_name_part[at_index + 1..];
|
||||||
|
let last_name_part = &last_name_part[..at_index];
|
||||||
|
let version_req = VersionReq::parse_from_specifier(version)
|
||||||
|
.with_context(|| "Invalid version requirement.")?;
|
||||||
|
let name = if name_parts.len() == 1 {
|
||||||
|
last_name_part.to_string()
|
||||||
|
} else {
|
||||||
|
format!("{}/{}", name_parts[0], last_name_part)
|
||||||
|
};
|
||||||
|
(name, Some(version_req))
|
||||||
|
} else {
|
||||||
|
(name_parts.join("/"), None)
|
||||||
|
};
|
||||||
|
if name.is_empty() {
|
||||||
|
bail!("Did not contain a package name.")
|
||||||
|
}
|
||||||
|
Ok(Self { name, version_req })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_npm_package_ref() {
|
||||||
|
assert_eq!(
|
||||||
|
NpmPackageReference::from_str("npm:@package/test").unwrap(),
|
||||||
|
NpmPackageReference {
|
||||||
|
req: NpmPackageReq {
|
||||||
|
name: "@package/test".to_string(),
|
||||||
|
version_req: None,
|
||||||
|
},
|
||||||
|
sub_path: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
NpmPackageReference::from_str("npm:@package/test@1").unwrap(),
|
||||||
|
NpmPackageReference {
|
||||||
|
req: NpmPackageReq {
|
||||||
|
name: "@package/test".to_string(),
|
||||||
|
version_req: Some(VersionReq::parse_from_specifier("1").unwrap()),
|
||||||
|
},
|
||||||
|
sub_path: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
NpmPackageReference::from_str("npm:@package/test@~1.1/sub_path").unwrap(),
|
||||||
|
NpmPackageReference {
|
||||||
|
req: NpmPackageReq {
|
||||||
|
name: "@package/test".to_string(),
|
||||||
|
version_req: Some(VersionReq::parse_from_specifier("~1.1").unwrap()),
|
||||||
|
},
|
||||||
|
sub_path: Some("sub_path".to_string()),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
NpmPackageReference::from_str("npm:@package/test/sub_path").unwrap(),
|
||||||
|
NpmPackageReference {
|
||||||
|
req: NpmPackageReq {
|
||||||
|
name: "@package/test".to_string(),
|
||||||
|
version_req: None,
|
||||||
|
},
|
||||||
|
sub_path: Some("sub_path".to_string()),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
NpmPackageReference::from_str("npm:test").unwrap(),
|
||||||
|
NpmPackageReference {
|
||||||
|
req: NpmPackageReq {
|
||||||
|
name: "test".to_string(),
|
||||||
|
version_req: None,
|
||||||
|
},
|
||||||
|
sub_path: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
NpmPackageReference::from_str("npm:test@^1.2").unwrap(),
|
||||||
|
NpmPackageReference {
|
||||||
|
req: NpmPackageReq {
|
||||||
|
name: "test".to_string(),
|
||||||
|
version_req: Some(VersionReq::parse_from_specifier("^1.2").unwrap()),
|
||||||
|
},
|
||||||
|
sub_path: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
NpmPackageReference::from_str("npm:test@~1.1/sub_path").unwrap(),
|
||||||
|
NpmPackageReference {
|
||||||
|
req: NpmPackageReq {
|
||||||
|
name: "test".to_string(),
|
||||||
|
version_req: Some(VersionReq::parse_from_specifier("~1.1").unwrap()),
|
||||||
|
},
|
||||||
|
sub_path: Some("sub_path".to_string()),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
NpmPackageReference::from_str("npm:@package/test/sub_path").unwrap(),
|
||||||
|
NpmPackageReference {
|
||||||
|
req: NpmPackageReq {
|
||||||
|
name: "@package/test".to_string(),
|
||||||
|
version_req: None,
|
||||||
|
},
|
||||||
|
sub_path: Some("sub_path".to_string()),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
NpmPackageReference::from_str("npm:@package")
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.to_string(),
|
||||||
|
"Not a valid package: @package"
|
||||||
|
);
|
||||||
|
|
||||||
|
// should parse leading slash
|
||||||
|
assert_eq!(
|
||||||
|
NpmPackageReference::from_str("npm:/@package/test/sub_path").unwrap(),
|
||||||
|
NpmPackageReference {
|
||||||
|
req: NpmPackageReq {
|
||||||
|
name: "@package/test".to_string(),
|
||||||
|
version_req: None,
|
||||||
|
},
|
||||||
|
sub_path: Some("sub_path".to_string()),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
NpmPackageReference::from_str("npm:/test").unwrap(),
|
||||||
|
NpmPackageReference {
|
||||||
|
req: NpmPackageReq {
|
||||||
|
name: "test".to_string(),
|
||||||
|
version_req: None,
|
||||||
|
},
|
||||||
|
sub_path: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
NpmPackageReference::from_str("npm:/test/").unwrap(),
|
||||||
|
NpmPackageReference {
|
||||||
|
req: NpmPackageReq {
|
||||||
|
name: "test".to_string(),
|
||||||
|
version_req: None,
|
||||||
|
},
|
||||||
|
sub_path: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// should error for no name
|
||||||
|
assert_eq!(
|
||||||
|
NpmPackageReference::from_str("npm:/")
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.to_string(),
|
||||||
|
"Invalid npm specifier 'npm:/'. Did not contain a package name."
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
NpmPackageReference::from_str("npm://test")
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.to_string(),
|
||||||
|
"Invalid npm specifier 'npm://test'. Did not contain a package name."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,11 +19,11 @@ use crate::npm::cache::NpmPackageCacheFolderId;
|
||||||
use crate::npm::registry::NpmPackageVersionDistInfo;
|
use crate::npm::registry::NpmPackageVersionDistInfo;
|
||||||
use crate::npm::registry::NpmRegistryApi;
|
use crate::npm::registry::NpmRegistryApi;
|
||||||
use crate::npm::registry::RealNpmRegistryApi;
|
use crate::npm::registry::RealNpmRegistryApi;
|
||||||
|
use crate::semver::VersionReq;
|
||||||
|
|
||||||
use super::NpmPackageId;
|
use super::NpmPackageId;
|
||||||
use super::NpmPackageReq;
|
use super::NpmPackageReq;
|
||||||
use super::NpmResolutionPackage;
|
use super::NpmResolutionPackage;
|
||||||
use super::NpmVersionMatcher;
|
|
||||||
|
|
||||||
/// Packages partitioned by if they are "copy" packages or not.
|
/// Packages partitioned by if they are "copy" packages or not.
|
||||||
pub struct NpmPackagesPartitioned {
|
pub struct NpmPackagesPartitioned {
|
||||||
|
@ -159,12 +159,8 @@ impl NpmResolutionSnapshot {
|
||||||
|
|
||||||
// TODO(bartlomieju): this should use a reverse lookup table in the
|
// TODO(bartlomieju): this should use a reverse lookup table in the
|
||||||
// snapshot instead of resolving best version again.
|
// snapshot instead of resolving best version again.
|
||||||
let req = NpmPackageReq {
|
let any_version_req = VersionReq::parse_from_npm("*").unwrap();
|
||||||
name: name.to_string(),
|
if let Some(id) = self.resolve_best_package_id(name, &any_version_req) {
|
||||||
version_req: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(id) = self.resolve_best_package_id(name, &req) {
|
|
||||||
if let Some(pkg) = self.packages.get(&id) {
|
if let Some(pkg) = self.packages.get(&id) {
|
||||||
return Ok(pkg);
|
return Ok(pkg);
|
||||||
}
|
}
|
||||||
|
@ -201,14 +197,14 @@ impl NpmResolutionSnapshot {
|
||||||
pub fn resolve_best_package_id(
|
pub fn resolve_best_package_id(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
version_matcher: &impl NpmVersionMatcher,
|
version_req: &VersionReq,
|
||||||
) -> Option<NpmPackageId> {
|
) -> Option<NpmPackageId> {
|
||||||
// todo(dsherret): this is not exactly correct because some ids
|
// todo(dsherret): this is not exactly correct because some ids
|
||||||
// will be better than others due to peer dependencies
|
// will be better than others due to peer dependencies
|
||||||
let mut maybe_best_id: Option<&NpmPackageId> = None;
|
let mut maybe_best_id: Option<&NpmPackageId> = None;
|
||||||
if let Some(ids) = self.packages_by_name.get(name) {
|
if let Some(ids) = self.packages_by_name.get(name) {
|
||||||
for id in ids {
|
for id in ids {
|
||||||
if version_matcher.matches(&id.version) {
|
if version_req.matches(&id.version) {
|
||||||
let is_best_version = maybe_best_id
|
let is_best_version = maybe_best_id
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|best_id| best_id.version.cmp(&id.version).is_lt())
|
.map(|best_id| best_id.version.cmp(&id.version).is_lt())
|
||||||
|
|
|
@ -6,165 +6,13 @@ use std::collections::HashSet;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use deno_ast::ModuleSpecifier;
|
use deno_ast::ModuleSpecifier;
|
||||||
use deno_core::anyhow::Context;
|
|
||||||
use deno_core::error::generic_error;
|
|
||||||
use deno_core::error::AnyError;
|
|
||||||
use deno_graph::ModuleGraph;
|
use deno_graph::ModuleGraph;
|
||||||
use deno_graph::Resolved;
|
use deno_graph::Resolved;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use super::super::semver::NpmVersion;
|
use crate::semver::VersionReq;
|
||||||
use super::super::semver::SpecifierVersionReq;
|
|
||||||
use super::NpmVersionMatcher;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
use super::NpmPackageReference;
|
||||||
pub struct NpmPackageReference {
|
use super::NpmPackageReq;
|
||||||
pub req: NpmPackageReq,
|
|
||||||
pub sub_path: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NpmPackageReference {
|
|
||||||
pub fn from_specifier(
|
|
||||||
specifier: &ModuleSpecifier,
|
|
||||||
) -> Result<NpmPackageReference, AnyError> {
|
|
||||||
Self::from_str(specifier.as_str())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_str(specifier: &str) -> Result<NpmPackageReference, AnyError> {
|
|
||||||
let original_text = specifier;
|
|
||||||
let specifier = match specifier.strip_prefix("npm:") {
|
|
||||||
Some(s) => {
|
|
||||||
// Strip leading slash, which might come from import map
|
|
||||||
s.strip_prefix('/').unwrap_or(s)
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// don't allocate a string here and instead use a static string
|
|
||||||
// because this is hit a lot when a url is not an npm specifier
|
|
||||||
return Err(generic_error("Not an npm specifier"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let parts = specifier.split('/').collect::<Vec<_>>();
|
|
||||||
let name_part_len = if specifier.starts_with('@') { 2 } else { 1 };
|
|
||||||
if parts.len() < name_part_len {
|
|
||||||
return Err(generic_error(format!("Not a valid package: {specifier}")));
|
|
||||||
}
|
|
||||||
let name_parts = &parts[0..name_part_len];
|
|
||||||
let last_name_part = &name_parts[name_part_len - 1];
|
|
||||||
let (name, version_req) = if let Some(at_index) = last_name_part.rfind('@')
|
|
||||||
{
|
|
||||||
let version = &last_name_part[at_index + 1..];
|
|
||||||
let last_name_part = &last_name_part[..at_index];
|
|
||||||
let version_req = SpecifierVersionReq::parse(version)
|
|
||||||
.with_context(|| "Invalid version requirement.")?;
|
|
||||||
let name = if name_part_len == 1 {
|
|
||||||
last_name_part.to_string()
|
|
||||||
} else {
|
|
||||||
format!("{}/{}", name_parts[0], last_name_part)
|
|
||||||
};
|
|
||||||
(name, Some(version_req))
|
|
||||||
} else {
|
|
||||||
(name_parts.join("/"), None)
|
|
||||||
};
|
|
||||||
let sub_path = if parts.len() == name_parts.len() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let sub_path = parts[name_part_len..].join("/");
|
|
||||||
if sub_path.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(sub_path)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(sub_path) = &sub_path {
|
|
||||||
if let Some(at_index) = sub_path.rfind('@') {
|
|
||||||
let (new_sub_path, version) = sub_path.split_at(at_index);
|
|
||||||
let msg = format!(
|
|
||||||
"Invalid package specifier 'npm:{name}/{sub_path}'. Did you mean to write 'npm:{name}{version}/{new_sub_path}'?"
|
|
||||||
);
|
|
||||||
return Err(generic_error(msg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if name.is_empty() {
|
|
||||||
let msg = format!(
|
|
||||||
"Invalid npm specifier '{original_text}'. Did not contain a package name."
|
|
||||||
);
|
|
||||||
return Err(generic_error(msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(NpmPackageReference {
|
|
||||||
req: NpmPackageReq { name, version_req },
|
|
||||||
sub_path,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for NpmPackageReference {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
if let Some(sub_path) = &self.sub_path {
|
|
||||||
write!(f, "npm:{}/{}", self.req, sub_path)
|
|
||||||
} else {
|
|
||||||
write!(f, "npm:{}", self.req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(
|
|
||||||
Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize,
|
|
||||||
)]
|
|
||||||
pub struct NpmPackageReq {
|
|
||||||
pub name: String,
|
|
||||||
pub version_req: Option<SpecifierVersionReq>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for NpmPackageReq {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match &self.version_req {
|
|
||||||
Some(req) => write!(f, "{}@{}", self.name, req),
|
|
||||||
None => write!(f, "{}", self.name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NpmPackageReq {
|
|
||||||
pub fn from_str(text: &str) -> Result<Self, AnyError> {
|
|
||||||
// probably should do something more targeted in the future
|
|
||||||
let reference = NpmPackageReference::from_str(&format!("npm:{text}"))?;
|
|
||||||
Ok(reference.req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NpmVersionMatcher for NpmPackageReq {
|
|
||||||
fn tag(&self) -> Option<&str> {
|
|
||||||
match &self.version_req {
|
|
||||||
Some(version_req) => version_req.tag(),
|
|
||||||
None => Some("latest"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn matches(&self, version: &NpmVersion) -> bool {
|
|
||||||
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 {
|
|
||||||
self
|
|
||||||
.version_req
|
|
||||||
.as_ref()
|
|
||||||
.map(|v| format!("{v}"))
|
|
||||||
.unwrap_or_else(|| "non-prerelease".to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GraphNpmInfo {
|
pub struct GraphNpmInfo {
|
||||||
/// The order of these package requirements is the order they
|
/// The order of these package requirements is the order they
|
||||||
|
@ -537,10 +385,7 @@ fn cmp_folder_specifiers(a: &ModuleSpecifier, b: &ModuleSpecifier) -> Ordering {
|
||||||
// duplicate packages (so sort None last since it's `*`), but
|
// duplicate packages (so sort None last since it's `*`), but
|
||||||
// mostly to create some determinism around how these are resolved.
|
// mostly to create some determinism around how these are resolved.
|
||||||
fn cmp_package_req(a: &NpmPackageReq, b: &NpmPackageReq) -> Ordering {
|
fn cmp_package_req(a: &NpmPackageReq, b: &NpmPackageReq) -> Ordering {
|
||||||
fn cmp_specifier_version_req(
|
fn cmp_specifier_version_req(a: &VersionReq, b: &VersionReq) -> Ordering {
|
||||||
a: &SpecifierVersionReq,
|
|
||||||
b: &SpecifierVersionReq,
|
|
||||||
) -> Ordering {
|
|
||||||
match a.tag() {
|
match a.tag() {
|
||||||
Some(a_tag) => match b.tag() {
|
Some(a_tag) => match b.tag() {
|
||||||
Some(b_tag) => b_tag.cmp(a_tag), // sort descending
|
Some(b_tag) => b_tag.cmp(a_tag), // sort descending
|
||||||
|
@ -581,153 +426,6 @@ mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_npm_package_ref() {
|
|
||||||
assert_eq!(
|
|
||||||
NpmPackageReference::from_str("npm:@package/test").unwrap(),
|
|
||||||
NpmPackageReference {
|
|
||||||
req: NpmPackageReq {
|
|
||||||
name: "@package/test".to_string(),
|
|
||||||
version_req: None,
|
|
||||||
},
|
|
||||||
sub_path: None,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
NpmPackageReference::from_str("npm:@package/test@1").unwrap(),
|
|
||||||
NpmPackageReference {
|
|
||||||
req: NpmPackageReq {
|
|
||||||
name: "@package/test".to_string(),
|
|
||||||
version_req: Some(SpecifierVersionReq::parse("1").unwrap()),
|
|
||||||
},
|
|
||||||
sub_path: None,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
NpmPackageReference::from_str("npm:@package/test@~1.1/sub_path").unwrap(),
|
|
||||||
NpmPackageReference {
|
|
||||||
req: NpmPackageReq {
|
|
||||||
name: "@package/test".to_string(),
|
|
||||||
version_req: Some(SpecifierVersionReq::parse("~1.1").unwrap()),
|
|
||||||
},
|
|
||||||
sub_path: Some("sub_path".to_string()),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
NpmPackageReference::from_str("npm:@package/test/sub_path").unwrap(),
|
|
||||||
NpmPackageReference {
|
|
||||||
req: NpmPackageReq {
|
|
||||||
name: "@package/test".to_string(),
|
|
||||||
version_req: None,
|
|
||||||
},
|
|
||||||
sub_path: Some("sub_path".to_string()),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
NpmPackageReference::from_str("npm:test").unwrap(),
|
|
||||||
NpmPackageReference {
|
|
||||||
req: NpmPackageReq {
|
|
||||||
name: "test".to_string(),
|
|
||||||
version_req: None,
|
|
||||||
},
|
|
||||||
sub_path: None,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
NpmPackageReference::from_str("npm:test@^1.2").unwrap(),
|
|
||||||
NpmPackageReference {
|
|
||||||
req: NpmPackageReq {
|
|
||||||
name: "test".to_string(),
|
|
||||||
version_req: Some(SpecifierVersionReq::parse("^1.2").unwrap()),
|
|
||||||
},
|
|
||||||
sub_path: None,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
NpmPackageReference::from_str("npm:test@~1.1/sub_path").unwrap(),
|
|
||||||
NpmPackageReference {
|
|
||||||
req: NpmPackageReq {
|
|
||||||
name: "test".to_string(),
|
|
||||||
version_req: Some(SpecifierVersionReq::parse("~1.1").unwrap()),
|
|
||||||
},
|
|
||||||
sub_path: Some("sub_path".to_string()),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
NpmPackageReference::from_str("npm:@package/test/sub_path").unwrap(),
|
|
||||||
NpmPackageReference {
|
|
||||||
req: NpmPackageReq {
|
|
||||||
name: "@package/test".to_string(),
|
|
||||||
version_req: None,
|
|
||||||
},
|
|
||||||
sub_path: Some("sub_path".to_string()),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
NpmPackageReference::from_str("npm:@package")
|
|
||||||
.err()
|
|
||||||
.unwrap()
|
|
||||||
.to_string(),
|
|
||||||
"Not a valid package: @package"
|
|
||||||
);
|
|
||||||
|
|
||||||
// should parse leading slash
|
|
||||||
assert_eq!(
|
|
||||||
NpmPackageReference::from_str("npm:/@package/test/sub_path").unwrap(),
|
|
||||||
NpmPackageReference {
|
|
||||||
req: NpmPackageReq {
|
|
||||||
name: "@package/test".to_string(),
|
|
||||||
version_req: None,
|
|
||||||
},
|
|
||||||
sub_path: Some("sub_path".to_string()),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
NpmPackageReference::from_str("npm:/test").unwrap(),
|
|
||||||
NpmPackageReference {
|
|
||||||
req: NpmPackageReq {
|
|
||||||
name: "test".to_string(),
|
|
||||||
version_req: None,
|
|
||||||
},
|
|
||||||
sub_path: None,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
NpmPackageReference::from_str("npm:/test/").unwrap(),
|
|
||||||
NpmPackageReference {
|
|
||||||
req: NpmPackageReq {
|
|
||||||
name: "test".to_string(),
|
|
||||||
version_req: None,
|
|
||||||
},
|
|
||||||
sub_path: None,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// should error for no name
|
|
||||||
assert_eq!(
|
|
||||||
NpmPackageReference::from_str("npm:/")
|
|
||||||
.err()
|
|
||||||
.unwrap()
|
|
||||||
.to_string(),
|
|
||||||
"Invalid npm specifier 'npm:/'. Did not contain a package name."
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
NpmPackageReference::from_str("npm://test")
|
|
||||||
.err()
|
|
||||||
.unwrap()
|
|
||||||
.to_string(),
|
|
||||||
"Invalid npm specifier 'npm://test'. Did not contain a package name."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sorting_folder_specifiers() {
|
fn sorting_folder_specifiers() {
|
||||||
fn cmp(a: &str, b: &str) -> Ordering {
|
fn cmp(a: &str, b: &str) -> Ordering {
|
||||||
|
|
|
@ -13,10 +13,10 @@ use tar::EntryType;
|
||||||
|
|
||||||
use super::cache::with_folder_sync_lock;
|
use super::cache::with_folder_sync_lock;
|
||||||
use super::registry::NpmPackageVersionDistInfo;
|
use super::registry::NpmPackageVersionDistInfo;
|
||||||
use super::semver::NpmVersion;
|
use crate::semver::Version;
|
||||||
|
|
||||||
pub fn verify_and_extract_tarball(
|
pub fn verify_and_extract_tarball(
|
||||||
package: (&str, &NpmVersion),
|
package: (&str, &Version),
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
dist_info: &NpmPackageVersionDistInfo,
|
dist_info: &NpmPackageVersionDistInfo,
|
||||||
output_folder: &Path,
|
output_folder: &Path,
|
||||||
|
@ -29,7 +29,7 @@ pub fn verify_and_extract_tarball(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_tarball_integrity(
|
fn verify_tarball_integrity(
|
||||||
package: (&str, &NpmVersion),
|
package: (&str, &Version),
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
npm_integrity: &str,
|
npm_integrity: &str,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
@ -120,12 +120,11 @@ fn extract_tarball(data: &[u8], output_folder: &Path) -> Result<(), AnyError> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::npm::semver::NpmVersion;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_verify_tarball() {
|
pub fn test_verify_tarball() {
|
||||||
let package_name = "package".to_string();
|
let package_name = "package".to_string();
|
||||||
let package_version = NpmVersion::parse("1.0.0").unwrap();
|
let package_version = Version::parse_from_npm("1.0.0").unwrap();
|
||||||
let package = (package_name.as_str(), &package_version);
|
let package = (package_name.as_str(), &package_version);
|
||||||
let actual_checksum =
|
let actual_checksum =
|
||||||
"z4phnx7vul3xvchq1m2ab9yg5aulvxxcg/spidns6c5h0ne8xyxysp+dgnkhfuwvy7kxvudbeoglodj6+sfapg==";
|
"z4phnx7vul3xvchq1m2ab9yg5aulvxxcg/spidns6c5h0ne8xyxysp+dgnkhfuwvy7kxvudbeoglodj6+sfapg==";
|
||||||
|
|
200
cli/semver/mod.rs
Normal file
200
cli/semver/mod.rs
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use deno_core::error::AnyError;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
mod npm;
|
||||||
|
mod range;
|
||||||
|
mod specifier;
|
||||||
|
|
||||||
|
use self::npm::parse_npm_version_req;
|
||||||
|
pub use self::range::Partial;
|
||||||
|
pub use self::range::VersionBoundKind;
|
||||||
|
pub use self::range::VersionRange;
|
||||||
|
pub use self::range::VersionRangeSet;
|
||||||
|
pub use self::range::XRange;
|
||||||
|
use self::specifier::parse_version_req_from_specifier;
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Clone, Debug, PartialEq, Eq, Default, Hash, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
|
pub struct Version {
|
||||||
|
pub major: u64,
|
||||||
|
pub minor: u64,
|
||||||
|
pub patch: u64,
|
||||||
|
pub pre: Vec<String>,
|
||||||
|
pub build: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Version {
|
||||||
|
pub fn parse_from_npm(text: &str) -> Result<Version, AnyError> {
|
||||||
|
npm::parse_npm_version(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Version {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
|
||||||
|
if !self.pre.is_empty() {
|
||||||
|
write!(f, "-")?;
|
||||||
|
for (i, part) in self.pre.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
write!(f, ".")?;
|
||||||
|
}
|
||||||
|
write!(f, "{part}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !self.build.is_empty() {
|
||||||
|
write!(f, "+")?;
|
||||||
|
for (i, part) in self.build.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
write!(f, ".")?;
|
||||||
|
}
|
||||||
|
write!(f, "{part}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::cmp::PartialOrd for Version {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::cmp::Ord for Version {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
let cmp_result = self.major.cmp(&other.major);
|
||||||
|
if cmp_result != Ordering::Equal {
|
||||||
|
return cmp_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cmp_result = self.minor.cmp(&other.minor);
|
||||||
|
if cmp_result != Ordering::Equal {
|
||||||
|
return cmp_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cmp_result = self.patch.cmp(&other.patch);
|
||||||
|
if cmp_result != Ordering::Equal {
|
||||||
|
return cmp_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only compare the pre-release and not the build as node-semver does
|
||||||
|
if self.pre.is_empty() && other.pre.is_empty() {
|
||||||
|
Ordering::Equal
|
||||||
|
} else if !self.pre.is_empty() && other.pre.is_empty() {
|
||||||
|
Ordering::Less
|
||||||
|
} else if self.pre.is_empty() && !other.pre.is_empty() {
|
||||||
|
Ordering::Greater
|
||||||
|
} else {
|
||||||
|
let mut i = 0;
|
||||||
|
loop {
|
||||||
|
let a = self.pre.get(i);
|
||||||
|
let b = other.pre.get(i);
|
||||||
|
if a.is_none() && b.is_none() {
|
||||||
|
return Ordering::Equal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/npm/node-semver/blob/4907647d169948a53156502867ed679268063a9f/internal/identifiers.js
|
||||||
|
let a = match a {
|
||||||
|
Some(a) => a,
|
||||||
|
None => return Ordering::Less,
|
||||||
|
};
|
||||||
|
let b = match b {
|
||||||
|
Some(b) => b,
|
||||||
|
None => return Ordering::Greater,
|
||||||
|
};
|
||||||
|
|
||||||
|
// prefer numbers
|
||||||
|
if let Ok(a_num) = a.parse::<u64>() {
|
||||||
|
if let Ok(b_num) = b.parse::<u64>() {
|
||||||
|
return a_num.cmp(&b_num);
|
||||||
|
} else {
|
||||||
|
return Ordering::Less;
|
||||||
|
}
|
||||||
|
} else if b.parse::<u64>().is_ok() {
|
||||||
|
return Ordering::Greater;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cmp_result = a.cmp(b);
|
||||||
|
if cmp_result != Ordering::Equal {
|
||||||
|
return cmp_result;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn is_valid_tag(value: &str) -> bool {
|
||||||
|
// we use the same rules as npm tags
|
||||||
|
npm::is_valid_npm_tag(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum RangeSetOrTag {
|
||||||
|
RangeSet(VersionRangeSet),
|
||||||
|
Tag(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A version requirement found in an npm package's dependencies.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct VersionReq {
|
||||||
|
raw_text: String,
|
||||||
|
inner: RangeSetOrTag,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VersionReq {
|
||||||
|
/// Creates a version requirement without examining the raw text.
|
||||||
|
pub fn from_raw_text_and_inner(
|
||||||
|
raw_text: String,
|
||||||
|
inner: RangeSetOrTag,
|
||||||
|
) -> Self {
|
||||||
|
Self { raw_text, inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_from_specifier(specifier: &str) -> Result<Self, AnyError> {
|
||||||
|
parse_version_req_from_specifier(specifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_from_npm(text: &str) -> Result<Self, AnyError> {
|
||||||
|
parse_npm_version_req(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn inner(&self) -> &RangeSetOrTag {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tag(&self) -> Option<&str> {
|
||||||
|
match &self.inner {
|
||||||
|
RangeSetOrTag::RangeSet(_) => None,
|
||||||
|
RangeSetOrTag::Tag(tag) => Some(tag.as_str()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matches(&self, version: &Version) -> bool {
|
||||||
|
match &self.inner {
|
||||||
|
RangeSetOrTag::RangeSet(range_set) => range_set.satisfies(version),
|
||||||
|
RangeSetOrTag::Tag(_) => panic!(
|
||||||
|
"programming error: cannot use matches with a tag: {}",
|
||||||
|
self.raw_text
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn version_text(&self) -> &str {
|
||||||
|
&self.raw_text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for VersionReq {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", &self.raw_text)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,29 +1,17 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use deno_core::anyhow::Context;
|
use deno_core::anyhow::Context;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use monch::*;
|
use monch::*;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use crate::npm::resolution::NpmVersionMatcher;
|
use super::Partial;
|
||||||
|
use super::RangeSetOrTag;
|
||||||
use self::range::Partial;
|
use super::Version;
|
||||||
use self::range::VersionBoundKind;
|
use super::VersionBoundKind;
|
||||||
use self::range::VersionRange;
|
use super::VersionRange;
|
||||||
use self::range::VersionRangeSet;
|
use super::VersionRangeSet;
|
||||||
|
use super::VersionReq;
|
||||||
use self::range::XRange;
|
use super::XRange;
|
||||||
pub use self::specifier::SpecifierVersionReq;
|
|
||||||
|
|
||||||
mod range;
|
|
||||||
mod specifier;
|
|
||||||
|
|
||||||
// A lot of the below is a re-implementation of parts of https://github.com/npm/node-semver
|
|
||||||
// which is Copyright (c) Isaac Z. Schlueter and Contributors (ISC License)
|
|
||||||
|
|
||||||
pub fn is_valid_npm_tag(value: &str) -> bool {
|
pub fn is_valid_npm_tag(value: &str) -> bool {
|
||||||
// a valid tag is anything that doesn't get url encoded
|
// a valid tag is anything that doesn't get url encoded
|
||||||
|
@ -33,200 +21,46 @@ pub fn is_valid_npm_tag(value: &str) -> bool {
|
||||||
.all(|c| c.is_alphanumeric() || matches!(c, '-' | '_' | '.' | '~'))
|
.all(|c| c.is_alphanumeric() || matches!(c, '-' | '_' | '.' | '~'))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
// A lot of the below is a re-implementation of parts of https://github.com/npm/node-semver
|
||||||
Clone, Debug, PartialEq, Eq, Default, Hash, Serialize, Deserialize,
|
// which is Copyright (c) Isaac Z. Schlueter and Contributors (ISC License)
|
||||||
)]
|
|
||||||
pub struct NpmVersion {
|
pub fn parse_npm_version(text: &str) -> Result<Version, AnyError> {
|
||||||
pub major: u64,
|
let text = text.trim();
|
||||||
pub minor: u64,
|
with_failure_handling(|input| {
|
||||||
pub patch: u64,
|
let (input, _) = maybe(ch('='))(input)?; // skip leading =
|
||||||
pub pre: Vec<String>,
|
let (input, _) = skip_whitespace(input)?;
|
||||||
pub build: Vec<String>,
|
let (input, _) = maybe(ch('v'))(input)?; // skip leading v
|
||||||
|
let (input, _) = skip_whitespace(input)?;
|
||||||
|
let (input, major) = nr(input)?;
|
||||||
|
let (input, _) = ch('.')(input)?;
|
||||||
|
let (input, minor) = nr(input)?;
|
||||||
|
let (input, _) = ch('.')(input)?;
|
||||||
|
let (input, patch) = nr(input)?;
|
||||||
|
let (input, q) = maybe(qualifier)(input)?;
|
||||||
|
let q = q.unwrap_or_default();
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
input,
|
||||||
|
Version {
|
||||||
|
major,
|
||||||
|
minor,
|
||||||
|
patch,
|
||||||
|
pre: q.pre,
|
||||||
|
build: q.build,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
})(text)
|
||||||
|
.with_context(|| format!("Invalid npm version '{text}'."))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for NpmVersion {
|
pub fn parse_npm_version_req(text: &str) -> Result<VersionReq, AnyError> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
let text = text.trim();
|
||||||
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
|
with_failure_handling(|input| {
|
||||||
if !self.pre.is_empty() {
|
map(inner, |inner| {
|
||||||
write!(f, "-")?;
|
VersionReq::from_raw_text_and_inner(input.to_string(), inner)
|
||||||
for (i, part) in self.pre.iter().enumerate() {
|
})(input)
|
||||||
if i > 0 {
|
})(text)
|
||||||
write!(f, ".")?;
|
.with_context(|| format!("Invalid npm version requirement '{text}'."))
|
||||||
}
|
|
||||||
write!(f, "{part}")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !self.build.is_empty() {
|
|
||||||
write!(f, "+")?;
|
|
||||||
for (i, part) in self.build.iter().enumerate() {
|
|
||||||
if i > 0 {
|
|
||||||
write!(f, ".")?;
|
|
||||||
}
|
|
||||||
write!(f, "{part}")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::cmp::PartialOrd for NpmVersion {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::cmp::Ord for NpmVersion {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
let cmp_result = self.major.cmp(&other.major);
|
|
||||||
if cmp_result != Ordering::Equal {
|
|
||||||
return cmp_result;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cmp_result = self.minor.cmp(&other.minor);
|
|
||||||
if cmp_result != Ordering::Equal {
|
|
||||||
return cmp_result;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cmp_result = self.patch.cmp(&other.patch);
|
|
||||||
if cmp_result != Ordering::Equal {
|
|
||||||
return cmp_result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// only compare the pre-release and not the build as node-semver does
|
|
||||||
if self.pre.is_empty() && other.pre.is_empty() {
|
|
||||||
Ordering::Equal
|
|
||||||
} else if !self.pre.is_empty() && other.pre.is_empty() {
|
|
||||||
Ordering::Less
|
|
||||||
} else if self.pre.is_empty() && !other.pre.is_empty() {
|
|
||||||
Ordering::Greater
|
|
||||||
} else {
|
|
||||||
let mut i = 0;
|
|
||||||
loop {
|
|
||||||
let a = self.pre.get(i);
|
|
||||||
let b = other.pre.get(i);
|
|
||||||
if a.is_none() && b.is_none() {
|
|
||||||
return Ordering::Equal;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/npm/node-semver/blob/4907647d169948a53156502867ed679268063a9f/internal/identifiers.js
|
|
||||||
let a = match a {
|
|
||||||
Some(a) => a,
|
|
||||||
None => return Ordering::Less,
|
|
||||||
};
|
|
||||||
let b = match b {
|
|
||||||
Some(b) => b,
|
|
||||||
None => return Ordering::Greater,
|
|
||||||
};
|
|
||||||
|
|
||||||
// prefer numbers
|
|
||||||
if let Ok(a_num) = a.parse::<u64>() {
|
|
||||||
if let Ok(b_num) = b.parse::<u64>() {
|
|
||||||
return a_num.cmp(&b_num);
|
|
||||||
} else {
|
|
||||||
return Ordering::Less;
|
|
||||||
}
|
|
||||||
} else if b.parse::<u64>().is_ok() {
|
|
||||||
return Ordering::Greater;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cmp_result = a.cmp(b);
|
|
||||||
if cmp_result != Ordering::Equal {
|
|
||||||
return cmp_result;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NpmVersion {
|
|
||||||
pub fn parse(text: &str) -> Result<Self, AnyError> {
|
|
||||||
let text = text.trim();
|
|
||||||
with_failure_handling(parse_npm_version)(text)
|
|
||||||
.with_context(|| format!("Invalid npm version '{text}'."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_npm_version(input: &str) -> ParseResult<NpmVersion> {
|
|
||||||
let (input, _) = maybe(ch('='))(input)?; // skip leading =
|
|
||||||
let (input, _) = skip_whitespace(input)?;
|
|
||||||
let (input, _) = maybe(ch('v'))(input)?; // skip leading v
|
|
||||||
let (input, _) = skip_whitespace(input)?;
|
|
||||||
let (input, major) = nr(input)?;
|
|
||||||
let (input, _) = ch('.')(input)?;
|
|
||||||
let (input, minor) = nr(input)?;
|
|
||||||
let (input, _) = ch('.')(input)?;
|
|
||||||
let (input, patch) = nr(input)?;
|
|
||||||
let (input, q) = maybe(qualifier)(input)?;
|
|
||||||
let q = q.unwrap_or_default();
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
NpmVersion {
|
|
||||||
major,
|
|
||||||
minor,
|
|
||||||
patch,
|
|
||||||
pre: q.pre,
|
|
||||||
build: q.build,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
||||||
enum NpmVersionReqInner {
|
|
||||||
RangeSet(VersionRangeSet),
|
|
||||||
Tag(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A version requirement found in an npm package's dependencies.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub struct NpmVersionReq {
|
|
||||||
raw_text: String,
|
|
||||||
inner: NpmVersionReqInner,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NpmVersionMatcher for NpmVersionReq {
|
|
||||||
fn tag(&self) -> Option<&str> {
|
|
||||||
match &self.inner {
|
|
||||||
NpmVersionReqInner::RangeSet(_) => None,
|
|
||||||
NpmVersionReqInner::Tag(tag) => Some(tag.as_str()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn matches(&self, version: &NpmVersion) -> bool {
|
|
||||||
match &self.inner {
|
|
||||||
NpmVersionReqInner::RangeSet(range_set) => range_set.satisfies(version),
|
|
||||||
NpmVersionReqInner::Tag(_) => panic!(
|
|
||||||
"programming error: cannot use matches with a tag: {}",
|
|
||||||
self.raw_text
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn version_text(&self) -> String {
|
|
||||||
self.raw_text.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for NpmVersionReq {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", &self.raw_text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NpmVersionReq {
|
|
||||||
pub fn parse(text: &str) -> Result<Self, AnyError> {
|
|
||||||
let text = text.trim();
|
|
||||||
with_failure_handling(parse_npm_version_req)(text)
|
|
||||||
.with_context(|| format!("Invalid npm version requirement '{text}'."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_npm_version_req(input: &str) -> ParseResult<NpmVersionReq> {
|
|
||||||
map(inner, |inner| NpmVersionReq {
|
|
||||||
raw_text: input.to_string(),
|
|
||||||
inner,
|
|
||||||
})(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/npm/node-semver/tree/4907647d169948a53156502867ed679268063a9f#range-grammar
|
// https://github.com/npm/node-semver/tree/4907647d169948a53156502867ed679268063a9f#range-grammar
|
||||||
|
@ -248,11 +82,11 @@ fn parse_npm_version_req(input: &str) -> ParseResult<NpmVersionReq> {
|
||||||
// part ::= nr | [-0-9A-Za-z]+
|
// part ::= nr | [-0-9A-Za-z]+
|
||||||
|
|
||||||
// range-set ::= range ( logical-or range ) *
|
// range-set ::= range ( logical-or range ) *
|
||||||
fn inner(input: &str) -> ParseResult<NpmVersionReqInner> {
|
fn inner(input: &str) -> ParseResult<RangeSetOrTag> {
|
||||||
if input.is_empty() {
|
if input.is_empty() {
|
||||||
return Ok((
|
return Ok((
|
||||||
input,
|
input,
|
||||||
NpmVersionReqInner::RangeSet(VersionRangeSet(vec![VersionRange::all()])),
|
RangeSetOrTag::RangeSet(VersionRangeSet(vec![VersionRange::all()])),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,10 +97,7 @@ fn inner(input: &str) -> ParseResult<NpmVersionReqInner> {
|
||||||
match ranges.remove(0) {
|
match ranges.remove(0) {
|
||||||
RangeOrInvalid::Invalid(invalid) => {
|
RangeOrInvalid::Invalid(invalid) => {
|
||||||
if is_valid_npm_tag(invalid.text) {
|
if is_valid_npm_tag(invalid.text) {
|
||||||
return Ok((
|
return Ok((input, RangeSetOrTag::Tag(invalid.text.to_string())));
|
||||||
input,
|
|
||||||
NpmVersionReqInner::Tag(invalid.text.to_string()),
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
return Err(invalid.failure);
|
return Err(invalid.failure);
|
||||||
}
|
}
|
||||||
|
@ -282,7 +113,7 @@ fn inner(input: &str) -> ParseResult<NpmVersionReqInner> {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|r| r.into_range())
|
.filter_map(|r| r.into_range())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
Ok((input, NpmVersionReqInner::RangeSet(VersionRangeSet(ranges))))
|
Ok((input, RangeSetOrTag::RangeSet(VersionRangeSet(ranges))))
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RangeOrInvalid<'a> {
|
enum RangeOrInvalid<'a> {
|
||||||
|
@ -582,21 +413,21 @@ mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
struct NpmVersionReqTester(NpmVersionReq);
|
struct NpmVersionReqTester(VersionReq);
|
||||||
|
|
||||||
impl NpmVersionReqTester {
|
impl NpmVersionReqTester {
|
||||||
fn new(text: &str) -> Self {
|
fn new(text: &str) -> Self {
|
||||||
Self(NpmVersionReq::parse(text).unwrap())
|
Self(parse_npm_version_req(text).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches(&self, version: &str) -> bool {
|
fn matches(&self, version: &str) -> bool {
|
||||||
self.0.matches(&NpmVersion::parse(version).unwrap())
|
self.0.matches(&parse_npm_version(version).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn npm_version_req_with_v() {
|
pub fn npm_version_req_with_v() {
|
||||||
assert!(NpmVersionReq::parse("v1.0.0").is_ok());
|
assert!(parse_npm_version_req("v1.0.0").is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -641,8 +472,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn npm_version_req_with_tag() {
|
pub fn npm_version_req_with_tag() {
|
||||||
let req = NpmVersionReq::parse("latest").unwrap();
|
let req = parse_npm_version_req("latest").unwrap();
|
||||||
assert_eq!(req.inner, NpmVersionReqInner::Tag("latest".to_string()));
|
assert_eq!(req.tag(), Some("latest"));
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! assert_cmp {
|
macro_rules! assert_cmp {
|
||||||
|
@ -660,8 +491,8 @@ mod tests {
|
||||||
|
|
||||||
macro_rules! test_compare {
|
macro_rules! test_compare {
|
||||||
($a:expr, $b:expr, $expected:expr) => {
|
($a:expr, $b:expr, $expected:expr) => {
|
||||||
let a = NpmVersion::parse($a).unwrap();
|
let a = parse_npm_version($a).unwrap();
|
||||||
let b = NpmVersion::parse($b).unwrap();
|
let b = parse_npm_version($b).unwrap();
|
||||||
assert_cmp!(a, b, $expected);
|
assert_cmp!(a, b, $expected);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -759,8 +590,8 @@ mod tests {
|
||||||
("1.2.3-r100", "1.2.3-R2"),
|
("1.2.3-r100", "1.2.3-R2"),
|
||||||
];
|
];
|
||||||
for (a, b) in fixtures {
|
for (a, b) in fixtures {
|
||||||
let a = NpmVersion::parse(a).unwrap();
|
let a = parse_npm_version(a).unwrap();
|
||||||
let b = NpmVersion::parse(b).unwrap();
|
let b = parse_npm_version(b).unwrap();
|
||||||
assert_cmp!(a, b, Ordering::Greater);
|
assert_cmp!(a, b, Ordering::Greater);
|
||||||
assert_cmp!(b, a, Ordering::Less);
|
assert_cmp!(b, a, Ordering::Less);
|
||||||
assert_cmp!(a, a, Ordering::Equal);
|
assert_cmp!(a, a, Ordering::Equal);
|
||||||
|
@ -856,12 +687,14 @@ mod tests {
|
||||||
(">=09090", ">=9090.0.0"),
|
(">=09090", ">=9090.0.0"),
|
||||||
];
|
];
|
||||||
for (range_text, expected) in fixtures {
|
for (range_text, expected) in fixtures {
|
||||||
let range = NpmVersionReq::parse(range_text).unwrap();
|
let range = parse_npm_version_req(range_text).unwrap();
|
||||||
let expected_range = NpmVersionReq::parse(expected).unwrap();
|
let expected_range = parse_npm_version_req(expected).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
range.inner, expected_range.inner,
|
range.inner(),
|
||||||
|
expected_range.inner(),
|
||||||
"failed for {} and {}",
|
"failed for {} and {}",
|
||||||
range_text, expected
|
range_text,
|
||||||
|
expected
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -980,8 +813,8 @@ mod tests {
|
||||||
("1.0.0-alpha.13", "1.0.0-alpha.13"),
|
("1.0.0-alpha.13", "1.0.0-alpha.13"),
|
||||||
];
|
];
|
||||||
for (req_text, version_text) in fixtures {
|
for (req_text, version_text) in fixtures {
|
||||||
let req = NpmVersionReq::parse(req_text).unwrap();
|
let req = parse_npm_version_req(req_text).unwrap();
|
||||||
let version = NpmVersion::parse(version_text).unwrap();
|
let version = parse_npm_version(version_text).unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
req.matches(&version),
|
req.matches(&version),
|
||||||
"Checking {req_text} satisfies {version_text}"
|
"Checking {req_text} satisfies {version_text}"
|
||||||
|
@ -1077,8 +910,8 @@ mod tests {
|
||||||
];
|
];
|
||||||
|
|
||||||
for (req_text, version_text) in fixtures {
|
for (req_text, version_text) in fixtures {
|
||||||
let req = NpmVersionReq::parse(req_text).unwrap();
|
let req = parse_npm_version_req(req_text).unwrap();
|
||||||
let version = NpmVersion::parse(version_text).unwrap();
|
let version = parse_npm_version(version_text).unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
!req.matches(&version),
|
!req.matches(&version),
|
||||||
"Checking {req_text} not satisfies {version_text}"
|
"Checking {req_text} not satisfies {version_text}"
|
||||||
|
@ -1123,8 +956,8 @@ mod tests {
|
||||||
];
|
];
|
||||||
|
|
||||||
for (req_text, version_text, satisfies) in fixtures {
|
for (req_text, version_text, satisfies) in fixtures {
|
||||||
let req = NpmVersionReq::parse(req_text).unwrap();
|
let req = parse_npm_version_req(req_text).unwrap();
|
||||||
let version = NpmVersion::parse(version_text).unwrap();
|
let version = parse_npm_version(version_text).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
req.matches(&version),
|
req.matches(&version),
|
||||||
*satisfies,
|
*satisfies,
|
||||||
|
@ -1146,7 +979,7 @@ mod tests {
|
||||||
for req_text in fixtures {
|
for req_text in fixtures {
|
||||||
// when it has no space, this is considered invalid
|
// when it has no space, this is considered invalid
|
||||||
// by node semver so we should error
|
// by node semver so we should error
|
||||||
assert!(NpmVersionReq::parse(req_text).is_err());
|
assert!(parse_npm_version_req(req_text).is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,14 +5,14 @@ use std::cmp::Ordering;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use super::NpmVersion;
|
use super::Version;
|
||||||
|
|
||||||
/// Collection of ranges.
|
/// Collection of ranges.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct VersionRangeSet(pub Vec<VersionRange>);
|
pub struct VersionRangeSet(pub Vec<VersionRange>);
|
||||||
|
|
||||||
impl VersionRangeSet {
|
impl VersionRangeSet {
|
||||||
pub fn satisfies(&self, version: &NpmVersion) -> bool {
|
pub fn satisfies(&self, version: &Version) -> bool {
|
||||||
self.0.iter().any(|r| r.satisfies(version))
|
self.0.iter().any(|r| r.satisfies(version))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,15 +24,15 @@ pub enum RangeBound {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RangeBound {
|
impl RangeBound {
|
||||||
pub fn inclusive(version: NpmVersion) -> Self {
|
pub fn inclusive(version: Version) -> Self {
|
||||||
Self::version(VersionBoundKind::Inclusive, version)
|
Self::version(VersionBoundKind::Inclusive, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exclusive(version: NpmVersion) -> Self {
|
pub fn exclusive(version: Version) -> Self {
|
||||||
Self::version(VersionBoundKind::Exclusive, version)
|
Self::version(VersionBoundKind::Exclusive, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn version(kind: VersionBoundKind, version: NpmVersion) -> Self {
|
pub fn version(kind: VersionBoundKind, version: Version) -> Self {
|
||||||
Self::Version(VersionBound::new(kind, version))
|
Self::Version(VersionBound::new(kind, version))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ impl RangeBound {
|
||||||
|
|
||||||
pub fn has_pre_with_exact_major_minor_patch(
|
pub fn has_pre_with_exact_major_minor_patch(
|
||||||
&self,
|
&self,
|
||||||
version: &NpmVersion,
|
version: &Version,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if let RangeBound::Version(self_version) = &self {
|
if let RangeBound::Version(self_version) = &self {
|
||||||
if !self_version.version.pre.is_empty()
|
if !self_version.version.pre.is_empty()
|
||||||
|
@ -103,11 +103,11 @@ pub enum VersionBoundKind {
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct VersionBound {
|
pub struct VersionBound {
|
||||||
pub kind: VersionBoundKind,
|
pub kind: VersionBoundKind,
|
||||||
pub version: NpmVersion,
|
pub version: Version,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VersionBound {
|
impl VersionBound {
|
||||||
pub fn new(kind: VersionBoundKind, version: NpmVersion) -> Self {
|
pub fn new(kind: VersionBoundKind, version: Version) -> Self {
|
||||||
Self { kind, version }
|
Self { kind, version }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ impl VersionRange {
|
||||||
VersionRange {
|
VersionRange {
|
||||||
start: RangeBound::Version(VersionBound {
|
start: RangeBound::Version(VersionBound {
|
||||||
kind: VersionBoundKind::Inclusive,
|
kind: VersionBoundKind::Inclusive,
|
||||||
version: NpmVersion::default(),
|
version: Version::default(),
|
||||||
}),
|
}),
|
||||||
end: RangeBound::Unbounded,
|
end: RangeBound::Unbounded,
|
||||||
}
|
}
|
||||||
|
@ -133,11 +133,11 @@ impl VersionRange {
|
||||||
VersionRange {
|
VersionRange {
|
||||||
start: RangeBound::Version(VersionBound {
|
start: RangeBound::Version(VersionBound {
|
||||||
kind: VersionBoundKind::Inclusive,
|
kind: VersionBoundKind::Inclusive,
|
||||||
version: NpmVersion::default(),
|
version: Version::default(),
|
||||||
}),
|
}),
|
||||||
end: RangeBound::Version(VersionBound {
|
end: RangeBound::Version(VersionBound {
|
||||||
kind: VersionBoundKind::Exclusive,
|
kind: VersionBoundKind::Exclusive,
|
||||||
version: NpmVersion::default(),
|
version: Version::default(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ impl VersionRange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn satisfies(&self, version: &NpmVersion) -> bool {
|
pub fn satisfies(&self, version: &Version) -> bool {
|
||||||
let satisfies = self.min_satisfies(version) && self.max_satisfies(version);
|
let satisfies = self.min_satisfies(version) && self.max_satisfies(version);
|
||||||
if satisfies && !version.pre.is_empty() {
|
if satisfies && !version.pre.is_empty() {
|
||||||
// check either side of the range has a pre and same version
|
// check either side of the range has a pre and same version
|
||||||
|
@ -165,7 +165,7 @@ impl VersionRange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn min_satisfies(&self, version: &NpmVersion) -> bool {
|
fn min_satisfies(&self, version: &Version) -> bool {
|
||||||
match &self.start {
|
match &self.start {
|
||||||
RangeBound::Unbounded => true,
|
RangeBound::Unbounded => true,
|
||||||
RangeBound::Version(bound) => match version.cmp(&bound.version) {
|
RangeBound::Version(bound) => match version.cmp(&bound.version) {
|
||||||
|
@ -176,7 +176,7 @@ impl VersionRange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn max_satisfies(&self, version: &NpmVersion) -> bool {
|
fn max_satisfies(&self, version: &Version) -> bool {
|
||||||
match &self.end {
|
match &self.end {
|
||||||
RangeBound::Unbounded => true,
|
RangeBound::Unbounded => true,
|
||||||
RangeBound::Version(bound) => match version.cmp(&bound.version) {
|
RangeBound::Version(bound) => match version.cmp(&bound.version) {
|
||||||
|
@ -219,14 +219,14 @@ impl Partial {
|
||||||
let end = match self.major {
|
let end = match self.major {
|
||||||
XRange::Wildcard => return VersionRange::all(),
|
XRange::Wildcard => return VersionRange::all(),
|
||||||
XRange::Val(major) => match self.minor {
|
XRange::Val(major) => match self.minor {
|
||||||
XRange::Wildcard => NpmVersion {
|
XRange::Wildcard => Version {
|
||||||
major: major + 1,
|
major: major + 1,
|
||||||
minor: 0,
|
minor: 0,
|
||||||
patch: 0,
|
patch: 0,
|
||||||
pre: Vec::new(),
|
pre: Vec::new(),
|
||||||
build: Vec::new(),
|
build: Vec::new(),
|
||||||
},
|
},
|
||||||
XRange::Val(minor) => NpmVersion {
|
XRange::Val(minor) => Version {
|
||||||
major,
|
major,
|
||||||
minor: minor + 1,
|
minor: minor + 1,
|
||||||
patch: 0,
|
patch: 0,
|
||||||
|
@ -248,7 +248,7 @@ impl Partial {
|
||||||
let end = match self.major {
|
let end = match self.major {
|
||||||
XRange::Wildcard => return VersionRange::all(),
|
XRange::Wildcard => return VersionRange::all(),
|
||||||
XRange::Val(major) => {
|
XRange::Val(major) => {
|
||||||
let next_major = NpmVersion {
|
let next_major = Version {
|
||||||
major: major + 1,
|
major: major + 1,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -258,7 +258,7 @@ impl Partial {
|
||||||
match self.minor {
|
match self.minor {
|
||||||
XRange::Wildcard => next_major,
|
XRange::Wildcard => next_major,
|
||||||
XRange::Val(minor) => {
|
XRange::Val(minor) => {
|
||||||
let next_minor = NpmVersion {
|
let next_minor = Version {
|
||||||
minor: minor + 1,
|
minor: minor + 1,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -267,7 +267,7 @@ impl Partial {
|
||||||
} else {
|
} else {
|
||||||
match self.patch {
|
match self.patch {
|
||||||
XRange::Wildcard => next_minor,
|
XRange::Wildcard => next_minor,
|
||||||
XRange::Val(patch) => NpmVersion {
|
XRange::Val(patch) => Version {
|
||||||
patch: patch + 1,
|
patch: patch + 1,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
@ -288,7 +288,7 @@ impl Partial {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_lower_bound(&self) -> RangeBound {
|
pub fn as_lower_bound(&self) -> RangeBound {
|
||||||
RangeBound::inclusive(NpmVersion {
|
RangeBound::inclusive(Version {
|
||||||
major: match self.major {
|
major: match self.major {
|
||||||
XRange::Val(val) => val,
|
XRange::Val(val) => val,
|
||||||
XRange::Wildcard => 0,
|
XRange::Wildcard => 0,
|
||||||
|
@ -307,7 +307,7 @@ impl Partial {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_upper_bound(&self) -> RangeBound {
|
pub fn as_upper_bound(&self) -> RangeBound {
|
||||||
let mut end = NpmVersion::default();
|
let mut end = Version::default();
|
||||||
let mut kind = VersionBoundKind::Inclusive;
|
let mut kind = VersionBoundKind::Inclusive;
|
||||||
match self.patch {
|
match self.patch {
|
||||||
XRange::Wildcard => {
|
XRange::Wildcard => {
|
||||||
|
@ -363,7 +363,7 @@ impl Partial {
|
||||||
}
|
}
|
||||||
XRange::Val(val) => val,
|
XRange::Val(val) => val,
|
||||||
};
|
};
|
||||||
let version = NpmVersion {
|
let version = Version {
|
||||||
major,
|
major,
|
||||||
minor,
|
minor,
|
||||||
patch,
|
patch,
|
||||||
|
@ -387,7 +387,7 @@ impl Partial {
|
||||||
},
|
},
|
||||||
XRange::Val(major) => major,
|
XRange::Val(major) => major,
|
||||||
};
|
};
|
||||||
let mut start = NpmVersion::default();
|
let mut start = Version::default();
|
||||||
|
|
||||||
if start_kind == VersionBoundKind::Inclusive {
|
if start_kind == VersionBoundKind::Inclusive {
|
||||||
start.pre = self.pre.clone();
|
start.pre = self.pre.clone();
|
||||||
|
@ -431,7 +431,7 @@ impl Partial {
|
||||||
},
|
},
|
||||||
XRange::Val(major) => major,
|
XRange::Val(major) => major,
|
||||||
};
|
};
|
||||||
let mut end = NpmVersion {
|
let mut end = Version {
|
||||||
major,
|
major,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -471,8 +471,8 @@ impl Partial {
|
||||||
XRange::Wildcard => return VersionRange::all(),
|
XRange::Wildcard => return VersionRange::all(),
|
||||||
XRange::Val(major) => major,
|
XRange::Val(major) => major,
|
||||||
};
|
};
|
||||||
let mut start = NpmVersion::default();
|
let mut start = Version::default();
|
||||||
let mut end = NpmVersion::default();
|
let mut end = Version::default();
|
||||||
start.major = major;
|
start.major = major;
|
||||||
end.major = major;
|
end.major = major;
|
||||||
match self.patch {
|
match self.patch {
|
|
@ -3,85 +3,56 @@
|
||||||
use deno_core::anyhow::Context;
|
use deno_core::anyhow::Context;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use monch::*;
|
use monch::*;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use super::is_valid_npm_tag;
|
|
||||||
use super::range::Partial;
|
use super::range::Partial;
|
||||||
use super::range::VersionRange;
|
use super::range::VersionRange;
|
||||||
|
use super::range::VersionRangeSet;
|
||||||
use super::range::XRange;
|
use super::range::XRange;
|
||||||
|
use super::RangeSetOrTag;
|
||||||
|
use super::VersionReq;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
use super::is_valid_tag;
|
||||||
enum SpecifierVersionReqInner {
|
|
||||||
Range(VersionRange),
|
|
||||||
Tag(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Version requirement found in npm specifiers.
|
pub fn parse_version_req_from_specifier(
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
text: &str,
|
||||||
pub struct SpecifierVersionReq {
|
) -> Result<VersionReq, AnyError> {
|
||||||
raw_text: String,
|
with_failure_handling(|input| {
|
||||||
inner: SpecifierVersionReqInner,
|
map_res(version_range, |result| {
|
||||||
}
|
let (new_input, range_result) = match result {
|
||||||
|
Ok((input, range)) => (input, Ok(range)),
|
||||||
impl std::fmt::Display for SpecifierVersionReq {
|
// use an empty string because we'll consider it a tag
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
Err(err) => ("", Err(err)),
|
||||||
write!(f, "{}", self.raw_text)
|
};
|
||||||
}
|
Ok((
|
||||||
}
|
new_input,
|
||||||
|
VersionReq::from_raw_text_and_inner(
|
||||||
impl SpecifierVersionReq {
|
input.to_string(),
|
||||||
pub fn parse(text: &str) -> Result<Self, AnyError> {
|
match range_result {
|
||||||
with_failure_handling(parse_npm_specifier)(text).with_context(|| {
|
Ok(range) => RangeSetOrTag::RangeSet(VersionRangeSet(vec![range])),
|
||||||
format!("Invalid npm specifier version requirement '{text}'.")
|
Err(err) => {
|
||||||
})
|
if !is_valid_tag(input) {
|
||||||
}
|
return Err(err);
|
||||||
|
} else {
|
||||||
pub fn range(&self) -> Option<&VersionRange> {
|
RangeSetOrTag::Tag(input.to_string())
|
||||||
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> {
|
|
||||||
map_res(version_range, |result| {
|
|
||||||
let (new_input, range_result) = match result {
|
|
||||||
Ok((input, range)) => (input, Ok(range)),
|
|
||||||
// use an empty string because we'll consider it a 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) => {
|
|
||||||
if !is_valid_npm_tag(input) {
|
|
||||||
return Err(err);
|
|
||||||
} else {
|
|
||||||
SpecifierVersionReqInner::Tag(input.to_string())
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
),
|
||||||
},
|
))
|
||||||
))
|
})(input)
|
||||||
})(input)
|
})(text)
|
||||||
|
.with_context(|| {
|
||||||
|
format!("Invalid npm specifier version requirement '{text}'.")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Although the code below looks very similar to what's used for
|
// Note: Although the code below looks very similar to what's used for
|
||||||
// parsing npm version requirements, the code here is more strict
|
// parsing npm version requirements, the code here is more strict
|
||||||
// in order to not allow for people to get ridiculous when using
|
// in order to not allow for people to get ridiculous when using
|
||||||
// npm specifiers.
|
// npm specifiers.
|
||||||
|
//
|
||||||
|
// A lot of the code below is adapted from https://github.com/npm/node-semver
|
||||||
|
// which is Copyright (c) Isaac Z. Schlueter and Contributors (ISC License)
|
||||||
|
|
||||||
// version_range ::= partial | tilde | caret
|
// version_range ::= partial | tilde | caret
|
||||||
fn version_range(input: &str) -> ParseResult<VersionRange> {
|
fn version_range(input: &str) -> ParseResult<VersionRange> {
|
||||||
|
@ -198,23 +169,18 @@ fn part(input: &str) -> ParseResult<&str> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::npm::semver::NpmVersion;
|
use super::super::Version;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
struct VersionReqTester(SpecifierVersionReq);
|
struct VersionReqTester(VersionReq);
|
||||||
|
|
||||||
impl VersionReqTester {
|
impl VersionReqTester {
|
||||||
fn new(text: &str) -> Self {
|
fn new(text: &str) -> Self {
|
||||||
Self(SpecifierVersionReq::parse(text).unwrap())
|
Self(parse_version_req_from_specifier(text).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches(&self, version: &str) -> bool {
|
fn matches(&self, version: &str) -> bool {
|
||||||
self
|
self.0.matches(&Version::parse_from_npm(version).unwrap())
|
||||||
.0
|
|
||||||
.range()
|
|
||||||
.map(|r| r.satisfies(&NpmVersion::parse(version).unwrap()))
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,7 +259,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_tag() {
|
fn parses_tag() {
|
||||||
let latest_tag = SpecifierVersionReq::parse("latest").unwrap();
|
let latest_tag = VersionReq::parse_from_specifier("latest").unwrap();
|
||||||
assert_eq!(latest_tag.tag().unwrap(), "latest");
|
assert_eq!(latest_tag.tag().unwrap(), "latest");
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue