mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
refactor: use deno_graph's semver and npm structs (#17791)
This commit is contained in:
parent
b34e751a5b
commit
c4b9a91e27
35 changed files with 63 additions and 2512 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -851,7 +851,6 @@ dependencies = [
|
|||
"ring",
|
||||
"rustyline",
|
||||
"rustyline-derive",
|
||||
"semver 1.0.14",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
|
@ -1092,9 +1091,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "deno_graph"
|
||||
version = "0.43.1"
|
||||
version = "0.43.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bfe0ba0ce8e54d198821714439a46b97c97de112d8a53a9a53d87c400da578c"
|
||||
checksum = "2c8c63d016d4bbc7fcd0cecfb3903b92591ace5ba61229e2c5cf61337b21d165"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"data-url",
|
||||
|
|
|
@ -104,7 +104,6 @@ ring = "=0.16.20"
|
|||
rusqlite = { version = "=0.28.0", features = ["unlock_notify", "bundled"] }
|
||||
rustls = "0.20.5"
|
||||
rustls-pemfile = "1.0.0"
|
||||
semver = "=1.0.14"
|
||||
serde = { version = "1.0.149", features = ["derive"] }
|
||||
serde_bytes = "0.11"
|
||||
serde_json = "1.0.85"
|
||||
|
|
|
@ -46,7 +46,7 @@ deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "dep_gra
|
|||
deno_core.workspace = true
|
||||
deno_doc = "0.55.0"
|
||||
deno_emit = "0.15.0"
|
||||
deno_graph = "0.43.1"
|
||||
deno_graph = "0.43.2"
|
||||
deno_lint = { version = "0.38.0", features = ["docs"] }
|
||||
deno_lockfile.workspace = true
|
||||
deno_runtime.workspace = true
|
||||
|
@ -93,7 +93,6 @@ ring.workspace = true
|
|||
rustyline = { version = "=10.0.0", default-features = false, features = ["custom-bindings"] }
|
||||
rustyline-derive = "=0.7.0"
|
||||
secure_tempfile = { version = "=3.3.0", package = "tempfile" } # different name to discourage use in tests
|
||||
semver.workspace = true
|
||||
serde.workspace = true
|
||||
serde_repr.workspace = true
|
||||
shell-escape = "=0.1.5"
|
||||
|
|
5
cli/cache/mod.rs
vendored
5
cli/cache/mod.rs
vendored
|
@ -2,7 +2,6 @@
|
|||
|
||||
use crate::errors::get_error_class_name;
|
||||
use crate::file_fetcher::FileFetcher;
|
||||
use crate::npm;
|
||||
|
||||
use deno_core::futures;
|
||||
use deno_core::futures::FutureExt;
|
||||
|
@ -104,11 +103,11 @@ impl Loader for FetchCacher {
|
|||
) -> LoadFuture {
|
||||
if specifier.scheme() == "npm" {
|
||||
return Box::pin(futures::future::ready(
|
||||
match npm::NpmPackageReference::from_specifier(specifier) {
|
||||
match deno_graph::npm::NpmPackageReference::from_specifier(specifier) {
|
||||
Ok(_) => Ok(Some(deno_graph::source::LoadResponse::External {
|
||||
specifier: specifier.clone(),
|
||||
})),
|
||||
Err(err) => Err(err),
|
||||
Err(err) => Err(err.into()),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
|
|
@ -65,11 +65,13 @@ pub fn get_error_class_name(e: &AnyError) -> &'static str {
|
|||
.map(get_resolution_error_class)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
eprintln!(
|
||||
"Error '{}' contains boxed error of unknown type:{}",
|
||||
e,
|
||||
e.chain().map(|e| format!("\n {e:?}")).collect::<String>()
|
||||
);
|
||||
if cfg!(debug) {
|
||||
log::warn!(
|
||||
"Error '{}' contains boxed error of unknown type:{}",
|
||||
e,
|
||||
e.chain().map(|e| format!("\n {e:?}")).collect::<String>()
|
||||
);
|
||||
}
|
||||
"Error"
|
||||
})
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ use crate::args::LintOptions;
|
|||
use crate::graph_util;
|
||||
use crate::graph_util::enhanced_resolution_error_message;
|
||||
use crate::node;
|
||||
use crate::npm::NpmPackageReference;
|
||||
use crate::tools::lint::get_configured_rules;
|
||||
|
||||
use deno_ast::MediaType;
|
||||
|
@ -27,6 +26,7 @@ use deno_core::serde::Deserialize;
|
|||
use deno_core::serde_json;
|
||||
use deno_core::serde_json::json;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use deno_graph::npm::NpmPackageReference;
|
||||
use deno_graph::Resolution;
|
||||
use deno_graph::ResolutionError;
|
||||
use deno_graph::SpecifierError;
|
||||
|
|
|
@ -16,8 +16,6 @@ use crate::file_fetcher::SUPPORTED_SCHEMES;
|
|||
use crate::node;
|
||||
use crate::node::node_resolve_npm_reference;
|
||||
use crate::node::NodeResolution;
|
||||
use crate::npm::NpmPackageReference;
|
||||
use crate::npm::NpmPackageReq;
|
||||
use crate::npm::NpmPackageResolver;
|
||||
use crate::resolver::CliGraphResolver;
|
||||
use crate::util::path::specifier_to_file_path;
|
||||
|
@ -32,6 +30,8 @@ use deno_core::futures::future;
|
|||
use deno_core::parking_lot::Mutex;
|
||||
use deno_core::url;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use deno_graph::npm::NpmPackageReference;
|
||||
use deno_graph::npm::NpmPackageReq;
|
||||
use deno_graph::GraphImport;
|
||||
use deno_graph::Resolution;
|
||||
use deno_runtime::deno_node::NodeResolutionMode;
|
||||
|
|
|
@ -18,7 +18,6 @@ mod npm;
|
|||
mod ops;
|
||||
mod proc_state;
|
||||
mod resolver;
|
||||
mod semver;
|
||||
mod standalone;
|
||||
mod tools;
|
||||
mod tsc;
|
||||
|
|
|
@ -15,6 +15,8 @@ use deno_core::error::generic_error;
|
|||
use deno_core::error::AnyError;
|
||||
use deno_core::serde_json::Value;
|
||||
use deno_core::url::Url;
|
||||
use deno_graph::npm::NpmPackageReference;
|
||||
use deno_graph::npm::NpmPackageReq;
|
||||
use deno_runtime::deno_node;
|
||||
use deno_runtime::deno_node::errors;
|
||||
use deno_runtime::deno_node::find_builtin_node_module;
|
||||
|
@ -39,8 +41,6 @@ use regex::Regex;
|
|||
use crate::cache::NodeAnalysisCache;
|
||||
use crate::deno_std::CURRENT_STD_URL;
|
||||
use crate::file_fetcher::FileFetcher;
|
||||
use crate::npm::NpmPackageReference;
|
||||
use crate::npm::NpmPackageReq;
|
||||
use crate::npm::NpmPackageResolver;
|
||||
|
||||
mod analyze;
|
||||
|
|
|
@ -13,11 +13,11 @@ use deno_core::error::custom_error;
|
|||
use deno_core::error::AnyError;
|
||||
use deno_core::parking_lot::Mutex;
|
||||
use deno_core::url::Url;
|
||||
use deno_graph::semver::Version;
|
||||
|
||||
use crate::args::CacheSetting;
|
||||
use crate::cache::DenoDir;
|
||||
use crate::http_util::HttpClient;
|
||||
use crate::semver::Version;
|
||||
use crate::util::fs::canonicalize_path;
|
||||
use crate::util::fs::hard_link_dir_recursive;
|
||||
use crate::util::path::root_url_to_safe_local_dirname;
|
||||
|
@ -514,10 +514,10 @@ pub fn mixed_case_package_name_decode(name: &str) -> Option<String> {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use deno_core::url::Url;
|
||||
use deno_graph::semver::Version;
|
||||
|
||||
use super::ReadonlyNpmCache;
|
||||
use crate::npm::cache::NpmPackageCacheFolderId;
|
||||
use crate::semver::Version;
|
||||
|
||||
#[test]
|
||||
fn should_get_package_folder() {
|
||||
|
|
|
@ -12,9 +12,6 @@ pub use registry::NpmPackageVersionDistInfo;
|
|||
pub use registry::NpmRegistryApi;
|
||||
pub use registry::RealNpmRegistryApi;
|
||||
pub use resolution::resolve_graph_npm_info;
|
||||
pub use resolution::NpmPackageId;
|
||||
pub use resolution::NpmPackageReference;
|
||||
pub use resolution::NpmPackageReq;
|
||||
pub use resolution::NpmResolutionPackage;
|
||||
pub use resolution::NpmResolutionSnapshot;
|
||||
pub use resolvers::NpmPackageResolver;
|
||||
|
|
|
@ -19,14 +19,14 @@ use deno_core::parking_lot::Mutex;
|
|||
use deno_core::serde::Deserialize;
|
||||
use deno_core::serde_json;
|
||||
use deno_core::url::Url;
|
||||
use deno_graph::semver::Version;
|
||||
use deno_graph::semver::VersionReq;
|
||||
use deno_runtime::colors;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::args::CacheSetting;
|
||||
use crate::cache::CACHE_PERM;
|
||||
use crate::http_util::HttpClient;
|
||||
use crate::semver::Version;
|
||||
use crate::semver::VersionReq;
|
||||
use crate::util::fs::atomic_write_file;
|
||||
use crate::util::progress_bar::ProgressBar;
|
||||
|
||||
|
|
|
@ -13,6 +13,10 @@ use deno_core::error::AnyError;
|
|||
use deno_core::futures;
|
||||
use deno_core::parking_lot::Mutex;
|
||||
use deno_core::parking_lot::MutexGuard;
|
||||
use deno_graph::npm::NpmPackageId;
|
||||
use deno_graph::npm::NpmPackageReq;
|
||||
use deno_graph::semver::Version;
|
||||
use deno_graph::semver::VersionReq;
|
||||
use log::debug;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
|
@ -22,13 +26,9 @@ use crate::npm::registry::NpmDependencyEntryKind;
|
|||
use crate::npm::registry::NpmPackageInfo;
|
||||
use crate::npm::registry::NpmPackageVersionInfo;
|
||||
use crate::npm::NpmRegistryApi;
|
||||
use crate::semver::Version;
|
||||
use crate::semver::VersionReq;
|
||||
|
||||
use super::snapshot::NpmResolutionSnapshot;
|
||||
use super::snapshot::SnapshotPackageCopyIndexResolver;
|
||||
use super::NpmPackageId;
|
||||
use super::NpmPackageReq;
|
||||
use super::NpmResolutionPackage;
|
||||
|
||||
pub static LATEST_VERSION_REQ: Lazy<VersionReq> =
|
||||
|
@ -1113,10 +1113,10 @@ fn tag_to_version_info<'a>(
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use deno_graph::npm::NpmPackageReference;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::npm::registry::TestNpmRegistryApi;
|
||||
use crate::npm::NpmPackageReference;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
|
@ -3,15 +3,15 @@
|
|||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use deno_core::anyhow::Context;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::futures;
|
||||
use deno_core::parking_lot::RwLock;
|
||||
use deno_graph::npm::NpmPackageId;
|
||||
use deno_graph::npm::NpmPackageReq;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::args::Lockfile;
|
||||
use crate::semver::Version;
|
||||
|
||||
use self::graph::GraphDependencyResolver;
|
||||
use self::snapshot::NpmPackagesPartitioned;
|
||||
|
@ -23,157 +23,13 @@ use super::registry::RealNpmRegistryApi;
|
|||
use super::NpmRegistryApi;
|
||||
|
||||
mod graph;
|
||||
mod reference;
|
||||
mod snapshot;
|
||||
mod specifier;
|
||||
|
||||
use graph::Graph;
|
||||
pub use reference::NpmPackageReference;
|
||||
pub use reference::NpmPackageReq;
|
||||
pub use snapshot::NpmResolutionSnapshot;
|
||||
pub use specifier::resolve_graph_npm_info;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
pub struct NpmPackageId {
|
||||
pub name: String,
|
||||
pub version: Version,
|
||||
pub peer_dependencies: Vec<NpmPackageId>,
|
||||
}
|
||||
|
||||
impl NpmPackageId {
|
||||
#[allow(unused)]
|
||||
pub fn scope(&self) -> Option<&str> {
|
||||
if self.name.starts_with('@') && self.name.contains('/') {
|
||||
self.name.split('/').next()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_serialized(&self) -> String {
|
||||
self.as_serialized_with_level(0)
|
||||
}
|
||||
|
||||
fn as_serialized_with_level(&self, level: usize) -> String {
|
||||
// WARNING: This should not change because it's used in the lockfile
|
||||
let mut result = format!(
|
||||
"{}@{}",
|
||||
if level == 0 {
|
||||
self.name.to_string()
|
||||
} else {
|
||||
self.name.replace('/', "+")
|
||||
},
|
||||
self.version
|
||||
);
|
||||
for peer in &self.peer_dependencies {
|
||||
// unfortunately we can't do something like `_3` when
|
||||
// this gets deep because npm package names can start
|
||||
// with a number
|
||||
result.push_str(&"_".repeat(level + 1));
|
||||
result.push_str(&peer.as_serialized_with_level(level + 1));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn from_serialized(id: &str) -> Result<Self, AnyError> {
|
||||
use monch::*;
|
||||
|
||||
fn parse_name(input: &str) -> ParseResult<&str> {
|
||||
if_not_empty(substring(move |input| {
|
||||
for (pos, c) in input.char_indices() {
|
||||
// first character might be a scope, so skip it
|
||||
if pos > 0 && c == '@' {
|
||||
return Ok((&input[pos..], ()));
|
||||
}
|
||||
}
|
||||
ParseError::backtrace()
|
||||
}))(input)
|
||||
}
|
||||
|
||||
fn parse_version(input: &str) -> ParseResult<&str> {
|
||||
if_not_empty(substring(skip_while(|c| c != '_')))(input)
|
||||
}
|
||||
|
||||
fn parse_name_and_version(input: &str) -> ParseResult<(String, Version)> {
|
||||
let (input, name) = parse_name(input)?;
|
||||
let (input, _) = ch('@')(input)?;
|
||||
let at_version_input = input;
|
||||
let (input, version) = parse_version(input)?;
|
||||
match Version::parse_from_npm(version) {
|
||||
Ok(version) => Ok((input, (name.to_string(), version))),
|
||||
Err(err) => ParseError::fail(at_version_input, format!("{err:#}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_level_at_level<'a>(
|
||||
level: usize,
|
||||
) -> impl Fn(&'a str) -> ParseResult<'a, ()> {
|
||||
fn parse_level(input: &str) -> ParseResult<usize> {
|
||||
let level = input.chars().take_while(|c| *c == '_').count();
|
||||
Ok((&input[level..], level))
|
||||
}
|
||||
|
||||
move |input| {
|
||||
let (input, parsed_level) = parse_level(input)?;
|
||||
if parsed_level == level {
|
||||
Ok((input, ()))
|
||||
} else {
|
||||
ParseError::backtrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_peers_at_level<'a>(
|
||||
level: usize,
|
||||
) -> impl Fn(&'a str) -> ParseResult<'a, Vec<NpmPackageId>> {
|
||||
move |mut input| {
|
||||
let mut peers = Vec::new();
|
||||
while let Ok((level_input, _)) = parse_level_at_level(level)(input) {
|
||||
input = level_input;
|
||||
let peer_result = parse_id_at_level(level)(input)?;
|
||||
input = peer_result.0;
|
||||
peers.push(peer_result.1);
|
||||
}
|
||||
Ok((input, peers))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_id_at_level<'a>(
|
||||
level: usize,
|
||||
) -> impl Fn(&'a str) -> ParseResult<'a, NpmPackageId> {
|
||||
move |input| {
|
||||
let (input, (name, version)) = parse_name_and_version(input)?;
|
||||
let name = if level > 0 {
|
||||
name.replace('+', "/")
|
||||
} else {
|
||||
name
|
||||
};
|
||||
let (input, peer_dependencies) =
|
||||
parse_peers_at_level(level + 1)(input)?;
|
||||
Ok((
|
||||
input,
|
||||
NpmPackageId {
|
||||
name,
|
||||
version,
|
||||
peer_dependencies,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
with_failure_handling(parse_id_at_level(0))(id)
|
||||
.with_context(|| format!("Invalid npm package id '{id}'."))
|
||||
}
|
||||
|
||||
pub fn display(&self) -> String {
|
||||
// Don't implement std::fmt::Display because we don't
|
||||
// want this to be used by accident in certain scenarios.
|
||||
format!("{}@{}", self.name, self.version)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct NpmResolutionPackage {
|
||||
pub id: NpmPackageId,
|
||||
|
@ -399,46 +255,3 @@ impl NpmResolution {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serialize_npm_package_id() {
|
||||
let id = NpmPackageId {
|
||||
name: "pkg-a".to_string(),
|
||||
version: Version::parse_from_npm("1.2.3").unwrap(),
|
||||
peer_dependencies: vec![
|
||||
NpmPackageId {
|
||||
name: "pkg-b".to_string(),
|
||||
version: Version::parse_from_npm("3.2.1").unwrap(),
|
||||
peer_dependencies: vec![
|
||||
NpmPackageId {
|
||||
name: "pkg-c".to_string(),
|
||||
version: Version::parse_from_npm("1.3.2").unwrap(),
|
||||
peer_dependencies: vec![],
|
||||
},
|
||||
NpmPackageId {
|
||||
name: "pkg-d".to_string(),
|
||||
version: Version::parse_from_npm("2.3.4").unwrap(),
|
||||
peer_dependencies: vec![],
|
||||
},
|
||||
],
|
||||
},
|
||||
NpmPackageId {
|
||||
name: "pkg-e".to_string(),
|
||||
version: Version::parse_from_npm("2.3.1").unwrap(),
|
||||
peer_dependencies: vec![NpmPackageId {
|
||||
name: "pkg-f".to_string(),
|
||||
version: Version::parse_from_npm("2.3.1").unwrap(),
|
||||
peer_dependencies: vec![],
|
||||
}],
|
||||
},
|
||||
],
|
||||
};
|
||||
let serialized = id.as_serialized();
|
||||
assert_eq!(serialized, "pkg-a@1.2.3_pkg-b@3.2.1__pkg-c@1.3.2__pkg-d@2.3.4_pkg-e@2.3.1__pkg-f@2.3.1");
|
||||
assert_eq!(NpmPackageId::from_serialized(&serialized).unwrap(), id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,298 +0,0 @@
|
|||
// 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."
|
||||
);
|
||||
}
|
||||
}
|
|
@ -10,6 +10,9 @@ use deno_core::anyhow::Context;
|
|||
use deno_core::error::AnyError;
|
||||
use deno_core::futures;
|
||||
use deno_core::parking_lot::Mutex;
|
||||
use deno_graph::npm::NpmPackageId;
|
||||
use deno_graph::npm::NpmPackageReq;
|
||||
use deno_graph::semver::VersionReq;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
|
@ -19,10 +22,7 @@ use crate::npm::cache::NpmPackageCacheFolderId;
|
|||
use crate::npm::registry::NpmPackageVersionDistInfo;
|
||||
use crate::npm::registry::NpmRegistryApi;
|
||||
use crate::npm::registry::RealNpmRegistryApi;
|
||||
use crate::semver::VersionReq;
|
||||
|
||||
use super::NpmPackageId;
|
||||
use super::NpmPackageReq;
|
||||
use super::NpmResolutionPackage;
|
||||
|
||||
/// Packages partitioned by if they are "copy" packages or not.
|
||||
|
|
|
@ -6,13 +6,11 @@ use std::collections::HashSet;
|
|||
use std::collections::VecDeque;
|
||||
|
||||
use deno_ast::ModuleSpecifier;
|
||||
use deno_graph::npm::NpmPackageReference;
|
||||
use deno_graph::npm::NpmPackageReq;
|
||||
use deno_graph::semver::VersionReq;
|
||||
use deno_graph::ModuleGraph;
|
||||
|
||||
use crate::semver::VersionReq;
|
||||
|
||||
use super::NpmPackageReference;
|
||||
use super::NpmPackageReq;
|
||||
|
||||
pub struct GraphNpmInfo {
|
||||
/// The order of these package requirements is the order they
|
||||
/// should be resolved in.
|
||||
|
|
|
@ -10,6 +10,8 @@ use deno_core::error::AnyError;
|
|||
use deno_core::futures;
|
||||
use deno_core::futures::future::BoxFuture;
|
||||
use deno_core::url::Url;
|
||||
use deno_graph::npm::NpmPackageId;
|
||||
use deno_graph::npm::NpmPackageReq;
|
||||
use deno_runtime::deno_node::NodePermissions;
|
||||
use deno_runtime::deno_node::NodeResolutionMode;
|
||||
|
||||
|
@ -17,8 +19,6 @@ use crate::args::Lockfile;
|
|||
use crate::npm::cache::should_sync_download;
|
||||
use crate::npm::resolution::NpmResolutionSnapshot;
|
||||
use crate::npm::NpmCache;
|
||||
use crate::npm::NpmPackageId;
|
||||
use crate::npm::NpmPackageReq;
|
||||
use crate::npm::NpmResolutionPackage;
|
||||
|
||||
pub trait InnerNpmPackageResolver: Send + Sync {
|
||||
|
|
|
@ -12,6 +12,8 @@ use deno_core::error::AnyError;
|
|||
use deno_core::futures::future::BoxFuture;
|
||||
use deno_core::futures::FutureExt;
|
||||
use deno_core::url::Url;
|
||||
use deno_graph::npm::NpmPackageId;
|
||||
use deno_graph::npm::NpmPackageReq;
|
||||
use deno_runtime::deno_node::NodePermissions;
|
||||
use deno_runtime::deno_node::NodeResolutionMode;
|
||||
|
||||
|
@ -21,8 +23,6 @@ use crate::npm::resolution::NpmResolution;
|
|||
use crate::npm::resolution::NpmResolutionSnapshot;
|
||||
use crate::npm::resolvers::common::cache_packages;
|
||||
use crate::npm::NpmCache;
|
||||
use crate::npm::NpmPackageId;
|
||||
use crate::npm::NpmPackageReq;
|
||||
use crate::npm::NpmResolutionPackage;
|
||||
use crate::npm::RealNpmRegistryApi;
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ use deno_core::error::AnyError;
|
|||
use deno_core::futures::future::BoxFuture;
|
||||
use deno_core::futures::FutureExt;
|
||||
use deno_core::url::Url;
|
||||
use deno_graph::npm::NpmPackageId;
|
||||
use deno_graph::npm::NpmPackageReq;
|
||||
use deno_runtime::deno_core::futures;
|
||||
use deno_runtime::deno_node::NodePermissions;
|
||||
use deno_runtime::deno_node::NodeResolutionMode;
|
||||
|
@ -31,8 +33,6 @@ use crate::npm::cache::NpmPackageCacheFolderId;
|
|||
use crate::npm::resolution::NpmResolution;
|
||||
use crate::npm::resolution::NpmResolutionSnapshot;
|
||||
use crate::npm::NpmCache;
|
||||
use crate::npm::NpmPackageId;
|
||||
use crate::npm::NpmPackageReq;
|
||||
use crate::npm::NpmResolutionPackage;
|
||||
use crate::npm::RealNpmRegistryApi;
|
||||
use crate::util::fs::copy_dir_recursive;
|
||||
|
|
|
@ -11,6 +11,8 @@ use deno_core::error::custom_error;
|
|||
use deno_core::error::AnyError;
|
||||
use deno_core::parking_lot::Mutex;
|
||||
use deno_core::serde_json;
|
||||
use deno_graph::npm::NpmPackageId;
|
||||
use deno_graph::npm::NpmPackageReq;
|
||||
use deno_runtime::deno_node::NodePermissions;
|
||||
use deno_runtime::deno_node::NodeResolutionMode;
|
||||
use deno_runtime::deno_node::PathClean;
|
||||
|
@ -30,8 +32,6 @@ use crate::util::fs::canonicalize_path_maybe_not_exists;
|
|||
use self::common::InnerNpmPackageResolver;
|
||||
use self::local::LocalNpmPackageResolver;
|
||||
use super::NpmCache;
|
||||
use super::NpmPackageId;
|
||||
use super::NpmPackageReq;
|
||||
use super::NpmResolutionSnapshot;
|
||||
use super::RealNpmRegistryApi;
|
||||
|
||||
|
|
|
@ -7,13 +7,13 @@ use std::path::PathBuf;
|
|||
|
||||
use deno_core::anyhow::bail;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_graph::semver::Version;
|
||||
use flate2::read::GzDecoder;
|
||||
use tar::Archive;
|
||||
use tar::EntryType;
|
||||
|
||||
use super::cache::with_folder_sync_lock;
|
||||
use super::registry::NpmPackageVersionDistInfo;
|
||||
use crate::semver::Version;
|
||||
|
||||
pub fn verify_and_extract_tarball(
|
||||
package: (&str, &Version),
|
||||
|
|
|
@ -24,8 +24,6 @@ use crate::node;
|
|||
use crate::node::NodeResolution;
|
||||
use crate::npm::resolve_graph_npm_info;
|
||||
use crate::npm::NpmCache;
|
||||
use crate::npm::NpmPackageReference;
|
||||
use crate::npm::NpmPackageReq;
|
||||
use crate::npm::NpmPackageResolver;
|
||||
use crate::npm::RealNpmRegistryApi;
|
||||
use crate::resolver::CliGraphResolver;
|
||||
|
@ -45,6 +43,8 @@ use deno_core::resolve_url_or_path;
|
|||
use deno_core::CompiledWasmModuleStore;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use deno_core::SharedArrayBufferStore;
|
||||
use deno_graph::npm::NpmPackageReference;
|
||||
use deno_graph::npm::NpmPackageReq;
|
||||
use deno_graph::source::Loader;
|
||||
use deno_graph::source::Resolver;
|
||||
use deno_graph::ModuleGraph;
|
||||
|
|
|
@ -1,200 +0,0 @@
|
|||
// 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,985 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use deno_core::anyhow::Context;
|
||||
use deno_core::error::AnyError;
|
||||
use monch::*;
|
||||
|
||||
use super::Partial;
|
||||
use super::RangeSetOrTag;
|
||||
use super::Version;
|
||||
use super::VersionBoundKind;
|
||||
use super::VersionRange;
|
||||
use super::VersionRangeSet;
|
||||
use super::VersionReq;
|
||||
use super::XRange;
|
||||
|
||||
pub fn is_valid_npm_tag(value: &str) -> bool {
|
||||
// a valid tag is anything that doesn't get url encoded
|
||||
// https://github.com/npm/npm-package-arg/blob/103c0fda8ed8185733919c7c6c73937cfb2baf3a/lib/npa.js#L399-L401
|
||||
value
|
||||
.chars()
|
||||
.all(|c| c.is_alphanumeric() || matches!(c, '-' | '_' | '.' | '~'))
|
||||
}
|
||||
|
||||
// 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 parse_npm_version(text: &str) -> Result<Version, AnyError> {
|
||||
let text = text.trim();
|
||||
with_failure_handling(|input| {
|
||||
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,
|
||||
Version {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
pre: q.pre,
|
||||
build: q.build,
|
||||
},
|
||||
))
|
||||
})(text)
|
||||
.with_context(|| format!("Invalid npm version '{text}'."))
|
||||
}
|
||||
|
||||
pub fn parse_npm_version_req(text: &str) -> Result<VersionReq, AnyError> {
|
||||
let text = text.trim();
|
||||
with_failure_handling(|input| {
|
||||
map(inner, |inner| {
|
||||
VersionReq::from_raw_text_and_inner(input.to_string(), inner)
|
||||
})(input)
|
||||
})(text)
|
||||
.with_context(|| format!("Invalid npm version requirement '{text}'."))
|
||||
}
|
||||
|
||||
// https://github.com/npm/node-semver/tree/4907647d169948a53156502867ed679268063a9f#range-grammar
|
||||
// range-set ::= range ( logical-or range ) *
|
||||
// logical-or ::= ( ' ' ) * '||' ( ' ' ) *
|
||||
// range ::= hyphen | simple ( ' ' simple ) * | ''
|
||||
// hyphen ::= partial ' - ' partial
|
||||
// simple ::= primitive | partial | tilde | caret
|
||||
// primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial
|
||||
// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )?
|
||||
// xr ::= 'x' | 'X' | '*' | nr
|
||||
// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) *
|
||||
// tilde ::= '~' partial
|
||||
// caret ::= '^' partial
|
||||
// qualifier ::= ( '-' pre )? ( '+' build )?
|
||||
// pre ::= parts
|
||||
// build ::= parts
|
||||
// parts ::= part ( '.' part ) *
|
||||
// part ::= nr | [-0-9A-Za-z]+
|
||||
|
||||
// range-set ::= range ( logical-or range ) *
|
||||
fn inner(input: &str) -> ParseResult<RangeSetOrTag> {
|
||||
if input.is_empty() {
|
||||
return Ok((
|
||||
input,
|
||||
RangeSetOrTag::RangeSet(VersionRangeSet(vec![VersionRange::all()])),
|
||||
));
|
||||
}
|
||||
|
||||
let (input, mut ranges) =
|
||||
separated_list(range_or_invalid, logical_or)(input)?;
|
||||
|
||||
if ranges.len() == 1 {
|
||||
match ranges.remove(0) {
|
||||
RangeOrInvalid::Invalid(invalid) => {
|
||||
if is_valid_npm_tag(invalid.text) {
|
||||
return Ok((input, RangeSetOrTag::Tag(invalid.text.to_string())));
|
||||
} else {
|
||||
return Err(invalid.failure);
|
||||
}
|
||||
}
|
||||
RangeOrInvalid::Range(range) => {
|
||||
// add it back
|
||||
ranges.push(RangeOrInvalid::Range(range));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ranges = ranges
|
||||
.into_iter()
|
||||
.filter_map(|r| r.into_range())
|
||||
.collect::<Vec<_>>();
|
||||
Ok((input, RangeSetOrTag::RangeSet(VersionRangeSet(ranges))))
|
||||
}
|
||||
|
||||
enum RangeOrInvalid<'a> {
|
||||
Range(VersionRange),
|
||||
Invalid(InvalidRange<'a>),
|
||||
}
|
||||
|
||||
impl<'a> RangeOrInvalid<'a> {
|
||||
pub fn into_range(self) -> Option<VersionRange> {
|
||||
match self {
|
||||
RangeOrInvalid::Range(r) => {
|
||||
if r.is_none() {
|
||||
None
|
||||
} else {
|
||||
Some(r)
|
||||
}
|
||||
}
|
||||
RangeOrInvalid::Invalid(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InvalidRange<'a> {
|
||||
failure: ParseError<'a>,
|
||||
text: &'a str,
|
||||
}
|
||||
|
||||
fn range_or_invalid(input: &str) -> ParseResult<RangeOrInvalid> {
|
||||
let range_result =
|
||||
map_res(map(range, RangeOrInvalid::Range), |result| match result {
|
||||
Ok((input, range)) => {
|
||||
let is_end = input.is_empty() || logical_or(input).is_ok();
|
||||
if is_end {
|
||||
Ok((input, range))
|
||||
} else {
|
||||
ParseError::backtrace()
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
})(input);
|
||||
match range_result {
|
||||
Ok(result) => Ok(result),
|
||||
Err(failure) => {
|
||||
let (input, text) = invalid_range(input)?;
|
||||
Ok((
|
||||
input,
|
||||
RangeOrInvalid::Invalid(InvalidRange { failure, text }),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn invalid_range(input: &str) -> ParseResult<&str> {
|
||||
let end_index = input.find("||").unwrap_or(input.len());
|
||||
let text = input[..end_index].trim();
|
||||
Ok((&input[end_index..], text))
|
||||
}
|
||||
|
||||
// range ::= hyphen | simple ( ' ' simple ) * | ''
|
||||
fn range(input: &str) -> ParseResult<VersionRange> {
|
||||
or(
|
||||
map(hyphen, |hyphen| VersionRange {
|
||||
start: hyphen.start.as_lower_bound(),
|
||||
end: hyphen.end.as_upper_bound(),
|
||||
}),
|
||||
map(separated_list(simple, whitespace), |ranges| {
|
||||
let mut final_range = VersionRange::all();
|
||||
for range in ranges {
|
||||
final_range = final_range.clamp(&range);
|
||||
}
|
||||
final_range
|
||||
}),
|
||||
)(input)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Hyphen {
|
||||
start: Partial,
|
||||
end: Partial,
|
||||
}
|
||||
|
||||
// hyphen ::= partial ' - ' partial
|
||||
fn hyphen(input: &str) -> ParseResult<Hyphen> {
|
||||
let (input, first) = partial(input)?;
|
||||
let (input, _) = whitespace(input)?;
|
||||
let (input, _) = tag("-")(input)?;
|
||||
let (input, _) = whitespace(input)?;
|
||||
let (input, second) = partial(input)?;
|
||||
Ok((
|
||||
input,
|
||||
Hyphen {
|
||||
start: first,
|
||||
end: second,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
// logical-or ::= ( ' ' ) * '||' ( ' ' ) *
|
||||
fn logical_or(input: &str) -> ParseResult<&str> {
|
||||
delimited(skip_whitespace, tag("||"), skip_whitespace)(input)
|
||||
}
|
||||
|
||||
fn skip_whitespace_or_v(input: &str) -> ParseResult<()> {
|
||||
map(
|
||||
pair(skip_whitespace, pair(maybe(ch('v')), skip_whitespace)),
|
||||
|_| (),
|
||||
)(input)
|
||||
}
|
||||
|
||||
// simple ::= primitive | partial | tilde | caret
|
||||
fn simple(input: &str) -> ParseResult<VersionRange> {
|
||||
or4(
|
||||
map(preceded(tilde, partial), |partial| {
|
||||
partial.as_tilde_version_range()
|
||||
}),
|
||||
map(preceded(caret, partial), |partial| {
|
||||
partial.as_caret_version_range()
|
||||
}),
|
||||
map(primitive, |primitive| {
|
||||
let partial = primitive.partial;
|
||||
match primitive.kind {
|
||||
PrimitiveKind::Equal => partial.as_equal_range(),
|
||||
PrimitiveKind::GreaterThan => {
|
||||
partial.as_greater_than(VersionBoundKind::Exclusive)
|
||||
}
|
||||
PrimitiveKind::GreaterThanOrEqual => {
|
||||
partial.as_greater_than(VersionBoundKind::Inclusive)
|
||||
}
|
||||
PrimitiveKind::LessThan => {
|
||||
partial.as_less_than(VersionBoundKind::Exclusive)
|
||||
}
|
||||
PrimitiveKind::LessThanOrEqual => {
|
||||
partial.as_less_than(VersionBoundKind::Inclusive)
|
||||
}
|
||||
}
|
||||
}),
|
||||
map(partial, |partial| partial.as_equal_range()),
|
||||
)(input)
|
||||
}
|
||||
|
||||
fn tilde(input: &str) -> ParseResult<()> {
|
||||
fn raw_tilde(input: &str) -> ParseResult<()> {
|
||||
map(pair(or(tag("~>"), tag("~")), skip_whitespace_or_v), |_| ())(input)
|
||||
}
|
||||
|
||||
or(
|
||||
preceded(terminated(primitive_kind, whitespace), raw_tilde),
|
||||
raw_tilde,
|
||||
)(input)
|
||||
}
|
||||
|
||||
fn caret(input: &str) -> ParseResult<()> {
|
||||
fn raw_caret(input: &str) -> ParseResult<()> {
|
||||
map(pair(ch('^'), skip_whitespace_or_v), |_| ())(input)
|
||||
}
|
||||
|
||||
or(
|
||||
preceded(terminated(primitive_kind, whitespace), raw_caret),
|
||||
raw_caret,
|
||||
)(input)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum PrimitiveKind {
|
||||
GreaterThan,
|
||||
LessThan,
|
||||
GreaterThanOrEqual,
|
||||
LessThanOrEqual,
|
||||
Equal,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Primitive {
|
||||
kind: PrimitiveKind,
|
||||
partial: Partial,
|
||||
}
|
||||
|
||||
fn primitive(input: &str) -> ParseResult<Primitive> {
|
||||
let (input, kind) = primitive_kind(input)?;
|
||||
let (input, _) = skip_whitespace(input)?;
|
||||
let (input, partial) = partial(input)?;
|
||||
Ok((input, Primitive { kind, partial }))
|
||||
}
|
||||
|
||||
fn primitive_kind(input: &str) -> ParseResult<PrimitiveKind> {
|
||||
or5(
|
||||
map(tag(">="), |_| PrimitiveKind::GreaterThanOrEqual),
|
||||
map(tag("<="), |_| PrimitiveKind::LessThanOrEqual),
|
||||
map(ch('<'), |_| PrimitiveKind::LessThan),
|
||||
map(ch('>'), |_| PrimitiveKind::GreaterThan),
|
||||
map(ch('='), |_| PrimitiveKind::Equal),
|
||||
)(input)
|
||||
}
|
||||
|
||||
// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )?
|
||||
fn partial(input: &str) -> ParseResult<Partial> {
|
||||
let (input, _) = maybe(ch('v'))(input)?; // skip leading v
|
||||
let (input, major) = xr()(input)?;
|
||||
let (input, maybe_minor) = maybe(preceded(ch('.'), xr()))(input)?;
|
||||
let (input, maybe_patch) = if maybe_minor.is_some() {
|
||||
maybe(preceded(ch('.'), xr()))(input)?
|
||||
} else {
|
||||
(input, None)
|
||||
};
|
||||
let (input, qual) = if maybe_patch.is_some() {
|
||||
maybe(qualifier)(input)?
|
||||
} else {
|
||||
(input, None)
|
||||
};
|
||||
let qual = qual.unwrap_or_default();
|
||||
Ok((
|
||||
input,
|
||||
Partial {
|
||||
major,
|
||||
minor: maybe_minor.unwrap_or(XRange::Wildcard),
|
||||
patch: maybe_patch.unwrap_or(XRange::Wildcard),
|
||||
pre: qual.pre,
|
||||
build: qual.build,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
// xr ::= 'x' | 'X' | '*' | nr
|
||||
fn xr<'a>() -> impl Fn(&'a str) -> ParseResult<'a, XRange> {
|
||||
or(
|
||||
map(or3(tag("x"), tag("X"), tag("*")), |_| XRange::Wildcard),
|
||||
map(nr, XRange::Val),
|
||||
)
|
||||
}
|
||||
|
||||
// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) *
|
||||
fn nr(input: &str) -> ParseResult<u64> {
|
||||
// we do loose parsing to support people doing stuff like 01.02.03
|
||||
let (input, result) =
|
||||
if_not_empty(substring(skip_while(|c| c.is_ascii_digit())))(input)?;
|
||||
let val = match result.parse::<u64>() {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
return ParseError::fail(
|
||||
input,
|
||||
format!("Error parsing '{result}' to u64.\n\n{err:#}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
Ok((input, val))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct Qualifier {
|
||||
pre: Vec<String>,
|
||||
build: Vec<String>,
|
||||
}
|
||||
|
||||
// qualifier ::= ( '-' pre )? ( '+' build )?
|
||||
fn qualifier(input: &str) -> ParseResult<Qualifier> {
|
||||
let (input, pre_parts) = maybe(pre)(input)?;
|
||||
let (input, build_parts) = maybe(build)(input)?;
|
||||
Ok((
|
||||
input,
|
||||
Qualifier {
|
||||
pre: pre_parts.unwrap_or_default(),
|
||||
build: build_parts.unwrap_or_default(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
// pre ::= parts
|
||||
fn pre(input: &str) -> ParseResult<Vec<String>> {
|
||||
preceded(maybe(ch('-')), parts)(input)
|
||||
}
|
||||
|
||||
// build ::= parts
|
||||
fn build(input: &str) -> ParseResult<Vec<String>> {
|
||||
preceded(ch('+'), parts)(input)
|
||||
}
|
||||
|
||||
// parts ::= part ( '.' part ) *
|
||||
fn parts(input: &str) -> ParseResult<Vec<String>> {
|
||||
if_not_empty(map(separated_list(part, ch('.')), |text| {
|
||||
text.into_iter().map(ToOwned::to_owned).collect()
|
||||
}))(input)
|
||||
}
|
||||
|
||||
// part ::= nr | [-0-9A-Za-z]+
|
||||
fn part(input: &str) -> ParseResult<&str> {
|
||||
// nr is in the other set, so don't bother checking for it
|
||||
if_true(
|
||||
take_while(|c| c.is_ascii_alphanumeric() || c == '-'),
|
||||
|result| !result.is_empty(),
|
||||
)(input)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use super::*;
|
||||
|
||||
struct NpmVersionReqTester(VersionReq);
|
||||
|
||||
impl NpmVersionReqTester {
|
||||
fn new(text: &str) -> Self {
|
||||
Self(parse_npm_version_req(text).unwrap())
|
||||
}
|
||||
|
||||
fn matches(&self, version: &str) -> bool {
|
||||
self.0.matches(&parse_npm_version(version).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn npm_version_req_with_v() {
|
||||
assert!(parse_npm_version_req("v1.0.0").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn npm_version_req_exact() {
|
||||
let tester = NpmVersionReqTester::new("2.1.2");
|
||||
assert!(!tester.matches("2.1.1"));
|
||||
assert!(tester.matches("2.1.2"));
|
||||
assert!(!tester.matches("2.1.3"));
|
||||
|
||||
let tester = NpmVersionReqTester::new("2.1.2 || 2.1.5");
|
||||
assert!(!tester.matches("2.1.1"));
|
||||
assert!(tester.matches("2.1.2"));
|
||||
assert!(!tester.matches("2.1.3"));
|
||||
assert!(!tester.matches("2.1.4"));
|
||||
assert!(tester.matches("2.1.5"));
|
||||
assert!(!tester.matches("2.1.6"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn npm_version_req_minor() {
|
||||
let tester = NpmVersionReqTester::new("1.1");
|
||||
assert!(!tester.matches("1.0.0"));
|
||||
assert!(tester.matches("1.1.0"));
|
||||
assert!(tester.matches("1.1.1"));
|
||||
assert!(!tester.matches("1.2.0"));
|
||||
assert!(!tester.matches("1.2.1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn npm_version_req_ranges() {
|
||||
let tester = NpmVersionReqTester::new(
|
||||
">= 2.1.2 < 3.0.0 || 5.x || ignored-invalid-range || $#$%^#$^#$^%@#$%SDF|||",
|
||||
);
|
||||
assert!(!tester.matches("2.1.1"));
|
||||
assert!(tester.matches("2.1.2"));
|
||||
assert!(tester.matches("2.9.9"));
|
||||
assert!(!tester.matches("3.0.0"));
|
||||
assert!(tester.matches("5.0.0"));
|
||||
assert!(tester.matches("5.1.0"));
|
||||
assert!(!tester.matches("6.1.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn npm_version_req_with_tag() {
|
||||
let req = parse_npm_version_req("latest").unwrap();
|
||||
assert_eq!(req.tag(), Some("latest"));
|
||||
}
|
||||
|
||||
macro_rules! assert_cmp {
|
||||
($a:expr, $b:expr, $expected:expr) => {
|
||||
assert_eq!(
|
||||
$a.cmp(&$b),
|
||||
$expected,
|
||||
"expected {} to be {:?} {}",
|
||||
$a,
|
||||
$expected,
|
||||
$b
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! test_compare {
|
||||
($a:expr, $b:expr, $expected:expr) => {
|
||||
let a = parse_npm_version($a).unwrap();
|
||||
let b = parse_npm_version($b).unwrap();
|
||||
assert_cmp!(a, b, $expected);
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_compare() {
|
||||
test_compare!("1.2.3", "2.3.4", Ordering::Less);
|
||||
test_compare!("1.2.3", "1.2.4", Ordering::Less);
|
||||
test_compare!("1.2.3", "1.2.3", Ordering::Equal);
|
||||
test_compare!("1.2.3", "1.2.2", Ordering::Greater);
|
||||
test_compare!("1.2.3", "1.1.5", Ordering::Greater);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_compare_equal() {
|
||||
// https://github.com/npm/node-semver/blob/bce42589d33e1a99454530a8fd52c7178e2b11c1/test/fixtures/equality.js
|
||||
let fixtures = &[
|
||||
("1.2.3", "v1.2.3"),
|
||||
("1.2.3", "=1.2.3"),
|
||||
("1.2.3", "v 1.2.3"),
|
||||
("1.2.3", "= 1.2.3"),
|
||||
("1.2.3", " v1.2.3"),
|
||||
("1.2.3", " =1.2.3"),
|
||||
("1.2.3", " v 1.2.3"),
|
||||
("1.2.3", " = 1.2.3"),
|
||||
("1.2.3-0", "v1.2.3-0"),
|
||||
("1.2.3-0", "=1.2.3-0"),
|
||||
("1.2.3-0", "v 1.2.3-0"),
|
||||
("1.2.3-0", "= 1.2.3-0"),
|
||||
("1.2.3-0", " v1.2.3-0"),
|
||||
("1.2.3-0", " =1.2.3-0"),
|
||||
("1.2.3-0", " v 1.2.3-0"),
|
||||
("1.2.3-0", " = 1.2.3-0"),
|
||||
("1.2.3-1", "v1.2.3-1"),
|
||||
("1.2.3-1", "=1.2.3-1"),
|
||||
("1.2.3-1", "v 1.2.3-1"),
|
||||
("1.2.3-1", "= 1.2.3-1"),
|
||||
("1.2.3-1", " v1.2.3-1"),
|
||||
("1.2.3-1", " =1.2.3-1"),
|
||||
("1.2.3-1", " v 1.2.3-1"),
|
||||
("1.2.3-1", " = 1.2.3-1"),
|
||||
("1.2.3-beta", "v1.2.3-beta"),
|
||||
("1.2.3-beta", "=1.2.3-beta"),
|
||||
("1.2.3-beta", "v 1.2.3-beta"),
|
||||
("1.2.3-beta", "= 1.2.3-beta"),
|
||||
("1.2.3-beta", " v1.2.3-beta"),
|
||||
("1.2.3-beta", " =1.2.3-beta"),
|
||||
("1.2.3-beta", " v 1.2.3-beta"),
|
||||
("1.2.3-beta", " = 1.2.3-beta"),
|
||||
("1.2.3-beta+build", " = 1.2.3-beta+otherbuild"),
|
||||
("1.2.3+build", " = 1.2.3+otherbuild"),
|
||||
("1.2.3-beta+build", "1.2.3-beta+otherbuild"),
|
||||
("1.2.3+build", "1.2.3+otherbuild"),
|
||||
(" v1.2.3+build", "1.2.3+otherbuild"),
|
||||
];
|
||||
for (a, b) in fixtures {
|
||||
test_compare!(a, b, Ordering::Equal);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_comparisons_test() {
|
||||
// https://github.com/npm/node-semver/blob/bce42589d33e1a99454530a8fd52c7178e2b11c1/test/fixtures/comparisons.js
|
||||
let fixtures = &[
|
||||
("0.0.0", "0.0.0-foo"),
|
||||
("0.0.1", "0.0.0"),
|
||||
("1.0.0", "0.9.9"),
|
||||
("0.10.0", "0.9.0"),
|
||||
("0.99.0", "0.10.0"),
|
||||
("2.0.0", "1.2.3"),
|
||||
("v0.0.0", "0.0.0-foo"),
|
||||
("v0.0.1", "0.0.0"),
|
||||
("v1.0.0", "0.9.9"),
|
||||
("v0.10.0", "0.9.0"),
|
||||
("v0.99.0", "0.10.0"),
|
||||
("v2.0.0", "1.2.3"),
|
||||
("0.0.0", "v0.0.0-foo"),
|
||||
("0.0.1", "v0.0.0"),
|
||||
("1.0.0", "v0.9.9"),
|
||||
("0.10.0", "v0.9.0"),
|
||||
("0.99.0", "v0.10.0"),
|
||||
("2.0.0", "v1.2.3"),
|
||||
("1.2.3", "1.2.3-asdf"),
|
||||
("1.2.3", "1.2.3-4"),
|
||||
("1.2.3", "1.2.3-4-foo"),
|
||||
("1.2.3-5-foo", "1.2.3-5"),
|
||||
("1.2.3-5", "1.2.3-4"),
|
||||
("1.2.3-5-foo", "1.2.3-5-Foo"),
|
||||
("3.0.0", "2.7.2+asdf"),
|
||||
("1.2.3-a.10", "1.2.3-a.5"),
|
||||
("1.2.3-a.b", "1.2.3-a.5"),
|
||||
("1.2.3-a.b", "1.2.3-a"),
|
||||
("1.2.3-a.b.c.10.d.5", "1.2.3-a.b.c.5.d.100"),
|
||||
("1.2.3-r2", "1.2.3-r100"),
|
||||
("1.2.3-r100", "1.2.3-R2"),
|
||||
];
|
||||
for (a, b) in fixtures {
|
||||
let a = parse_npm_version(a).unwrap();
|
||||
let b = parse_npm_version(b).unwrap();
|
||||
assert_cmp!(a, b, Ordering::Greater);
|
||||
assert_cmp!(b, a, Ordering::Less);
|
||||
assert_cmp!(a, a, Ordering::Equal);
|
||||
assert_cmp!(b, b, Ordering::Equal);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_parse() {
|
||||
// https://github.com/npm/node-semver/blob/4907647d169948a53156502867ed679268063a9f/test/fixtures/range-parse.js
|
||||
let fixtures = &[
|
||||
("1.0.0 - 2.0.0", ">=1.0.0 <=2.0.0"),
|
||||
("1 - 2", ">=1.0.0 <3.0.0-0"),
|
||||
("1.0 - 2.0", ">=1.0.0 <2.1.0-0"),
|
||||
("1.0.0", "1.0.0"),
|
||||
(">=*", "*"),
|
||||
("", "*"),
|
||||
("*", "*"),
|
||||
("*", "*"),
|
||||
(">=1.0.0", ">=1.0.0"),
|
||||
(">1.0.0", ">1.0.0"),
|
||||
("<=2.0.0", "<=2.0.0"),
|
||||
("1", ">=1.0.0 <2.0.0-0"),
|
||||
("<=2.0.0", "<=2.0.0"),
|
||||
("<=2.0.0", "<=2.0.0"),
|
||||
("<2.0.0", "<2.0.0"),
|
||||
("<2.0.0", "<2.0.0"),
|
||||
(">= 1.0.0", ">=1.0.0"),
|
||||
(">= 1.0.0", ">=1.0.0"),
|
||||
(">= 1.0.0", ">=1.0.0"),
|
||||
("> 1.0.0", ">1.0.0"),
|
||||
("> 1.0.0", ">1.0.0"),
|
||||
("<= 2.0.0", "<=2.0.0"),
|
||||
("<= 2.0.0", "<=2.0.0"),
|
||||
("<= 2.0.0", "<=2.0.0"),
|
||||
("< 2.0.0", "<2.0.0"),
|
||||
("<\t2.0.0", "<2.0.0"),
|
||||
(">=0.1.97", ">=0.1.97"),
|
||||
(">=0.1.97", ">=0.1.97"),
|
||||
("0.1.20 || 1.2.4", "0.1.20||1.2.4"),
|
||||
(">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"),
|
||||
(">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"),
|
||||
(">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"),
|
||||
("||", "*"),
|
||||
("2.x.x", ">=2.0.0 <3.0.0-0"),
|
||||
("1.2.x", ">=1.2.0 <1.3.0-0"),
|
||||
("1.2.x || 2.x", ">=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0"),
|
||||
("1.2.x || 2.x", ">=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0"),
|
||||
("x", "*"),
|
||||
("2.*.*", ">=2.0.0 <3.0.0-0"),
|
||||
("1.2.*", ">=1.2.0 <1.3.0-0"),
|
||||
("1.2.* || 2.*", ">=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0"),
|
||||
("*", "*"),
|
||||
("2", ">=2.0.0 <3.0.0-0"),
|
||||
("2.3", ">=2.3.0 <2.4.0-0"),
|
||||
("~2.4", ">=2.4.0 <2.5.0-0"),
|
||||
("~2.4", ">=2.4.0 <2.5.0-0"),
|
||||
("~>3.2.1", ">=3.2.1 <3.3.0-0"),
|
||||
("~1", ">=1.0.0 <2.0.0-0"),
|
||||
("~>1", ">=1.0.0 <2.0.0-0"),
|
||||
("~> 1", ">=1.0.0 <2.0.0-0"),
|
||||
("~1.0", ">=1.0.0 <1.1.0-0"),
|
||||
("~ 1.0", ">=1.0.0 <1.1.0-0"),
|
||||
("^0", "<1.0.0-0"),
|
||||
("^ 1", ">=1.0.0 <2.0.0-0"),
|
||||
("^0.1", ">=0.1.0 <0.2.0-0"),
|
||||
("^1.0", ">=1.0.0 <2.0.0-0"),
|
||||
("^1.2", ">=1.2.0 <2.0.0-0"),
|
||||
("^0.0.1", ">=0.0.1 <0.0.2-0"),
|
||||
("^0.0.1-beta", ">=0.0.1-beta <0.0.2-0"),
|
||||
("^0.1.2", ">=0.1.2 <0.2.0-0"),
|
||||
("^1.2.3", ">=1.2.3 <2.0.0-0"),
|
||||
("^1.2.3-beta.4", ">=1.2.3-beta.4 <2.0.0-0"),
|
||||
("<1", "<1.0.0-0"),
|
||||
("< 1", "<1.0.0-0"),
|
||||
(">=1", ">=1.0.0"),
|
||||
(">= 1", ">=1.0.0"),
|
||||
("<1.2", "<1.2.0-0"),
|
||||
("< 1.2", "<1.2.0-0"),
|
||||
("1", ">=1.0.0 <2.0.0-0"),
|
||||
("^ 1.2 ^ 1", ">=1.2.0 <2.0.0-0 >=1.0.0"),
|
||||
("1.2 - 3.4.5", ">=1.2.0 <=3.4.5"),
|
||||
("1.2.3 - 3.4", ">=1.2.3 <3.5.0-0"),
|
||||
("1.2 - 3.4", ">=1.2.0 <3.5.0-0"),
|
||||
(">1", ">=2.0.0"),
|
||||
(">1.2", ">=1.3.0"),
|
||||
(">X", "<0.0.0-0"),
|
||||
("<X", "<0.0.0-0"),
|
||||
("<x <* || >* 2.x", "<0.0.0-0"),
|
||||
(">x 2.x || * || <x", "*"),
|
||||
(">01.02.03", ">1.2.3"),
|
||||
("~1.2.3beta", ">=1.2.3-beta <1.3.0-0"),
|
||||
(">=09090", ">=9090.0.0"),
|
||||
];
|
||||
for (range_text, expected) in fixtures {
|
||||
let range = parse_npm_version_req(range_text).unwrap();
|
||||
let expected_range = parse_npm_version_req(expected).unwrap();
|
||||
assert_eq!(
|
||||
range.inner(),
|
||||
expected_range.inner(),
|
||||
"failed for {} and {}",
|
||||
range_text,
|
||||
expected
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_satisfies() {
|
||||
// https://github.com/npm/node-semver/blob/4907647d169948a53156502867ed679268063a9f/test/fixtures/range-include.js
|
||||
let fixtures = &[
|
||||
("1.0.0 - 2.0.0", "1.2.3"),
|
||||
("^1.2.3+build", "1.2.3"),
|
||||
("^1.2.3+build", "1.3.0"),
|
||||
("1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3"),
|
||||
("1.2.3pre+asdf - 2.4.3-pre+asdf", "1.2.3"),
|
||||
("1.2.3-pre+asdf - 2.4.3pre+asdf", "1.2.3"),
|
||||
("1.2.3pre+asdf - 2.4.3pre+asdf", "1.2.3"),
|
||||
("1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pre.2"),
|
||||
("1.2.3-pre+asdf - 2.4.3-pre+asdf", "2.4.3-alpha"),
|
||||
("1.2.3+asdf - 2.4.3+asdf", "1.2.3"),
|
||||
("1.0.0", "1.0.0"),
|
||||
(">=*", "0.2.4"),
|
||||
("", "1.0.0"),
|
||||
("*", "1.2.3"),
|
||||
("*", "v1.2.3"),
|
||||
(">=1.0.0", "1.0.0"),
|
||||
(">=1.0.0", "1.0.1"),
|
||||
(">=1.0.0", "1.1.0"),
|
||||
(">1.0.0", "1.0.1"),
|
||||
(">1.0.0", "1.1.0"),
|
||||
("<=2.0.0", "2.0.0"),
|
||||
("<=2.0.0", "1.9999.9999"),
|
||||
("<=2.0.0", "0.2.9"),
|
||||
("<2.0.0", "1.9999.9999"),
|
||||
("<2.0.0", "0.2.9"),
|
||||
(">= 1.0.0", "1.0.0"),
|
||||
(">= 1.0.0", "1.0.1"),
|
||||
(">= 1.0.0", "1.1.0"),
|
||||
("> 1.0.0", "1.0.1"),
|
||||
("> 1.0.0", "1.1.0"),
|
||||
("<= 2.0.0", "2.0.0"),
|
||||
("<= 2.0.0", "1.9999.9999"),
|
||||
("<= 2.0.0", "0.2.9"),
|
||||
("< 2.0.0", "1.9999.9999"),
|
||||
("<\t2.0.0", "0.2.9"),
|
||||
(">=0.1.97", "v0.1.97"),
|
||||
(">=0.1.97", "0.1.97"),
|
||||
("0.1.20 || 1.2.4", "1.2.4"),
|
||||
(">=0.2.3 || <0.0.1", "0.0.0"),
|
||||
(">=0.2.3 || <0.0.1", "0.2.3"),
|
||||
(">=0.2.3 || <0.0.1", "0.2.4"),
|
||||
("||", "1.3.4"),
|
||||
("2.x.x", "2.1.3"),
|
||||
("1.2.x", "1.2.3"),
|
||||
("1.2.x || 2.x", "2.1.3"),
|
||||
("1.2.x || 2.x", "1.2.3"),
|
||||
("x", "1.2.3"),
|
||||
("2.*.*", "2.1.3"),
|
||||
("1.2.*", "1.2.3"),
|
||||
("1.2.* || 2.*", "2.1.3"),
|
||||
("1.2.* || 2.*", "1.2.3"),
|
||||
("*", "1.2.3"),
|
||||
("2", "2.1.2"),
|
||||
("2.3", "2.3.1"),
|
||||
("~0.0.1", "0.0.1"),
|
||||
("~0.0.1", "0.0.2"),
|
||||
("~x", "0.0.9"), // >=2.4.0 <2.5.0
|
||||
("~2", "2.0.9"), // >=2.4.0 <2.5.0
|
||||
("~2.4", "2.4.0"), // >=2.4.0 <2.5.0
|
||||
("~2.4", "2.4.5"),
|
||||
("~>3.2.1", "3.2.2"), // >=3.2.1 <3.3.0,
|
||||
("~1", "1.2.3"), // >=1.0.0 <2.0.0
|
||||
("~>1", "1.2.3"),
|
||||
("~> 1", "1.2.3"),
|
||||
("~1.0", "1.0.2"), // >=1.0.0 <1.1.0,
|
||||
("~ 1.0", "1.0.2"),
|
||||
("~ 1.0.3", "1.0.12"),
|
||||
("~ 1.0.3alpha", "1.0.12"),
|
||||
(">=1", "1.0.0"),
|
||||
(">= 1", "1.0.0"),
|
||||
("<1.2", "1.1.1"),
|
||||
("< 1.2", "1.1.1"),
|
||||
("~v0.5.4-pre", "0.5.5"),
|
||||
("~v0.5.4-pre", "0.5.4"),
|
||||
("=0.7.x", "0.7.2"),
|
||||
("<=0.7.x", "0.7.2"),
|
||||
(">=0.7.x", "0.7.2"),
|
||||
("<=0.7.x", "0.6.2"),
|
||||
("~1.2.1 >=1.2.3", "1.2.3"),
|
||||
("~1.2.1 =1.2.3", "1.2.3"),
|
||||
("~1.2.1 1.2.3", "1.2.3"),
|
||||
("~1.2.1 >=1.2.3 1.2.3", "1.2.3"),
|
||||
("~1.2.1 1.2.3 >=1.2.3", "1.2.3"),
|
||||
("~1.2.1 1.2.3", "1.2.3"),
|
||||
(">=1.2.1 1.2.3", "1.2.3"),
|
||||
("1.2.3 >=1.2.1", "1.2.3"),
|
||||
(">=1.2.3 >=1.2.1", "1.2.3"),
|
||||
(">=1.2.1 >=1.2.3", "1.2.3"),
|
||||
(">=1.2", "1.2.8"),
|
||||
("^1.2.3", "1.8.1"),
|
||||
("^0.1.2", "0.1.2"),
|
||||
("^0.1", "0.1.2"),
|
||||
("^0.0.1", "0.0.1"),
|
||||
("^1.2", "1.4.2"),
|
||||
("^1.2 ^1", "1.4.2"),
|
||||
("^1.2.3-alpha", "1.2.3-pre"),
|
||||
("^1.2.0-alpha", "1.2.0-pre"),
|
||||
("^0.0.1-alpha", "0.0.1-beta"),
|
||||
("^0.0.1-alpha", "0.0.1"),
|
||||
("^0.1.1-alpha", "0.1.1-beta"),
|
||||
("^x", "1.2.3"),
|
||||
("x - 1.0.0", "0.9.7"),
|
||||
("x - 1.x", "0.9.7"),
|
||||
("1.0.0 - x", "1.9.7"),
|
||||
("1.x - x", "1.9.7"),
|
||||
("<=7.x", "7.9.9"),
|
||||
// additional tests
|
||||
("1.0.0-alpha.13", "1.0.0-alpha.13"),
|
||||
];
|
||||
for (req_text, version_text) in fixtures {
|
||||
let req = parse_npm_version_req(req_text).unwrap();
|
||||
let version = parse_npm_version(version_text).unwrap();
|
||||
assert!(
|
||||
req.matches(&version),
|
||||
"Checking {req_text} satisfies {version_text}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_not_satisfies() {
|
||||
let fixtures = &[
|
||||
("1.0.0 - 2.0.0", "2.2.3"),
|
||||
("1.2.3+asdf - 2.4.3+asdf", "1.2.3-pre.2"),
|
||||
("1.2.3+asdf - 2.4.3+asdf", "2.4.3-alpha"),
|
||||
("^1.2.3+build", "2.0.0"),
|
||||
("^1.2.3+build", "1.2.0"),
|
||||
("^1.2.3", "1.2.3-pre"),
|
||||
("^1.2", "1.2.0-pre"),
|
||||
(">1.2", "1.3.0-beta"),
|
||||
("<=1.2.3", "1.2.3-beta"),
|
||||
("^1.2.3", "1.2.3-beta"),
|
||||
("=0.7.x", "0.7.0-asdf"),
|
||||
(">=0.7.x", "0.7.0-asdf"),
|
||||
("<=0.7.x", "0.7.0-asdf"),
|
||||
("1", "1.0.0beta"),
|
||||
("<1", "1.0.0beta"),
|
||||
("< 1", "1.0.0beta"),
|
||||
("1.0.0", "1.0.1"),
|
||||
(">=1.0.0", "0.0.0"),
|
||||
(">=1.0.0", "0.0.1"),
|
||||
(">=1.0.0", "0.1.0"),
|
||||
(">1.0.0", "0.0.1"),
|
||||
(">1.0.0", "0.1.0"),
|
||||
("<=2.0.0", "3.0.0"),
|
||||
("<=2.0.0", "2.9999.9999"),
|
||||
("<=2.0.0", "2.2.9"),
|
||||
("<2.0.0", "2.9999.9999"),
|
||||
("<2.0.0", "2.2.9"),
|
||||
(">=0.1.97", "v0.1.93"),
|
||||
(">=0.1.97", "0.1.93"),
|
||||
("0.1.20 || 1.2.4", "1.2.3"),
|
||||
(">=0.2.3 || <0.0.1", "0.0.3"),
|
||||
(">=0.2.3 || <0.0.1", "0.2.2"),
|
||||
("2.x.x", "1.1.3"),
|
||||
("2.x.x", "3.1.3"),
|
||||
("1.2.x", "1.3.3"),
|
||||
("1.2.x || 2.x", "3.1.3"),
|
||||
("1.2.x || 2.x", "1.1.3"),
|
||||
("2.*.*", "1.1.3"),
|
||||
("2.*.*", "3.1.3"),
|
||||
("1.2.*", "1.3.3"),
|
||||
("1.2.* || 2.*", "3.1.3"),
|
||||
("1.2.* || 2.*", "1.1.3"),
|
||||
("2", "1.1.2"),
|
||||
("2.3", "2.4.1"),
|
||||
("~0.0.1", "0.1.0-alpha"),
|
||||
("~0.0.1", "0.1.0"),
|
||||
("~2.4", "2.5.0"), // >=2.4.0 <2.5.0
|
||||
("~2.4", "2.3.9"),
|
||||
("~>3.2.1", "3.3.2"), // >=3.2.1 <3.3.0
|
||||
("~>3.2.1", "3.2.0"), // >=3.2.1 <3.3.0
|
||||
("~1", "0.2.3"), // >=1.0.0 <2.0.0
|
||||
("~>1", "2.2.3"),
|
||||
("~1.0", "1.1.0"), // >=1.0.0 <1.1.0
|
||||
("<1", "1.0.0"),
|
||||
(">=1.2", "1.1.1"),
|
||||
("1", "2.0.0beta"),
|
||||
("~v0.5.4-beta", "0.5.4-alpha"),
|
||||
("=0.7.x", "0.8.2"),
|
||||
(">=0.7.x", "0.6.2"),
|
||||
("<0.7.x", "0.7.2"),
|
||||
("<1.2.3", "1.2.3-beta"),
|
||||
("=1.2.3", "1.2.3-beta"),
|
||||
(">1.2", "1.2.8"),
|
||||
("^0.0.1", "0.0.2-alpha"),
|
||||
("^0.0.1", "0.0.2"),
|
||||
("^1.2.3", "2.0.0-alpha"),
|
||||
("^1.2.3", "1.2.2"),
|
||||
("^1.2", "1.1.9"),
|
||||
("*", "v1.2.3-foo"),
|
||||
("^1.0.0", "2.0.0-rc1"),
|
||||
("1 - 2", "2.0.0-pre"),
|
||||
("1 - 2", "1.0.0-pre"),
|
||||
("1.0 - 2", "1.0.0-pre"),
|
||||
("1.1.x", "1.0.0-a"),
|
||||
("1.1.x", "1.1.0-a"),
|
||||
("1.1.x", "1.2.0-a"),
|
||||
("1.x", "1.0.0-a"),
|
||||
("1.x", "1.1.0-a"),
|
||||
("1.x", "1.2.0-a"),
|
||||
(">=1.0.0 <1.1.0", "1.1.0"),
|
||||
(">=1.0.0 <1.1.0", "1.1.0-pre"),
|
||||
(">=1.0.0 <1.1.0-pre", "1.1.0-pre"),
|
||||
];
|
||||
|
||||
for (req_text, version_text) in fixtures {
|
||||
let req = parse_npm_version_req(req_text).unwrap();
|
||||
let version = parse_npm_version(version_text).unwrap();
|
||||
assert!(
|
||||
!req.matches(&version),
|
||||
"Checking {req_text} not satisfies {version_text}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_primitive_kind_beside_caret_or_tilde_with_whitespace() {
|
||||
// node semver should have enforced strictness, but it didn't
|
||||
// and so we end up with a system that acts this way
|
||||
let fixtures = &[
|
||||
(">= ^1.2.3", "1.2.3", true),
|
||||
(">= ^1.2.3", "1.2.4", true),
|
||||
(">= ^1.2.3", "1.9.3", true),
|
||||
(">= ^1.2.3", "2.0.0", false),
|
||||
(">= ^1.2.3", "1.2.2", false),
|
||||
// this is considered the same as the above by node semver
|
||||
("> ^1.2.3", "1.2.3", true),
|
||||
("> ^1.2.3", "1.2.4", true),
|
||||
("> ^1.2.3", "1.9.3", true),
|
||||
("> ^1.2.3", "2.0.0", false),
|
||||
("> ^1.2.3", "1.2.2", false),
|
||||
// this is also considered the same
|
||||
("< ^1.2.3", "1.2.3", true),
|
||||
("< ^1.2.3", "1.2.4", true),
|
||||
("< ^1.2.3", "1.9.3", true),
|
||||
("< ^1.2.3", "2.0.0", false),
|
||||
("< ^1.2.3", "1.2.2", false),
|
||||
// same with this
|
||||
("<= ^1.2.3", "1.2.3", true),
|
||||
("<= ^1.2.3", "1.2.4", true),
|
||||
("<= ^1.2.3", "1.9.3", true),
|
||||
("<= ^1.2.3", "2.0.0", false),
|
||||
("<= ^1.2.3", "1.2.2", false),
|
||||
// now try a ~, which should work the same as above, but for ~
|
||||
("<= ~1.2.3", "1.2.3", true),
|
||||
("<= ~1.2.3", "1.2.4", true),
|
||||
("<= ~1.2.3", "1.9.3", false),
|
||||
("<= ~1.2.3", "2.0.0", false),
|
||||
("<= ~1.2.3", "1.2.2", false),
|
||||
];
|
||||
|
||||
for (req_text, version_text, satisfies) in fixtures {
|
||||
let req = parse_npm_version_req(req_text).unwrap();
|
||||
let version = parse_npm_version(version_text).unwrap();
|
||||
assert_eq!(
|
||||
req.matches(&version),
|
||||
*satisfies,
|
||||
"Checking {} {} satisfies {}",
|
||||
req_text,
|
||||
if *satisfies { "true" } else { "false" },
|
||||
version_text
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_primitive_kind_beside_caret_or_tilde_no_whitespace() {
|
||||
let fixtures = &[
|
||||
">=^1.2.3", ">^1.2.3", "<^1.2.3", "<=^1.2.3", ">=~1.2.3", ">~1.2.3",
|
||||
"<~1.2.3", "<=~1.2.3",
|
||||
];
|
||||
|
||||
for req_text in fixtures {
|
||||
// when it has no space, this is considered invalid
|
||||
// by node semver so we should error
|
||||
assert!(parse_npm_version_req(req_text).is_err());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,509 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::Version;
|
||||
|
||||
/// Collection of ranges.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct VersionRangeSet(pub Vec<VersionRange>);
|
||||
|
||||
impl VersionRangeSet {
|
||||
pub fn satisfies(&self, version: &Version) -> bool {
|
||||
self.0.iter().any(|r| r.satisfies(version))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum RangeBound {
|
||||
Version(VersionBound),
|
||||
Unbounded, // matches everything
|
||||
}
|
||||
|
||||
impl RangeBound {
|
||||
pub fn inclusive(version: Version) -> Self {
|
||||
Self::version(VersionBoundKind::Inclusive, version)
|
||||
}
|
||||
|
||||
pub fn exclusive(version: Version) -> Self {
|
||||
Self::version(VersionBoundKind::Exclusive, version)
|
||||
}
|
||||
|
||||
pub fn version(kind: VersionBoundKind, version: Version) -> Self {
|
||||
Self::Version(VersionBound::new(kind, version))
|
||||
}
|
||||
|
||||
pub fn clamp_start(&self, other: &RangeBound) -> RangeBound {
|
||||
match &self {
|
||||
RangeBound::Unbounded => other.clone(),
|
||||
RangeBound::Version(self_bound) => RangeBound::Version(match &other {
|
||||
RangeBound::Unbounded => self_bound.clone(),
|
||||
RangeBound::Version(other_bound) => {
|
||||
match self_bound.version.cmp(&other_bound.version) {
|
||||
Ordering::Greater => self_bound.clone(),
|
||||
Ordering::Less => other_bound.clone(),
|
||||
Ordering::Equal => match self_bound.kind {
|
||||
VersionBoundKind::Exclusive => self_bound.clone(),
|
||||
VersionBoundKind::Inclusive => other_bound.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp_end(&self, other: &RangeBound) -> RangeBound {
|
||||
match &self {
|
||||
RangeBound::Unbounded => other.clone(),
|
||||
RangeBound::Version(self_bound) => {
|
||||
RangeBound::Version(match other {
|
||||
RangeBound::Unbounded => self_bound.clone(),
|
||||
RangeBound::Version(other_bound) => {
|
||||
match self_bound.version.cmp(&other_bound.version) {
|
||||
// difference with above is the next two lines are switched
|
||||
Ordering::Greater => other_bound.clone(),
|
||||
Ordering::Less => self_bound.clone(),
|
||||
Ordering::Equal => match self_bound.kind {
|
||||
VersionBoundKind::Exclusive => self_bound.clone(),
|
||||
VersionBoundKind::Inclusive => other_bound.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_pre_with_exact_major_minor_patch(
|
||||
&self,
|
||||
version: &Version,
|
||||
) -> bool {
|
||||
if let RangeBound::Version(self_version) = &self {
|
||||
if !self_version.version.pre.is_empty()
|
||||
&& self_version.version.major == version.major
|
||||
&& self_version.version.minor == version.minor
|
||||
&& self_version.version.patch == version.patch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum VersionBoundKind {
|
||||
Inclusive,
|
||||
Exclusive,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct VersionBound {
|
||||
pub kind: VersionBoundKind,
|
||||
pub version: Version,
|
||||
}
|
||||
|
||||
impl VersionBound {
|
||||
pub fn new(kind: VersionBoundKind, version: Version) -> Self {
|
||||
Self { kind, version }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct VersionRange {
|
||||
pub start: RangeBound,
|
||||
pub end: RangeBound,
|
||||
}
|
||||
|
||||
impl VersionRange {
|
||||
pub fn all() -> VersionRange {
|
||||
VersionRange {
|
||||
start: RangeBound::Version(VersionBound {
|
||||
kind: VersionBoundKind::Inclusive,
|
||||
version: Version::default(),
|
||||
}),
|
||||
end: RangeBound::Unbounded,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn none() -> VersionRange {
|
||||
VersionRange {
|
||||
start: RangeBound::Version(VersionBound {
|
||||
kind: VersionBoundKind::Inclusive,
|
||||
version: Version::default(),
|
||||
}),
|
||||
end: RangeBound::Version(VersionBound {
|
||||
kind: VersionBoundKind::Exclusive,
|
||||
version: Version::default(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// If this range won't match anything.
|
||||
pub fn is_none(&self) -> bool {
|
||||
if let RangeBound::Version(end) = &self.end {
|
||||
end.kind == VersionBoundKind::Exclusive
|
||||
&& end.version.major == 0
|
||||
&& end.version.minor == 0
|
||||
&& end.version.patch == 0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn satisfies(&self, version: &Version) -> bool {
|
||||
let satisfies = self.min_satisfies(version) && self.max_satisfies(version);
|
||||
if satisfies && !version.pre.is_empty() {
|
||||
// check either side of the range has a pre and same version
|
||||
self.start.has_pre_with_exact_major_minor_patch(version)
|
||||
|| self.end.has_pre_with_exact_major_minor_patch(version)
|
||||
} else {
|
||||
satisfies
|
||||
}
|
||||
}
|
||||
|
||||
fn min_satisfies(&self, version: &Version) -> bool {
|
||||
match &self.start {
|
||||
RangeBound::Unbounded => true,
|
||||
RangeBound::Version(bound) => match version.cmp(&bound.version) {
|
||||
Ordering::Less => false,
|
||||
Ordering::Equal => bound.kind == VersionBoundKind::Inclusive,
|
||||
Ordering::Greater => true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn max_satisfies(&self, version: &Version) -> bool {
|
||||
match &self.end {
|
||||
RangeBound::Unbounded => true,
|
||||
RangeBound::Version(bound) => match version.cmp(&bound.version) {
|
||||
Ordering::Less => true,
|
||||
Ordering::Equal => bound.kind == VersionBoundKind::Inclusive,
|
||||
Ordering::Greater => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp(&self, range: &VersionRange) -> VersionRange {
|
||||
let start = self.start.clamp_start(&range.start);
|
||||
let end = self.end.clamp_end(&range.end);
|
||||
// clamp the start range to the end when greater
|
||||
let start = start.clamp_end(&end);
|
||||
VersionRange { start, end }
|
||||
}
|
||||
}
|
||||
|
||||
/// A range that could be a wildcard or number value.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum XRange {
|
||||
Wildcard,
|
||||
Val(u64),
|
||||
}
|
||||
|
||||
/// A partial version.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Partial {
|
||||
pub major: XRange,
|
||||
pub minor: XRange,
|
||||
pub patch: XRange,
|
||||
pub pre: Vec<String>,
|
||||
pub build: Vec<String>,
|
||||
}
|
||||
|
||||
impl Partial {
|
||||
pub fn as_tilde_version_range(&self) -> VersionRange {
|
||||
// tilde ranges allow patch-level changes
|
||||
let end = match self.major {
|
||||
XRange::Wildcard => return VersionRange::all(),
|
||||
XRange::Val(major) => match self.minor {
|
||||
XRange::Wildcard => Version {
|
||||
major: major + 1,
|
||||
minor: 0,
|
||||
patch: 0,
|
||||
pre: Vec::new(),
|
||||
build: Vec::new(),
|
||||
},
|
||||
XRange::Val(minor) => Version {
|
||||
major,
|
||||
minor: minor + 1,
|
||||
patch: 0,
|
||||
pre: Vec::new(),
|
||||
build: Vec::new(),
|
||||
},
|
||||
},
|
||||
};
|
||||
VersionRange {
|
||||
start: self.as_lower_bound(),
|
||||
end: RangeBound::exclusive(end),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_caret_version_range(&self) -> VersionRange {
|
||||
// partial ranges allow patch and minor updates, except when
|
||||
// leading parts are < 1 in which case it will only bump the
|
||||
// first non-zero or patch part
|
||||
let end = match self.major {
|
||||
XRange::Wildcard => return VersionRange::all(),
|
||||
XRange::Val(major) => {
|
||||
let next_major = Version {
|
||||
major: major + 1,
|
||||
..Default::default()
|
||||
};
|
||||
if major > 0 {
|
||||
next_major
|
||||
} else {
|
||||
match self.minor {
|
||||
XRange::Wildcard => next_major,
|
||||
XRange::Val(minor) => {
|
||||
let next_minor = Version {
|
||||
minor: minor + 1,
|
||||
..Default::default()
|
||||
};
|
||||
if minor > 0 {
|
||||
next_minor
|
||||
} else {
|
||||
match self.patch {
|
||||
XRange::Wildcard => next_minor,
|
||||
XRange::Val(patch) => Version {
|
||||
patch: patch + 1,
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
VersionRange {
|
||||
start: self.as_lower_bound(),
|
||||
end: RangeBound::Version(VersionBound {
|
||||
kind: VersionBoundKind::Exclusive,
|
||||
version: end,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_lower_bound(&self) -> RangeBound {
|
||||
RangeBound::inclusive(Version {
|
||||
major: match self.major {
|
||||
XRange::Val(val) => val,
|
||||
XRange::Wildcard => 0,
|
||||
},
|
||||
minor: match self.minor {
|
||||
XRange::Val(val) => val,
|
||||
XRange::Wildcard => 0,
|
||||
},
|
||||
patch: match self.patch {
|
||||
XRange::Val(val) => val,
|
||||
XRange::Wildcard => 0,
|
||||
},
|
||||
pre: self.pre.clone(),
|
||||
build: self.build.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn as_upper_bound(&self) -> RangeBound {
|
||||
let mut end = Version::default();
|
||||
let mut kind = VersionBoundKind::Inclusive;
|
||||
match self.patch {
|
||||
XRange::Wildcard => {
|
||||
end.minor += 1;
|
||||
kind = VersionBoundKind::Exclusive;
|
||||
}
|
||||
XRange::Val(val) => {
|
||||
end.patch = val;
|
||||
}
|
||||
}
|
||||
match self.minor {
|
||||
XRange::Wildcard => {
|
||||
end.minor = 0;
|
||||
end.major += 1;
|
||||
kind = VersionBoundKind::Exclusive;
|
||||
}
|
||||
XRange::Val(val) => {
|
||||
end.minor += val;
|
||||
}
|
||||
}
|
||||
match self.major {
|
||||
XRange::Wildcard => {
|
||||
return RangeBound::Unbounded;
|
||||
}
|
||||
XRange::Val(val) => {
|
||||
end.major += val;
|
||||
}
|
||||
}
|
||||
|
||||
if kind == VersionBoundKind::Inclusive {
|
||||
end.pre = self.pre.clone();
|
||||
}
|
||||
|
||||
RangeBound::version(kind, end)
|
||||
}
|
||||
|
||||
pub fn as_equal_range(&self) -> VersionRange {
|
||||
let major = match self.major {
|
||||
XRange::Wildcard => {
|
||||
return self.as_greater_range(VersionBoundKind::Inclusive)
|
||||
}
|
||||
XRange::Val(val) => val,
|
||||
};
|
||||
let minor = match self.minor {
|
||||
XRange::Wildcard => {
|
||||
return self.as_greater_range(VersionBoundKind::Inclusive)
|
||||
}
|
||||
XRange::Val(val) => val,
|
||||
};
|
||||
let patch = match self.patch {
|
||||
XRange::Wildcard => {
|
||||
return self.as_greater_range(VersionBoundKind::Inclusive)
|
||||
}
|
||||
XRange::Val(val) => val,
|
||||
};
|
||||
let version = Version {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
pre: self.pre.clone(),
|
||||
build: self.build.clone(),
|
||||
};
|
||||
VersionRange {
|
||||
start: RangeBound::inclusive(version.clone()),
|
||||
end: RangeBound::inclusive(version),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_greater_than(
|
||||
&self,
|
||||
mut start_kind: VersionBoundKind,
|
||||
) -> VersionRange {
|
||||
let major = match self.major {
|
||||
XRange::Wildcard => match start_kind {
|
||||
VersionBoundKind::Inclusive => return VersionRange::all(),
|
||||
VersionBoundKind::Exclusive => return VersionRange::none(),
|
||||
},
|
||||
XRange::Val(major) => major,
|
||||
};
|
||||
let mut start = Version::default();
|
||||
|
||||
if start_kind == VersionBoundKind::Inclusive {
|
||||
start.pre = self.pre.clone();
|
||||
}
|
||||
|
||||
start.major = major;
|
||||
match self.minor {
|
||||
XRange::Wildcard => {
|
||||
if start_kind == VersionBoundKind::Exclusive {
|
||||
start_kind = VersionBoundKind::Inclusive;
|
||||
start.major += 1;
|
||||
}
|
||||
}
|
||||
XRange::Val(minor) => {
|
||||
start.minor = minor;
|
||||
}
|
||||
}
|
||||
match self.patch {
|
||||
XRange::Wildcard => {
|
||||
if start_kind == VersionBoundKind::Exclusive {
|
||||
start_kind = VersionBoundKind::Inclusive;
|
||||
start.minor += 1;
|
||||
}
|
||||
}
|
||||
XRange::Val(patch) => {
|
||||
start.patch = patch;
|
||||
}
|
||||
}
|
||||
|
||||
VersionRange {
|
||||
start: RangeBound::version(start_kind, start),
|
||||
end: RangeBound::Unbounded,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_less_than(&self, mut end_kind: VersionBoundKind) -> VersionRange {
|
||||
let major = match self.major {
|
||||
XRange::Wildcard => match end_kind {
|
||||
VersionBoundKind::Inclusive => return VersionRange::all(),
|
||||
VersionBoundKind::Exclusive => return VersionRange::none(),
|
||||
},
|
||||
XRange::Val(major) => major,
|
||||
};
|
||||
let mut end = Version {
|
||||
major,
|
||||
..Default::default()
|
||||
};
|
||||
match self.minor {
|
||||
XRange::Wildcard => {
|
||||
if end_kind == VersionBoundKind::Inclusive {
|
||||
end.major += 1;
|
||||
}
|
||||
end_kind = VersionBoundKind::Exclusive;
|
||||
}
|
||||
XRange::Val(minor) => {
|
||||
end.minor = minor;
|
||||
}
|
||||
}
|
||||
match self.patch {
|
||||
XRange::Wildcard => {
|
||||
if end_kind == VersionBoundKind::Inclusive {
|
||||
end.minor += 1;
|
||||
}
|
||||
end_kind = VersionBoundKind::Exclusive;
|
||||
}
|
||||
XRange::Val(patch) => {
|
||||
end.patch = patch;
|
||||
}
|
||||
}
|
||||
if end_kind == VersionBoundKind::Inclusive {
|
||||
end.pre = self.pre.clone();
|
||||
}
|
||||
VersionRange {
|
||||
start: RangeBound::Unbounded,
|
||||
end: RangeBound::version(end_kind, end),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_greater_range(&self, start_kind: VersionBoundKind) -> VersionRange {
|
||||
let major = match self.major {
|
||||
XRange::Wildcard => return VersionRange::all(),
|
||||
XRange::Val(major) => major,
|
||||
};
|
||||
let mut start = Version::default();
|
||||
let mut end = Version::default();
|
||||
start.major = major;
|
||||
end.major = major;
|
||||
match self.patch {
|
||||
XRange::Wildcard => {
|
||||
if self.minor != XRange::Wildcard {
|
||||
end.minor += 1;
|
||||
}
|
||||
}
|
||||
XRange::Val(patch) => {
|
||||
start.patch = patch;
|
||||
end.patch = patch;
|
||||
}
|
||||
}
|
||||
match self.minor {
|
||||
XRange::Wildcard => {
|
||||
end.major += 1;
|
||||
}
|
||||
XRange::Val(minor) => {
|
||||
start.minor = minor;
|
||||
end.minor += minor;
|
||||
}
|
||||
}
|
||||
let end_kind = if start_kind == VersionBoundKind::Inclusive && start == end
|
||||
{
|
||||
VersionBoundKind::Inclusive
|
||||
} else {
|
||||
VersionBoundKind::Exclusive
|
||||
};
|
||||
VersionRange {
|
||||
start: RangeBound::version(start_kind, start),
|
||||
end: RangeBound::version(end_kind, end),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,265 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use deno_core::anyhow::Context;
|
||||
use deno_core::error::AnyError;
|
||||
use monch::*;
|
||||
|
||||
use super::range::Partial;
|
||||
use super::range::VersionRange;
|
||||
use super::range::VersionRangeSet;
|
||||
use super::range::XRange;
|
||||
use super::RangeSetOrTag;
|
||||
use super::VersionReq;
|
||||
|
||||
use super::is_valid_tag;
|
||||
|
||||
pub fn parse_version_req_from_specifier(
|
||||
text: &str,
|
||||
) -> Result<VersionReq, AnyError> {
|
||||
with_failure_handling(|input| {
|
||||
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,
|
||||
VersionReq::from_raw_text_and_inner(
|
||||
input.to_string(),
|
||||
match range_result {
|
||||
Ok(range) => RangeSetOrTag::RangeSet(VersionRangeSet(vec![range])),
|
||||
Err(err) => {
|
||||
if !is_valid_tag(input) {
|
||||
return Err(err);
|
||||
} else {
|
||||
RangeSetOrTag::Tag(input.to_string())
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
))
|
||||
})(input)
|
||||
})(text)
|
||||
.with_context(|| {
|
||||
format!("Invalid npm specifier version requirement '{text}'.")
|
||||
})
|
||||
}
|
||||
|
||||
// Note: Although the code below looks very similar to what's used for
|
||||
// parsing npm version requirements, the code here is more strict
|
||||
// in order to not allow for people to get ridiculous when using
|
||||
// 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
|
||||
fn version_range(input: &str) -> ParseResult<VersionRange> {
|
||||
or3(
|
||||
map(preceded(ch('~'), partial), |partial| {
|
||||
partial.as_tilde_version_range()
|
||||
}),
|
||||
map(preceded(ch('^'), partial), |partial| {
|
||||
partial.as_caret_version_range()
|
||||
}),
|
||||
map(partial, |partial| partial.as_equal_range()),
|
||||
)(input)
|
||||
}
|
||||
|
||||
// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )?
|
||||
fn partial(input: &str) -> ParseResult<Partial> {
|
||||
let (input, major) = xr()(input)?;
|
||||
let (input, maybe_minor) = maybe(preceded(ch('.'), xr()))(input)?;
|
||||
let (input, maybe_patch) = if maybe_minor.is_some() {
|
||||
maybe(preceded(ch('.'), xr()))(input)?
|
||||
} else {
|
||||
(input, None)
|
||||
};
|
||||
let (input, qual) = if maybe_patch.is_some() {
|
||||
maybe(qualifier)(input)?
|
||||
} else {
|
||||
(input, None)
|
||||
};
|
||||
let qual = qual.unwrap_or_default();
|
||||
Ok((
|
||||
input,
|
||||
Partial {
|
||||
major,
|
||||
minor: maybe_minor.unwrap_or(XRange::Wildcard),
|
||||
patch: maybe_patch.unwrap_or(XRange::Wildcard),
|
||||
pre: qual.pre,
|
||||
build: qual.build,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
// xr ::= 'x' | 'X' | '*' | nr
|
||||
fn xr<'a>() -> impl Fn(&'a str) -> ParseResult<'a, XRange> {
|
||||
or(
|
||||
map(or3(tag("x"), tag("X"), tag("*")), |_| XRange::Wildcard),
|
||||
map(nr, XRange::Val),
|
||||
)
|
||||
}
|
||||
|
||||
// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) *
|
||||
fn nr(input: &str) -> ParseResult<u64> {
|
||||
or(map(tag("0"), |_| 0), move |input| {
|
||||
let (input, result) = if_not_empty(substring(pair(
|
||||
if_true(next_char, |c| c.is_ascii_digit() && *c != '0'),
|
||||
skip_while(|c| c.is_ascii_digit()),
|
||||
)))(input)?;
|
||||
let val = match result.parse::<u64>() {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
return ParseError::fail(
|
||||
input,
|
||||
format!("Error parsing '{result}' to u64.\n\n{err:#}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
Ok((input, val))
|
||||
})(input)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct Qualifier {
|
||||
pre: Vec<String>,
|
||||
build: Vec<String>,
|
||||
}
|
||||
|
||||
// qualifier ::= ( '-' pre )? ( '+' build )?
|
||||
fn qualifier(input: &str) -> ParseResult<Qualifier> {
|
||||
let (input, pre_parts) = maybe(pre)(input)?;
|
||||
let (input, build_parts) = maybe(build)(input)?;
|
||||
Ok((
|
||||
input,
|
||||
Qualifier {
|
||||
pre: pre_parts.unwrap_or_default(),
|
||||
build: build_parts.unwrap_or_default(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
// pre ::= parts
|
||||
fn pre(input: &str) -> ParseResult<Vec<String>> {
|
||||
preceded(ch('-'), parts)(input)
|
||||
}
|
||||
|
||||
// build ::= parts
|
||||
fn build(input: &str) -> ParseResult<Vec<String>> {
|
||||
preceded(ch('+'), parts)(input)
|
||||
}
|
||||
|
||||
// parts ::= part ( '.' part ) *
|
||||
fn parts(input: &str) -> ParseResult<Vec<String>> {
|
||||
if_not_empty(map(separated_list(part, ch('.')), |text| {
|
||||
text.into_iter().map(ToOwned::to_owned).collect()
|
||||
}))(input)
|
||||
}
|
||||
|
||||
// part ::= nr | [-0-9A-Za-z]+
|
||||
fn part(input: &str) -> ParseResult<&str> {
|
||||
// nr is in the other set, so don't bother checking for it
|
||||
if_true(
|
||||
take_while(|c| c.is_ascii_alphanumeric() || c == '-'),
|
||||
|result| !result.is_empty(),
|
||||
)(input)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::Version;
|
||||
use super::*;
|
||||
|
||||
struct VersionReqTester(VersionReq);
|
||||
|
||||
impl VersionReqTester {
|
||||
fn new(text: &str) -> Self {
|
||||
Self(parse_version_req_from_specifier(text).unwrap())
|
||||
}
|
||||
|
||||
fn matches(&self, version: &str) -> bool {
|
||||
self.0.matches(&Version::parse_from_npm(version).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_req_exact() {
|
||||
let tester = VersionReqTester::new("1.0.1");
|
||||
assert!(!tester.matches("1.0.0"));
|
||||
assert!(tester.matches("1.0.1"));
|
||||
assert!(!tester.matches("1.0.2"));
|
||||
assert!(!tester.matches("1.1.1"));
|
||||
|
||||
// pre-release
|
||||
let tester = VersionReqTester::new("1.0.0-alpha.13");
|
||||
assert!(tester.matches("1.0.0-alpha.13"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_req_minor() {
|
||||
let tester = VersionReqTester::new("1.1");
|
||||
assert!(!tester.matches("1.0.0"));
|
||||
assert!(tester.matches("1.1.0"));
|
||||
assert!(tester.matches("1.1.1"));
|
||||
assert!(!tester.matches("1.2.0"));
|
||||
assert!(!tester.matches("1.2.1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_req_caret() {
|
||||
let tester = VersionReqTester::new("^1.1.1");
|
||||
assert!(!tester.matches("1.1.0"));
|
||||
assert!(tester.matches("1.1.1"));
|
||||
assert!(tester.matches("1.1.2"));
|
||||
assert!(tester.matches("1.2.0"));
|
||||
assert!(!tester.matches("2.0.0"));
|
||||
|
||||
let tester = VersionReqTester::new("^0.1.1");
|
||||
assert!(!tester.matches("0.0.0"));
|
||||
assert!(!tester.matches("0.1.0"));
|
||||
assert!(tester.matches("0.1.1"));
|
||||
assert!(tester.matches("0.1.2"));
|
||||
assert!(!tester.matches("0.2.0"));
|
||||
assert!(!tester.matches("1.0.0"));
|
||||
|
||||
let tester = VersionReqTester::new("^0.0.1");
|
||||
assert!(!tester.matches("0.0.0"));
|
||||
assert!(tester.matches("0.0.1"));
|
||||
assert!(!tester.matches("0.0.2"));
|
||||
assert!(!tester.matches("0.1.0"));
|
||||
assert!(!tester.matches("1.0.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_req_tilde() {
|
||||
let tester = VersionReqTester::new("~1.1.1");
|
||||
assert!(!tester.matches("1.1.0"));
|
||||
assert!(tester.matches("1.1.1"));
|
||||
assert!(tester.matches("1.1.2"));
|
||||
assert!(!tester.matches("1.2.0"));
|
||||
assert!(!tester.matches("2.0.0"));
|
||||
|
||||
let tester = VersionReqTester::new("~0.1.1");
|
||||
assert!(!tester.matches("0.0.0"));
|
||||
assert!(!tester.matches("0.1.0"));
|
||||
assert!(tester.matches("0.1.1"));
|
||||
assert!(tester.matches("0.1.2"));
|
||||
assert!(!tester.matches("0.2.0"));
|
||||
assert!(!tester.matches("1.0.0"));
|
||||
|
||||
let tester = VersionReqTester::new("~0.0.1");
|
||||
assert!(!tester.matches("0.0.0"));
|
||||
assert!(tester.matches("0.0.1"));
|
||||
assert!(tester.matches("0.0.2")); // for some reason this matches, but not with ^
|
||||
assert!(!tester.matches("0.1.0"));
|
||||
assert!(!tester.matches("1.0.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_tag() {
|
||||
let latest_tag = VersionReq::parse_from_specifier("latest").unwrap();
|
||||
assert_eq!(latest_tag.tag().unwrap(), "latest");
|
||||
}
|
||||
}
|
|
@ -10,6 +10,9 @@ use deno_core::error::AnyError;
|
|||
use deno_core::resolve_url_or_path;
|
||||
use deno_core::serde_json;
|
||||
use deno_core::serde_json::json;
|
||||
use deno_graph::npm::NpmPackageId;
|
||||
use deno_graph::npm::NpmPackageReference;
|
||||
use deno_graph::npm::NpmPackageReq;
|
||||
use deno_graph::Dependency;
|
||||
use deno_graph::Module;
|
||||
use deno_graph::ModuleGraph;
|
||||
|
@ -20,9 +23,6 @@ use deno_runtime::colors;
|
|||
use crate::args::Flags;
|
||||
use crate::args::InfoFlags;
|
||||
use crate::display;
|
||||
use crate::npm::NpmPackageId;
|
||||
use crate::npm::NpmPackageReference;
|
||||
use crate::npm::NpmPackageReq;
|
||||
use crate::npm::NpmPackageResolver;
|
||||
use crate::npm::NpmResolutionPackage;
|
||||
use crate::npm::NpmResolutionSnapshot;
|
||||
|
|
|
@ -7,14 +7,15 @@ use crate::args::Flags;
|
|||
use crate::args::InstallFlags;
|
||||
use crate::args::TypeCheckMode;
|
||||
use crate::http_util::HttpClient;
|
||||
use crate::npm::NpmPackageReference;
|
||||
use crate::proc_state::ProcState;
|
||||
use crate::util::fs::canonicalize_path_maybe_not_exists;
|
||||
|
||||
use deno_core::anyhow::Context;
|
||||
use deno_core::error::generic_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::resolve_url_or_path;
|
||||
use deno_core::url::Url;
|
||||
use deno_graph::npm::NpmPackageReference;
|
||||
use log::Level;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
use crate::colors;
|
||||
use crate::lsp::ReplLanguageServer;
|
||||
use crate::npm::NpmPackageReference;
|
||||
use crate::ProcState;
|
||||
|
||||
use deno_ast::swc::ast as swc_ast;
|
||||
use deno_ast::swc::visit::noop_visit_type;
|
||||
use deno_ast::swc::visit::Visit;
|
||||
|
@ -18,6 +18,7 @@ use deno_core::futures::StreamExt;
|
|||
use deno_core::serde_json;
|
||||
use deno_core::serde_json::Value;
|
||||
use deno_core::LocalInspectorSession;
|
||||
use deno_graph::npm::NpmPackageReference;
|
||||
use deno_graph::source::Resolver;
|
||||
use deno_runtime::deno_node;
|
||||
use deno_runtime::worker::MainWorker;
|
||||
|
|
|
@ -7,6 +7,7 @@ use deno_ast::MediaType;
|
|||
use deno_ast::ModuleSpecifier;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::resolve_url_or_path;
|
||||
use deno_graph::npm::NpmPackageReference;
|
||||
use deno_runtime::permissions::Permissions;
|
||||
use deno_runtime::permissions::PermissionsContainer;
|
||||
|
||||
|
@ -14,7 +15,6 @@ use crate::args::EvalFlags;
|
|||
use crate::args::Flags;
|
||||
use crate::args::RunFlags;
|
||||
use crate::file_fetcher::File;
|
||||
use crate::npm::NpmPackageReference;
|
||||
use crate::proc_state::ProcState;
|
||||
use crate::util;
|
||||
use crate::worker::create_main_worker;
|
||||
|
|
|
@ -16,6 +16,7 @@ use deno_core::anyhow::Context;
|
|||
use deno_core::error::AnyError;
|
||||
use deno_core::futures::future::BoxFuture;
|
||||
use deno_core::futures::FutureExt;
|
||||
use deno_graph::semver::Version;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::borrow::Cow;
|
||||
use std::env;
|
||||
|
@ -133,8 +134,8 @@ impl<TEnvironment: UpdateCheckerEnvironment> UpdateChecker<TEnvironment> {
|
|||
return None;
|
||||
}
|
||||
|
||||
if let Ok(current) = semver::Version::parse(&self.env.current_version()) {
|
||||
if let Ok(latest) = semver::Version::parse(&file.latest_version) {
|
||||
if let Ok(current) = Version::parse_standard(&self.env.current_version()) {
|
||||
if let Ok(latest) = Version::parse_standard(&file.latest_version) {
|
||||
if current >= latest {
|
||||
return None;
|
||||
}
|
||||
|
@ -292,9 +293,9 @@ pub async fn upgrade(
|
|||
{
|
||||
bail!("Invalid commit hash passed");
|
||||
} else if !upgrade_flags.canary
|
||||
&& semver::Version::parse(&passed_version).is_err()
|
||||
&& Version::parse_standard(&passed_version).is_err()
|
||||
{
|
||||
bail!("Invalid semver passed");
|
||||
bail!("Invalid version passed");
|
||||
}
|
||||
|
||||
let current_is_passed = if upgrade_flags.canary {
|
||||
|
@ -328,8 +329,8 @@ pub async fn upgrade(
|
|||
let latest_hash = latest_version.clone();
|
||||
crate::version::GIT_COMMIT_HASH == latest_hash
|
||||
} else if !crate::version::is_canary() {
|
||||
let current = semver::Version::parse(&crate::version::deno()).unwrap();
|
||||
let latest = semver::Version::parse(&latest_version).unwrap();
|
||||
let current = Version::parse_standard(&crate::version::deno()).unwrap();
|
||||
let latest = Version::parse_standard(&latest_version).unwrap();
|
||||
current >= latest
|
||||
} else {
|
||||
false
|
||||
|
|
|
@ -4,7 +4,6 @@ use crate::args::TsConfig;
|
|||
use crate::node;
|
||||
use crate::node::node_resolve_npm_reference;
|
||||
use crate::node::NodeResolution;
|
||||
use crate::npm::NpmPackageReference;
|
||||
use crate::npm::NpmPackageResolver;
|
||||
use crate::util::checksum;
|
||||
|
||||
|
@ -29,6 +28,7 @@ use deno_core::ModuleSpecifier;
|
|||
use deno_core::OpState;
|
||||
use deno_core::RuntimeOptions;
|
||||
use deno_core::Snapshot;
|
||||
use deno_graph::npm::NpmPackageReference;
|
||||
use deno_graph::ModuleGraph;
|
||||
use deno_graph::ModuleKind;
|
||||
use deno_graph::ResolutionResolved;
|
||||
|
|
|
@ -14,6 +14,7 @@ use deno_core::serde_v8;
|
|||
use deno_core::v8;
|
||||
use deno_core::Extension;
|
||||
use deno_core::ModuleId;
|
||||
use deno_graph::npm::NpmPackageReference;
|
||||
use deno_runtime::colors;
|
||||
use deno_runtime::deno_node;
|
||||
use deno_runtime::fmt_errors::format_js_error;
|
||||
|
@ -30,7 +31,6 @@ use crate::args::DenoSubcommand;
|
|||
use crate::errors;
|
||||
use crate::module_loader::CliModuleLoader;
|
||||
use crate::node;
|
||||
use crate::npm::NpmPackageReference;
|
||||
use crate::ops;
|
||||
use crate::proc_state::ProcState;
|
||||
use crate::tools;
|
||||
|
|
|
@ -30,7 +30,7 @@ regex.workspace = true
|
|||
reqwest.workspace = true
|
||||
ring.workspace = true
|
||||
rustls-pemfile.workspace = true
|
||||
semver.workspace = true
|
||||
semver = "=1.0.14"
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
tar.workspace = true
|
||||
|
|
Loading…
Reference in a new issue