mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
feat: subcommand to view and update outdated dependencies (#26942)
Closes #20487 Currently spelled ``` deno outdated ``` and ``` deno outdated --update ``` Works across package.json and deno.json, and in workspaces. There's a bit of duplicated code, I'll refactor to reduce this in follow ups ## Currently supported: ### Printing outdated deps (current output below which basically mimics pnpm, but requesting feedback / suggestions) ``` deno outdated ``` ![Screenshot 2024-11-19 at 2 01 56 PM](https://github.com/user-attachments/assets/51fea83a-181a-4082-b388-163313ce15e7) ### Updating deps semver compatible: ``` deno outdated --update ``` latest: ``` deno outdated --latest ``` current output is basic, again would love suggestions ![Screenshot 2024-11-19 at 2 13 46 PM](https://github.com/user-attachments/assets/e4c4db87-cd67-4b74-9ea7-4bd80106d5e9) #### Filters ``` deno outdated --update "@std/*" deno outdated --update --latest "@std/* "!@std/fmt" ``` #### Update to specific versions ``` deno outdated --update @std/fmt@1.0.2 @std/cli@^1.0.3 ``` ### Include all workspace members ``` deno outdated --recursive deno outdated --update --recursive ``` ## Future work - interactive update - update deps in js/ts files - better support for transitive deps Known issues (to be fixed in follow ups): - If no top level dependencies have changed, we won't update transitive deps (even if they could be updated) - Can't filter transitive deps, or update them to specific versions ## TODO (in this PR): - ~~spec tests for filters~~ - ~~spec test for mixed workspace (have tested manually)~~ - tweak output - suggestion when you try `deno update` --------- Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
parent
0670206a2c
commit
56f31628f7
80 changed files with 3109 additions and 18 deletions
|
@ -465,6 +465,7 @@ pub enum DenoSubcommand {
|
|||
Serve(ServeFlags),
|
||||
Task(TaskFlags),
|
||||
Test(TestFlags),
|
||||
Outdated(OutdatedFlags),
|
||||
Types,
|
||||
Upgrade(UpgradeFlags),
|
||||
Vendor,
|
||||
|
@ -472,6 +473,19 @@ pub enum DenoSubcommand {
|
|||
Help(HelpFlags),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum OutdatedKind {
|
||||
Update { latest: bool },
|
||||
PrintOutdated { compatible: bool },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct OutdatedFlags {
|
||||
pub filters: Vec<String>,
|
||||
pub recursive: bool,
|
||||
pub kind: OutdatedKind,
|
||||
}
|
||||
|
||||
impl DenoSubcommand {
|
||||
pub fn is_run(&self) -> bool {
|
||||
matches!(self, Self::Run(_))
|
||||
|
@ -1203,6 +1217,7 @@ static DENO_HELP: &str = cstr!(
|
|||
<p(245)>deno add jsr:@std/assert | deno add npm:express</>
|
||||
<g>install</> Installs dependencies either in the local project or globally to a bin directory
|
||||
<g>uninstall</> Uninstalls a dependency or an executable script in the installation root's bin directory
|
||||
<g>outdated</> Find and update outdated dependencies
|
||||
<g>remove</> Remove dependencies from the configuration file
|
||||
|
||||
<y>Tooling:</>
|
||||
|
@ -1385,6 +1400,7 @@ pub fn flags_from_vec(args: Vec<OsString>) -> clap::error::Result<Flags> {
|
|||
"jupyter" => jupyter_parse(&mut flags, &mut m),
|
||||
"lint" => lint_parse(&mut flags, &mut m)?,
|
||||
"lsp" => lsp_parse(&mut flags, &mut m),
|
||||
"outdated" => outdated_parse(&mut flags, &mut m)?,
|
||||
"repl" => repl_parse(&mut flags, &mut m)?,
|
||||
"run" => run_parse(&mut flags, &mut m, app, false)?,
|
||||
"serve" => serve_parse(&mut flags, &mut m, app)?,
|
||||
|
@ -1627,6 +1643,7 @@ pub fn clap_root() -> Command {
|
|||
.subcommand(json_reference_subcommand())
|
||||
.subcommand(jupyter_subcommand())
|
||||
.subcommand(uninstall_subcommand())
|
||||
.subcommand(outdated_subcommand())
|
||||
.subcommand(lsp_subcommand())
|
||||
.subcommand(lint_subcommand())
|
||||
.subcommand(publish_subcommand())
|
||||
|
@ -2617,6 +2634,83 @@ fn jupyter_subcommand() -> Command {
|
|||
.conflicts_with("install"))
|
||||
}
|
||||
|
||||
fn outdated_subcommand() -> Command {
|
||||
command(
|
||||
"outdated",
|
||||
cstr!("Find and update outdated dependencies.
|
||||
By default, outdated dependencies are only displayed.
|
||||
|
||||
Display outdated dependencies:
|
||||
<p(245)>deno outdated</>
|
||||
<p(245)>deno outdated --compatible</>
|
||||
|
||||
Update dependencies:
|
||||
<p(245)>deno outdated --update</>
|
||||
<p(245)>deno outdated --update --latest</>
|
||||
<p(245)>deno outdated --update</>
|
||||
|
||||
Filters can be used to select which packages to act on. Filters can include wildcards (*) to match multiple packages.
|
||||
<p(245)>deno outdated --update --latest \"@std/*\"</>
|
||||
<p(245)>deno outdated --update --latest \"react*\"</>
|
||||
Note that filters act on their aliases configured in deno.json / package.json, not the actual package names:
|
||||
Given \"foobar\": \"npm:react@17.0.0\" in deno.json or package.json, the filter \"foobar\" would update npm:react to
|
||||
the latest version.
|
||||
<p(245)>deno outdated --update --latest foobar</>
|
||||
Filters can be combined, and negative filters can be used to exclude results:
|
||||
<p(245)>deno outdated --update --latest \"@std/*\" \"!@std/fmt*\"</>
|
||||
|
||||
Specific version requirements to update to can be specified:
|
||||
<p(245)>deno outdated --update @std/fmt@^1.0.2</>
|
||||
"),
|
||||
UnstableArgsConfig::None,
|
||||
)
|
||||
.defer(|cmd| {
|
||||
cmd
|
||||
.arg(
|
||||
Arg::new("filters")
|
||||
.num_args(0..)
|
||||
.action(ArgAction::Append)
|
||||
.help(concat!("Filters selecting which packages to act on. Can include wildcards (*) to match multiple packages. ",
|
||||
"If a version requirement is specified, the matching packages will be updated to the given requirement."),
|
||||
)
|
||||
)
|
||||
.arg(no_lock_arg())
|
||||
.arg(lock_arg())
|
||||
.arg(
|
||||
Arg::new("latest")
|
||||
.long("latest")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help(
|
||||
"Update to the latest version, regardless of semver constraints",
|
||||
)
|
||||
.requires("update")
|
||||
.conflicts_with("compatible"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("update")
|
||||
.long("update")
|
||||
.short('u')
|
||||
.action(ArgAction::SetTrue)
|
||||
.conflicts_with("compatible")
|
||||
.help("Update dependency versions"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("compatible")
|
||||
.long("compatible")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Only output versions that satisfy semver requirements")
|
||||
.conflicts_with("update"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("recursive")
|
||||
.long("recursive")
|
||||
.short('r')
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("include all workspace members"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn uninstall_subcommand() -> Command {
|
||||
command(
|
||||
"uninstall",
|
||||
|
@ -4353,6 +4447,31 @@ fn remove_parse(flags: &mut Flags, matches: &mut ArgMatches) {
|
|||
});
|
||||
}
|
||||
|
||||
fn outdated_parse(
|
||||
flags: &mut Flags,
|
||||
matches: &mut ArgMatches,
|
||||
) -> clap::error::Result<()> {
|
||||
let filters = match matches.remove_many::<String>("filters") {
|
||||
Some(f) => f.collect(),
|
||||
None => vec![],
|
||||
};
|
||||
let recursive = matches.get_flag("recursive");
|
||||
let update = matches.get_flag("update");
|
||||
let kind = if update {
|
||||
let latest = matches.get_flag("latest");
|
||||
OutdatedKind::Update { latest }
|
||||
} else {
|
||||
let compatible = matches.get_flag("compatible");
|
||||
OutdatedKind::PrintOutdated { compatible }
|
||||
};
|
||||
flags.subcommand = DenoSubcommand::Outdated(OutdatedFlags {
|
||||
filters,
|
||||
recursive,
|
||||
kind,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bench_parse(
|
||||
flags: &mut Flags,
|
||||
matches: &mut ArgMatches,
|
||||
|
@ -11299,4 +11418,77 @@ Usage: deno repl [OPTIONS] [-- [ARGS]...]\n"
|
|||
assert!(r.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outdated_subcommand() {
|
||||
let cases = [
|
||||
(
|
||||
svec![],
|
||||
OutdatedFlags {
|
||||
filters: vec![],
|
||||
kind: OutdatedKind::PrintOutdated { compatible: false },
|
||||
recursive: false,
|
||||
},
|
||||
),
|
||||
(
|
||||
svec!["--recursive"],
|
||||
OutdatedFlags {
|
||||
filters: vec![],
|
||||
kind: OutdatedKind::PrintOutdated { compatible: false },
|
||||
recursive: true,
|
||||
},
|
||||
),
|
||||
(
|
||||
svec!["--recursive", "--compatible"],
|
||||
OutdatedFlags {
|
||||
filters: vec![],
|
||||
kind: OutdatedKind::PrintOutdated { compatible: true },
|
||||
recursive: true,
|
||||
},
|
||||
),
|
||||
(
|
||||
svec!["--update"],
|
||||
OutdatedFlags {
|
||||
filters: vec![],
|
||||
kind: OutdatedKind::Update { latest: false },
|
||||
recursive: false,
|
||||
},
|
||||
),
|
||||
(
|
||||
svec!["--update", "--latest"],
|
||||
OutdatedFlags {
|
||||
filters: vec![],
|
||||
kind: OutdatedKind::Update { latest: true },
|
||||
recursive: false,
|
||||
},
|
||||
),
|
||||
(
|
||||
svec!["--update", "--recursive"],
|
||||
OutdatedFlags {
|
||||
filters: vec![],
|
||||
kind: OutdatedKind::Update { latest: false },
|
||||
recursive: true,
|
||||
},
|
||||
),
|
||||
(
|
||||
svec!["--update", "@foo/bar"],
|
||||
OutdatedFlags {
|
||||
filters: svec!["@foo/bar"],
|
||||
kind: OutdatedKind::Update { latest: false },
|
||||
recursive: false,
|
||||
},
|
||||
),
|
||||
];
|
||||
for (input, expected) in cases {
|
||||
let mut args = svec!["deno", "outdated"];
|
||||
args.extend(input);
|
||||
let r = flags_from_vec(args.clone()).unwrap();
|
||||
assert_eq!(
|
||||
r.subcommand,
|
||||
DenoSubcommand::Outdated(expected),
|
||||
"incorrect result for args: {:?}",
|
||||
args
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1628,6 +1628,7 @@ impl CliOptions {
|
|||
DenoSubcommand::Install(_)
|
||||
| DenoSubcommand::Add(_)
|
||||
| DenoSubcommand::Remove(_)
|
||||
| DenoSubcommand::Outdated(_)
|
||||
) {
|
||||
// For `deno install/add/remove` we want to force the managed resolver so it can set up `node_modules/` directory.
|
||||
return false;
|
||||
|
|
|
@ -188,6 +188,11 @@ async fn run_subcommand(flags: Arc<Flags>) -> Result<i32, AnyError> {
|
|||
tools::lint::lint(flags, lint_flags).await
|
||||
}
|
||||
}),
|
||||
DenoSubcommand::Outdated(update_flags) => {
|
||||
spawn_subcommand(async move {
|
||||
tools::registry::outdated(flags, update_flags).await
|
||||
})
|
||||
}
|
||||
DenoSubcommand::Repl(repl_flags) => {
|
||||
spawn_subcommand(async move { tools::repl::run(flags, repl_flags).await })
|
||||
}
|
||||
|
|
|
@ -500,7 +500,7 @@ impl ManagedCliNpmResolver {
|
|||
self.resolve_pkg_folder_from_pkg_id(&pkg_id)
|
||||
}
|
||||
|
||||
fn resolve_pkg_id_from_pkg_req(
|
||||
pub fn resolve_pkg_id_from_pkg_req(
|
||||
&self,
|
||||
req: &PackageReq,
|
||||
) -> Result<NpmPackageId, PackageReqNotFoundError> {
|
||||
|
|
|
@ -68,6 +68,7 @@ use auth::get_auth_method;
|
|||
use auth::AuthMethod;
|
||||
pub use pm::add;
|
||||
pub use pm::cache_top_level_deps;
|
||||
pub use pm::outdated;
|
||||
pub use pm::remove;
|
||||
pub use pm::AddCommandName;
|
||||
pub use pm::AddRmPackageReq;
|
||||
|
|
|
@ -16,6 +16,7 @@ use deno_semver::package::PackageNv;
|
|||
use deno_semver::package::PackageReq;
|
||||
use deno_semver::Version;
|
||||
use deno_semver::VersionReq;
|
||||
use deps::KeyPath;
|
||||
use jsonc_parser::cst::CstObject;
|
||||
use jsonc_parser::cst::CstObjectProp;
|
||||
use jsonc_parser::cst::CstRootNode;
|
||||
|
@ -32,10 +33,13 @@ use crate::jsr::JsrFetchResolver;
|
|||
use crate::npm::NpmFetchResolver;
|
||||
|
||||
mod cache_deps;
|
||||
pub(crate) mod deps;
|
||||
mod outdated;
|
||||
|
||||
pub use cache_deps::cache_top_level_deps;
|
||||
pub use outdated::outdated;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Hash)]
|
||||
enum ConfigKind {
|
||||
DenoJson,
|
||||
PackageJson,
|
||||
|
@ -86,6 +90,28 @@ impl ConfigUpdater {
|
|||
self.cst.to_string()
|
||||
}
|
||||
|
||||
fn get_property_for_mutation(
|
||||
&mut self,
|
||||
key_path: &KeyPath,
|
||||
) -> Option<CstObjectProp> {
|
||||
let mut current_node = self.root_object.clone();
|
||||
|
||||
self.modified = true;
|
||||
|
||||
for (i, part) in key_path.parts.iter().enumerate() {
|
||||
let s = part.as_str();
|
||||
if i < key_path.parts.len().saturating_sub(1) {
|
||||
let object = current_node.object_value(s)?;
|
||||
current_node = object;
|
||||
} else {
|
||||
// last part
|
||||
return current_node.get(s);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn add(&mut self, selected: SelectedPackage, dev: bool) {
|
||||
fn insert_index(object: &CstObject, searching_name: &str) -> usize {
|
||||
object
|
||||
|
@ -824,7 +850,7 @@ async fn npm_install_after_modification(
|
|||
flags: Arc<Flags>,
|
||||
// explicitly provided to prevent redownloading
|
||||
jsr_resolver: Option<Arc<crate::jsr::JsrFetchResolver>>,
|
||||
) -> Result<(), AnyError> {
|
||||
) -> Result<CliFactory, AnyError> {
|
||||
// clear the previously cached package.json from memory before reloading it
|
||||
node_resolver::PackageJsonThreadLocalCache::clear();
|
||||
|
||||
|
@ -842,7 +868,7 @@ async fn npm_install_after_modification(
|
|||
lockfile.write_if_changed()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(cli_factory)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::graph_container::ModuleGraphUpdatePermit;
|
|||
use deno_core::error::AnyError;
|
||||
use deno_core::futures::stream::FuturesUnordered;
|
||||
use deno_core::futures::StreamExt;
|
||||
use deno_semver::package::PackageReq;
|
||||
use deno_semver::jsr::JsrPackageReqReference;
|
||||
|
||||
pub async fn cache_top_level_deps(
|
||||
// todo(dsherret): don't pass the factory into this function. Instead use ctor deps
|
||||
|
@ -56,15 +56,20 @@ pub async fn cache_top_level_deps(
|
|||
match specifier.scheme() {
|
||||
"jsr" => {
|
||||
let specifier_str = specifier.as_str();
|
||||
let specifier_str =
|
||||
specifier_str.strip_prefix("jsr:").unwrap_or(specifier_str);
|
||||
if let Ok(req) = PackageReq::from_str(specifier_str) {
|
||||
if !seen_reqs.insert(req.clone()) {
|
||||
if let Ok(req) = JsrPackageReqReference::from_str(specifier_str) {
|
||||
if let Some(sub_path) = req.sub_path() {
|
||||
if sub_path.ends_with('/') {
|
||||
continue;
|
||||
}
|
||||
roots.push(specifier.clone());
|
||||
continue;
|
||||
}
|
||||
if !seen_reqs.insert(req.req().clone()) {
|
||||
continue;
|
||||
}
|
||||
let jsr_resolver = jsr_resolver.clone();
|
||||
info_futures.push(async move {
|
||||
if let Some(nv) = jsr_resolver.req_to_nv(&req).await {
|
||||
if let Some(nv) = jsr_resolver.req_to_nv(req.req()).await {
|
||||
if let Some(info) = jsr_resolver.package_version_info(&nv).await
|
||||
{
|
||||
return Some((specifier.clone(), info));
|
||||
|
|
964
cli/tools/registry/pm/deps.rs
Normal file
964
cli/tools/registry/pm/deps.rs
Normal file
|
@ -0,0 +1,964 @@
|
|||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
use deno_ast::ModuleSpecifier;
|
||||
use deno_config::deno_json::ConfigFile;
|
||||
use deno_config::deno_json::ConfigFileRc;
|
||||
use deno_config::workspace::Workspace;
|
||||
use deno_config::workspace::WorkspaceDirectory;
|
||||
use deno_core::anyhow::bail;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::futures::future::try_join;
|
||||
use deno_core::futures::stream::FuturesOrdered;
|
||||
use deno_core::futures::stream::FuturesUnordered;
|
||||
use deno_core::futures::FutureExt;
|
||||
use deno_core::futures::StreamExt;
|
||||
use deno_core::serde_json;
|
||||
use deno_graph::FillFromLockfileOptions;
|
||||
use deno_package_json::PackageJsonDepValue;
|
||||
use deno_package_json::PackageJsonDepValueParseError;
|
||||
use deno_package_json::PackageJsonRc;
|
||||
use deno_runtime::deno_permissions::PermissionsContainer;
|
||||
use deno_semver::jsr::JsrPackageReqReference;
|
||||
use deno_semver::npm::NpmPackageReqReference;
|
||||
use deno_semver::package::PackageNv;
|
||||
use deno_semver::package::PackageReq;
|
||||
use deno_semver::package::PackageReqReference;
|
||||
use deno_semver::VersionReq;
|
||||
use import_map::ImportMap;
|
||||
use import_map::ImportMapWithDiagnostics;
|
||||
use import_map::SpecifierMapEntry;
|
||||
use indexmap::IndexMap;
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
use crate::args::CliLockfile;
|
||||
use crate::graph_container::MainModuleGraphContainer;
|
||||
use crate::graph_container::ModuleGraphContainer;
|
||||
use crate::graph_container::ModuleGraphUpdatePermit;
|
||||
use crate::jsr::JsrFetchResolver;
|
||||
use crate::module_loader::ModuleLoadPreparer;
|
||||
use crate::npm::CliNpmResolver;
|
||||
use crate::npm::NpmFetchResolver;
|
||||
|
||||
use super::ConfigUpdater;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ImportMapKind {
|
||||
Inline,
|
||||
Outline,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum DepLocation {
|
||||
DenoJson(ConfigFileRc, KeyPath, ImportMapKind),
|
||||
PackageJson(PackageJsonRc, KeyPath),
|
||||
}
|
||||
|
||||
impl DepLocation {
|
||||
pub fn is_deno_json(&self) -> bool {
|
||||
matches!(self, DepLocation::DenoJson(..))
|
||||
}
|
||||
|
||||
pub fn file_path(&self) -> Cow<std::path::Path> {
|
||||
match self {
|
||||
DepLocation::DenoJson(arc, _, _) => {
|
||||
Cow::Owned(arc.specifier.to_file_path().unwrap())
|
||||
}
|
||||
DepLocation::PackageJson(arc, _) => Cow::Borrowed(arc.path.as_ref()),
|
||||
}
|
||||
}
|
||||
fn config_kind(&self) -> super::ConfigKind {
|
||||
match self {
|
||||
DepLocation::DenoJson(_, _, _) => super::ConfigKind::DenoJson,
|
||||
DepLocation::PackageJson(_, _) => super::ConfigKind::PackageJson,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DebugAdapter<T>(T);
|
||||
|
||||
impl<'a> std::fmt::Debug for DebugAdapter<&'a ConfigFileRc> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ConfigFile")
|
||||
.field("specifier", &self.0.specifier)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl<'a> std::fmt::Debug for DebugAdapter<&'a PackageJsonRc> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("PackageJson")
|
||||
.field("path", &self.0.path)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DepLocation {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
DepLocation::DenoJson(arc, key_path, kind) => {
|
||||
let mut debug = f.debug_tuple("DenoJson");
|
||||
debug
|
||||
.field(&DebugAdapter(arc))
|
||||
.field(key_path)
|
||||
.field(kind)
|
||||
.finish()
|
||||
}
|
||||
DepLocation::PackageJson(arc, key_path) => {
|
||||
let mut debug = f.debug_tuple("PackageJson");
|
||||
debug.field(&DebugAdapter(arc)).field(key_path).finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum DepKind {
|
||||
Jsr,
|
||||
Npm,
|
||||
}
|
||||
|
||||
impl DepKind {
|
||||
pub fn scheme(&self) -> &'static str {
|
||||
match self {
|
||||
DepKind::Npm => "npm",
|
||||
DepKind::Jsr => "jsr",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum KeyPart {
|
||||
Imports,
|
||||
Scopes,
|
||||
Dependencies,
|
||||
DevDependencies,
|
||||
String(String),
|
||||
}
|
||||
|
||||
impl From<String> for KeyPart {
|
||||
fn from(value: String) -> Self {
|
||||
KeyPart::String(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PackageJsonDepKind> for KeyPart {
|
||||
fn from(value: PackageJsonDepKind) -> Self {
|
||||
match value {
|
||||
PackageJsonDepKind::Normal => Self::Dependencies,
|
||||
PackageJsonDepKind::Dev => Self::DevDependencies,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyPart {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
KeyPart::Imports => "imports",
|
||||
KeyPart::Scopes => "scopes",
|
||||
KeyPart::Dependencies => "dependencies",
|
||||
KeyPart::DevDependencies => "devDependencies",
|
||||
KeyPart::String(s) => s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct KeyPath {
|
||||
pub parts: Vec<KeyPart>,
|
||||
}
|
||||
|
||||
impl KeyPath {
|
||||
fn from_parts(parts: impl IntoIterator<Item = KeyPart>) -> Self {
|
||||
Self {
|
||||
parts: parts.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
fn last(&self) -> Option<&KeyPart> {
|
||||
self.parts.last()
|
||||
}
|
||||
fn push(&mut self, part: KeyPart) {
|
||||
self.parts.push(part)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Dep {
|
||||
pub req: PackageReq,
|
||||
pub kind: DepKind,
|
||||
pub location: DepLocation,
|
||||
#[allow(dead_code)]
|
||||
pub id: DepId,
|
||||
#[allow(dead_code)]
|
||||
pub alias: Option<String>,
|
||||
}
|
||||
|
||||
fn import_map_entries(
|
||||
import_map: &ImportMap,
|
||||
) -> impl Iterator<Item = (KeyPath, SpecifierMapEntry<'_>)> {
|
||||
import_map
|
||||
.imports()
|
||||
.entries()
|
||||
.map(|entry| {
|
||||
(
|
||||
KeyPath::from_parts([
|
||||
KeyPart::Imports,
|
||||
KeyPart::String(entry.raw_key.into()),
|
||||
]),
|
||||
entry,
|
||||
)
|
||||
})
|
||||
.chain(import_map.scopes().flat_map(|scope| {
|
||||
let path = KeyPath::from_parts([
|
||||
KeyPart::Scopes,
|
||||
scope.raw_key.to_string().into(),
|
||||
]);
|
||||
|
||||
scope.imports.entries().map(move |entry| {
|
||||
let mut full_path = path.clone();
|
||||
full_path.push(KeyPart::String(entry.raw_key.to_string()));
|
||||
(full_path, entry)
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn to_import_map_value_from_imports(
|
||||
deno_json: &ConfigFile,
|
||||
) -> serde_json::Value {
|
||||
let mut value = serde_json::Map::with_capacity(2);
|
||||
if let Some(imports) = &deno_json.json.imports {
|
||||
value.insert("imports".to_string(), imports.clone());
|
||||
}
|
||||
if let Some(scopes) = &deno_json.json.scopes {
|
||||
value.insert("scopes".to_string(), scopes.clone());
|
||||
}
|
||||
serde_json::Value::Object(value)
|
||||
}
|
||||
|
||||
fn deno_json_import_map(
|
||||
deno_json: &ConfigFile,
|
||||
) -> Result<Option<(ImportMapWithDiagnostics, ImportMapKind)>, AnyError> {
|
||||
let (value, kind) =
|
||||
if deno_json.json.imports.is_some() || deno_json.json.scopes.is_some() {
|
||||
(
|
||||
to_import_map_value_from_imports(deno_json),
|
||||
ImportMapKind::Inline,
|
||||
)
|
||||
} else {
|
||||
match deno_json.to_import_map_path()? {
|
||||
Some(path) => {
|
||||
let text = std::fs::read_to_string(&path)?;
|
||||
let value = serde_json::from_str(&text)?;
|
||||
(value, ImportMapKind::Outline)
|
||||
}
|
||||
None => return Ok(None),
|
||||
}
|
||||
};
|
||||
|
||||
import_map::parse_from_value(deno_json.specifier.clone(), value)
|
||||
.map_err(Into::into)
|
||||
.map(|import_map| Some((import_map, kind)))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum PackageJsonDepKind {
|
||||
Normal,
|
||||
Dev,
|
||||
}
|
||||
|
||||
type PackageJsonDeps = IndexMap<
|
||||
String,
|
||||
Result<
|
||||
(PackageJsonDepKind, PackageJsonDepValue),
|
||||
PackageJsonDepValueParseError,
|
||||
>,
|
||||
>;
|
||||
|
||||
/// Resolve the package.json's dependencies.
|
||||
// TODO(nathanwhit): Remove once we update deno_package_json with dev deps split out
|
||||
fn resolve_local_package_json_deps(
|
||||
package_json: &PackageJsonRc,
|
||||
) -> PackageJsonDeps {
|
||||
/// Gets the name and raw version constraint for a registry info or
|
||||
/// package.json dependency entry taking into account npm package aliases.
|
||||
fn parse_dep_entry_name_and_raw_version<'a>(
|
||||
key: &'a str,
|
||||
value: &'a str,
|
||||
) -> (&'a str, &'a str) {
|
||||
if let Some(package_and_version) = value.strip_prefix("npm:") {
|
||||
if let Some((name, version)) = package_and_version.rsplit_once('@') {
|
||||
// if empty, then the name was scoped and there's no version
|
||||
if name.is_empty() {
|
||||
(package_and_version, "*")
|
||||
} else {
|
||||
(name, version)
|
||||
}
|
||||
} else {
|
||||
(package_and_version, "*")
|
||||
}
|
||||
} else {
|
||||
(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_entry(
|
||||
key: &str,
|
||||
value: &str,
|
||||
) -> Result<PackageJsonDepValue, PackageJsonDepValueParseError> {
|
||||
if let Some(workspace_key) = value.strip_prefix("workspace:") {
|
||||
let version_req = VersionReq::parse_from_npm(workspace_key)?;
|
||||
return Ok(PackageJsonDepValue::Workspace(version_req));
|
||||
}
|
||||
if value.starts_with("file:")
|
||||
|| value.starts_with("git:")
|
||||
|| value.starts_with("http:")
|
||||
|| value.starts_with("https:")
|
||||
{
|
||||
return Err(PackageJsonDepValueParseError::Unsupported {
|
||||
scheme: value.split(':').next().unwrap().to_string(),
|
||||
});
|
||||
}
|
||||
let (name, version_req) = parse_dep_entry_name_and_raw_version(key, value);
|
||||
let result = VersionReq::parse_from_npm(version_req);
|
||||
match result {
|
||||
Ok(version_req) => Ok(PackageJsonDepValue::Req(PackageReq {
|
||||
name: name.to_string(),
|
||||
version_req,
|
||||
})),
|
||||
Err(err) => Err(PackageJsonDepValueParseError::VersionReq(err)),
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_deps(
|
||||
deps: Option<&IndexMap<String, String>>,
|
||||
result: &mut PackageJsonDeps,
|
||||
kind: PackageJsonDepKind,
|
||||
) {
|
||||
if let Some(deps) = deps {
|
||||
for (key, value) in deps {
|
||||
result.entry(key.to_string()).or_insert_with(|| {
|
||||
parse_entry(key, value).map(|entry| (kind, entry))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let deps = package_json.dependencies.as_ref();
|
||||
let dev_deps = package_json.dev_dependencies.as_ref();
|
||||
let mut result = IndexMap::new();
|
||||
|
||||
// favors the deps over dev_deps
|
||||
insert_deps(deps, &mut result, PackageJsonDepKind::Normal);
|
||||
insert_deps(dev_deps, &mut result, PackageJsonDepKind::Dev);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn add_deps_from_deno_json(
|
||||
deno_json: &Arc<ConfigFile>,
|
||||
mut filter: impl DepFilter,
|
||||
deps: &mut Vec<Dep>,
|
||||
) {
|
||||
let (import_map, import_map_kind) = match deno_json_import_map(deno_json) {
|
||||
Ok(Some((import_map, import_map_kind))) => (import_map, import_map_kind),
|
||||
Ok(None) => return,
|
||||
Err(e) => {
|
||||
log::warn!("failed to parse imports from {}: {e}", &deno_json.specifier);
|
||||
return;
|
||||
}
|
||||
};
|
||||
for (key_path, entry) in import_map_entries(&import_map.import_map) {
|
||||
let Some(value) = entry.value else { continue };
|
||||
let kind = match value.scheme() {
|
||||
"npm" => DepKind::Npm,
|
||||
"jsr" => DepKind::Jsr,
|
||||
_ => continue,
|
||||
};
|
||||
let req = match parse_req_reference(value.as_str(), kind) {
|
||||
Ok(req) => req.req.clone(),
|
||||
Err(err) => {
|
||||
log::warn!("failed to parse package req \"{}\": {err}", value.as_str());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let alias: &str = key_path.last().unwrap().as_str().trim_end_matches('/');
|
||||
let alias = (alias != req.name).then(|| alias.to_string());
|
||||
if !filter.should_include(alias.as_deref(), &req, kind) {
|
||||
continue;
|
||||
}
|
||||
let id = DepId(deps.len());
|
||||
deps.push(Dep {
|
||||
location: DepLocation::DenoJson(
|
||||
deno_json.clone(),
|
||||
key_path,
|
||||
import_map_kind,
|
||||
),
|
||||
kind,
|
||||
req,
|
||||
id,
|
||||
alias,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn add_deps_from_package_json(
|
||||
package_json: &PackageJsonRc,
|
||||
mut filter: impl DepFilter,
|
||||
deps: &mut Vec<Dep>,
|
||||
) {
|
||||
let package_json_deps = resolve_local_package_json_deps(package_json);
|
||||
for (k, v) in package_json_deps {
|
||||
let (package_dep_kind, v) = match v {
|
||||
Ok((k, v)) => (k, v),
|
||||
Err(e) => {
|
||||
log::warn!("bad package json dep value: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match v {
|
||||
deno_package_json::PackageJsonDepValue::Req(req) => {
|
||||
let alias = k.as_str();
|
||||
let alias = (alias != req.name).then(|| alias.to_string());
|
||||
if !filter.should_include(alias.as_deref(), &req, DepKind::Npm) {
|
||||
continue;
|
||||
}
|
||||
let id = DepId(deps.len());
|
||||
deps.push(Dep {
|
||||
id,
|
||||
kind: DepKind::Npm,
|
||||
location: DepLocation::PackageJson(
|
||||
package_json.clone(),
|
||||
KeyPath::from_parts([package_dep_kind.into(), k.into()]),
|
||||
),
|
||||
req,
|
||||
alias,
|
||||
})
|
||||
}
|
||||
deno_package_json::PackageJsonDepValue::Workspace(_) => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn deps_from_workspace(
|
||||
workspace: &Arc<Workspace>,
|
||||
dep_filter: impl DepFilter,
|
||||
) -> Result<Vec<Dep>, AnyError> {
|
||||
let mut deps = Vec::with_capacity(256);
|
||||
for deno_json in workspace.deno_jsons() {
|
||||
add_deps_from_deno_json(deno_json, dep_filter, &mut deps);
|
||||
}
|
||||
for package_json in workspace.package_jsons() {
|
||||
add_deps_from_package_json(package_json, dep_filter, &mut deps);
|
||||
}
|
||||
|
||||
Ok(deps)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct DepId(usize);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Change {
|
||||
Update(DepId, VersionReq),
|
||||
}
|
||||
|
||||
pub trait DepFilter: Copy {
|
||||
fn should_include(
|
||||
&mut self,
|
||||
alias: Option<&str>,
|
||||
package_req: &PackageReq,
|
||||
dep_kind: DepKind,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
impl<T> DepFilter for T
|
||||
where
|
||||
T: FnMut(Option<&str>, &PackageReq, DepKind) -> bool + Copy,
|
||||
{
|
||||
fn should_include<'a>(
|
||||
&mut self,
|
||||
alias: Option<&'a str>,
|
||||
package_req: &'a PackageReq,
|
||||
dep_kind: DepKind,
|
||||
) -> bool {
|
||||
(*self)(alias, package_req, dep_kind)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PackageLatestVersion {
|
||||
pub semver_compatible: Option<PackageNv>,
|
||||
pub latest: Option<PackageNv>,
|
||||
}
|
||||
|
||||
pub struct DepManager {
|
||||
deps: Vec<Dep>,
|
||||
resolved_versions: Vec<Option<PackageNv>>,
|
||||
latest_versions: Vec<PackageLatestVersion>,
|
||||
|
||||
pending_changes: Vec<Change>,
|
||||
|
||||
dependencies_resolved: AtomicBool,
|
||||
module_load_preparer: Arc<ModuleLoadPreparer>,
|
||||
// TODO(nathanwhit): probably shouldn't be pub
|
||||
pub(crate) jsr_fetch_resolver: Arc<JsrFetchResolver>,
|
||||
pub(crate) npm_fetch_resolver: Arc<NpmFetchResolver>,
|
||||
npm_resolver: Arc<dyn CliNpmResolver>,
|
||||
permissions_container: PermissionsContainer,
|
||||
main_module_graph_container: Arc<MainModuleGraphContainer>,
|
||||
lockfile: Option<Arc<CliLockfile>>,
|
||||
}
|
||||
|
||||
pub struct DepManagerArgs {
|
||||
pub module_load_preparer: Arc<ModuleLoadPreparer>,
|
||||
pub jsr_fetch_resolver: Arc<JsrFetchResolver>,
|
||||
pub npm_fetch_resolver: Arc<NpmFetchResolver>,
|
||||
pub npm_resolver: Arc<dyn CliNpmResolver>,
|
||||
pub permissions_container: PermissionsContainer,
|
||||
pub main_module_graph_container: Arc<MainModuleGraphContainer>,
|
||||
pub lockfile: Option<Arc<CliLockfile>>,
|
||||
}
|
||||
|
||||
impl DepManager {
|
||||
pub fn reloaded_after_modification(self, args: DepManagerArgs) -> Self {
|
||||
let mut new = Self::with_deps_args(self.deps, args);
|
||||
new.latest_versions = self.latest_versions;
|
||||
new
|
||||
}
|
||||
fn with_deps_args(deps: Vec<Dep>, args: DepManagerArgs) -> Self {
|
||||
let DepManagerArgs {
|
||||
module_load_preparer,
|
||||
jsr_fetch_resolver,
|
||||
npm_fetch_resolver,
|
||||
npm_resolver,
|
||||
permissions_container,
|
||||
main_module_graph_container,
|
||||
lockfile,
|
||||
} = args;
|
||||
Self {
|
||||
deps,
|
||||
resolved_versions: Vec::new(),
|
||||
latest_versions: Vec::new(),
|
||||
jsr_fetch_resolver,
|
||||
dependencies_resolved: AtomicBool::new(false),
|
||||
module_load_preparer,
|
||||
npm_fetch_resolver,
|
||||
npm_resolver,
|
||||
permissions_container,
|
||||
main_module_graph_container,
|
||||
lockfile,
|
||||
pending_changes: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub fn from_workspace_dir(
|
||||
workspace_dir: &Arc<WorkspaceDirectory>,
|
||||
dep_filter: impl DepFilter,
|
||||
args: DepManagerArgs,
|
||||
) -> Result<Self, AnyError> {
|
||||
let mut deps = Vec::with_capacity(256);
|
||||
if let Some(deno_json) = workspace_dir.maybe_deno_json() {
|
||||
if deno_json.specifier.scheme() != "file" {
|
||||
bail!("remote deno.json files are not supported");
|
||||
}
|
||||
let path = deno_json.specifier.to_file_path().unwrap();
|
||||
if path.parent().unwrap() == workspace_dir.dir_path() {
|
||||
add_deps_from_deno_json(deno_json, dep_filter, &mut deps);
|
||||
}
|
||||
}
|
||||
if let Some(package_json) = workspace_dir.maybe_pkg_json() {
|
||||
add_deps_from_package_json(package_json, dep_filter, &mut deps);
|
||||
}
|
||||
|
||||
Ok(Self::with_deps_args(deps, args))
|
||||
}
|
||||
pub fn from_workspace(
|
||||
workspace: &Arc<Workspace>,
|
||||
dep_filter: impl DepFilter,
|
||||
args: DepManagerArgs,
|
||||
) -> Result<Self, AnyError> {
|
||||
let deps = deps_from_workspace(workspace, dep_filter)?;
|
||||
Ok(Self::with_deps_args(deps, args))
|
||||
}
|
||||
|
||||
async fn run_dependency_resolution(&self) -> Result<(), AnyError> {
|
||||
if self
|
||||
.dependencies_resolved
|
||||
.load(std::sync::atomic::Ordering::Relaxed)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut graph_permit = self
|
||||
.main_module_graph_container
|
||||
.acquire_update_permit()
|
||||
.await;
|
||||
let graph = graph_permit.graph_mut();
|
||||
// populate the information from the lockfile
|
||||
if let Some(lockfile) = &self.lockfile {
|
||||
let lockfile = lockfile.lock();
|
||||
graph.fill_from_lockfile(FillFromLockfileOptions {
|
||||
redirects: lockfile
|
||||
.content
|
||||
.redirects
|
||||
.iter()
|
||||
.map(|(from, to)| (from.as_str(), to.as_str())),
|
||||
package_specifiers: lockfile
|
||||
.content
|
||||
.packages
|
||||
.specifiers
|
||||
.iter()
|
||||
.map(|(dep, id)| (dep, id.as_str())),
|
||||
});
|
||||
}
|
||||
|
||||
let npm_resolver = self.npm_resolver.as_managed().unwrap();
|
||||
if self.deps.iter().all(|dep| match dep.kind {
|
||||
DepKind::Npm => {
|
||||
npm_resolver.resolve_pkg_id_from_pkg_req(&dep.req).is_ok()
|
||||
}
|
||||
DepKind::Jsr => graph.packages.mappings().contains_key(&dep.req),
|
||||
}) {
|
||||
self
|
||||
.dependencies_resolved
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
graph_permit.commit();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
npm_resolver.ensure_top_level_package_json_install().await?;
|
||||
let mut roots = Vec::new();
|
||||
let mut info_futures = FuturesUnordered::new();
|
||||
for dep in &self.deps {
|
||||
if dep.location.is_deno_json() {
|
||||
match dep.kind {
|
||||
DepKind::Npm => roots.push(
|
||||
ModuleSpecifier::parse(&format!("npm:/{}/", dep.req)).unwrap(),
|
||||
),
|
||||
DepKind::Jsr => info_futures.push(async {
|
||||
if let Some(nv) = self.jsr_fetch_resolver.req_to_nv(&dep.req).await
|
||||
{
|
||||
if let Some(info) =
|
||||
self.jsr_fetch_resolver.package_version_info(&nv).await
|
||||
{
|
||||
let specifier =
|
||||
ModuleSpecifier::parse(&format!("jsr:/{}/", dep.req))
|
||||
.unwrap();
|
||||
return Some((specifier, info));
|
||||
}
|
||||
}
|
||||
None
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(info_future) = info_futures.next().await {
|
||||
if let Some((specifier, info)) = info_future {
|
||||
let exports = info.exports();
|
||||
for (k, _) in exports {
|
||||
if let Ok(spec) = specifier.join(k) {
|
||||
roots.push(spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
.module_load_preparer
|
||||
.prepare_module_load(
|
||||
graph,
|
||||
&roots,
|
||||
false,
|
||||
deno_config::deno_json::TsTypeLib::DenoWindow,
|
||||
self.permissions_container.clone(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
graph_permit.commit();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn resolved_version(&self, id: DepId) -> Option<&PackageNv> {
|
||||
self.resolved_versions[id.0].as_ref()
|
||||
}
|
||||
|
||||
pub async fn resolve_current_versions(&mut self) -> Result<(), AnyError> {
|
||||
self.run_dependency_resolution().await?;
|
||||
|
||||
let graph = self.main_module_graph_container.graph();
|
||||
|
||||
let mut resolved = Vec::with_capacity(self.deps.len());
|
||||
let snapshot = self.npm_resolver.as_managed().unwrap().snapshot();
|
||||
let resolved_npm = snapshot.package_reqs();
|
||||
let resolved_jsr = graph.packages.mappings();
|
||||
for dep in &self.deps {
|
||||
match dep.kind {
|
||||
DepKind::Npm => {
|
||||
let resolved_version = resolved_npm.get(&dep.req).cloned();
|
||||
resolved.push(resolved_version);
|
||||
}
|
||||
DepKind::Jsr => {
|
||||
let resolved_version = resolved_jsr.get(&dep.req).cloned();
|
||||
resolved.push(resolved_version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.resolved_versions = resolved;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn load_latest_versions(
|
||||
&self,
|
||||
) -> Result<Vec<PackageLatestVersion>, AnyError> {
|
||||
if self.latest_versions.len() == self.deps.len() {
|
||||
return Ok(self.latest_versions.clone());
|
||||
}
|
||||
let latest_tag_req = deno_semver::VersionReq::from_raw_text_and_inner(
|
||||
"latest".into(),
|
||||
deno_semver::RangeSetOrTag::Tag("latest".into()),
|
||||
);
|
||||
let mut latest_versions = Vec::with_capacity(self.deps.len());
|
||||
|
||||
let npm_sema = Semaphore::new(32);
|
||||
let jsr_sema = Semaphore::new(32);
|
||||
let mut futs = FuturesOrdered::new();
|
||||
|
||||
for dep in &self.deps {
|
||||
match dep.kind {
|
||||
DepKind::Npm => futs.push_back(
|
||||
async {
|
||||
let semver_req = &dep.req;
|
||||
let latest_req = PackageReq {
|
||||
name: dep.req.name.clone(),
|
||||
version_req: latest_tag_req.clone(),
|
||||
};
|
||||
let _permit = npm_sema.acquire().await;
|
||||
let semver_compatible =
|
||||
self.npm_fetch_resolver.req_to_nv(semver_req).await;
|
||||
let latest = self.npm_fetch_resolver.req_to_nv(&latest_req).await;
|
||||
PackageLatestVersion {
|
||||
latest,
|
||||
semver_compatible,
|
||||
}
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
DepKind::Jsr => futs.push_back(
|
||||
async {
|
||||
let semver_req = &dep.req;
|
||||
let latest_req = PackageReq {
|
||||
name: dep.req.name.clone(),
|
||||
version_req: deno_semver::WILDCARD_VERSION_REQ.clone(),
|
||||
};
|
||||
let _permit = jsr_sema.acquire().await;
|
||||
let semver_compatible =
|
||||
self.jsr_fetch_resolver.req_to_nv(semver_req).await;
|
||||
let latest = self.jsr_fetch_resolver.req_to_nv(&latest_req).await;
|
||||
PackageLatestVersion {
|
||||
latest,
|
||||
semver_compatible,
|
||||
}
|
||||
}
|
||||
.boxed_local(),
|
||||
),
|
||||
}
|
||||
}
|
||||
while let Some(nv) = futs.next().await {
|
||||
latest_versions.push(nv);
|
||||
}
|
||||
|
||||
Ok(latest_versions)
|
||||
}
|
||||
|
||||
pub async fn resolve_versions(&mut self) -> Result<(), AnyError> {
|
||||
let (_, latest_versions) = try_join(
|
||||
self.run_dependency_resolution(),
|
||||
self.load_latest_versions(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.latest_versions = latest_versions;
|
||||
|
||||
self.resolve_current_versions().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn deps_with_resolved_latest_versions(
|
||||
&self,
|
||||
) -> impl IntoIterator<Item = (DepId, Option<PackageNv>, PackageLatestVersion)> + '_
|
||||
{
|
||||
self
|
||||
.resolved_versions
|
||||
.iter()
|
||||
.zip(self.latest_versions.iter())
|
||||
.enumerate()
|
||||
.map(|(i, (resolved, latest))| {
|
||||
(DepId(i), resolved.clone(), latest.clone())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_dep(&self, id: DepId) -> &Dep {
|
||||
&self.deps[id.0]
|
||||
}
|
||||
|
||||
pub fn update_dep(&mut self, dep_id: DepId, new_version_req: VersionReq) {
|
||||
self
|
||||
.pending_changes
|
||||
.push(Change::Update(dep_id, new_version_req));
|
||||
}
|
||||
|
||||
pub fn commit_changes(&mut self) -> Result<(), AnyError> {
|
||||
let changes = std::mem::take(&mut self.pending_changes);
|
||||
let mut config_updaters = HashMap::new();
|
||||
for change in changes {
|
||||
match change {
|
||||
Change::Update(dep_id, version_req) => {
|
||||
// TODO: move most of this to ConfigUpdater
|
||||
let dep = &mut self.deps[dep_id.0];
|
||||
dep.req.version_req = version_req.clone();
|
||||
match &dep.location {
|
||||
DepLocation::DenoJson(arc, key_path, import_map_kind) => {
|
||||
if matches!(import_map_kind, ImportMapKind::Outline) {
|
||||
// not supported
|
||||
continue;
|
||||
}
|
||||
let updater =
|
||||
get_or_create_updater(&mut config_updaters, &dep.location)?;
|
||||
|
||||
let Some(property) = updater.get_property_for_mutation(key_path)
|
||||
else {
|
||||
log::warn!(
|
||||
"failed to find property at path {key_path:?} for file {}",
|
||||
arc.specifier
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let Some(string_value) = cst_string_literal(&property) else {
|
||||
continue;
|
||||
};
|
||||
let mut req_reference = match dep.kind {
|
||||
DepKind::Npm => NpmPackageReqReference::from_str(&string_value)
|
||||
.unwrap()
|
||||
.into_inner(),
|
||||
DepKind::Jsr => JsrPackageReqReference::from_str(&string_value)
|
||||
.unwrap()
|
||||
.into_inner(),
|
||||
};
|
||||
req_reference.req.version_req = version_req;
|
||||
let mut new_value =
|
||||
format!("{}:{}", dep.kind.scheme(), req_reference);
|
||||
if string_value.ends_with('/') && !new_value.ends_with('/') {
|
||||
// the display impl for PackageReqReference maps `/` to the root
|
||||
// subpath, but for the import map the trailing `/` is significant
|
||||
new_value.push('/');
|
||||
}
|
||||
if string_value
|
||||
.trim_start_matches(format!("{}:", dep.kind.scheme()).as_str())
|
||||
.starts_with('/')
|
||||
{
|
||||
// this is gross
|
||||
new_value = new_value.replace(':', ":/");
|
||||
}
|
||||
property
|
||||
.set_value(jsonc_parser::cst::CstInputValue::String(new_value));
|
||||
}
|
||||
DepLocation::PackageJson(arc, key_path) => {
|
||||
let updater =
|
||||
get_or_create_updater(&mut config_updaters, &dep.location)?;
|
||||
let Some(property) = updater.get_property_for_mutation(key_path)
|
||||
else {
|
||||
log::warn!(
|
||||
"failed to find property at path {key_path:?} for file {}",
|
||||
arc.path.display()
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let Some(string_value) = cst_string_literal(&property) else {
|
||||
continue;
|
||||
};
|
||||
let new_value = if string_value.starts_with("npm:") {
|
||||
// aliased
|
||||
let rest = string_value.trim_start_matches("npm:");
|
||||
let mut parts = rest.split('@');
|
||||
let first = parts.next().unwrap();
|
||||
if first.is_empty() {
|
||||
let scope_and_name = parts.next().unwrap();
|
||||
format!("npm:@{scope_and_name}@{version_req}")
|
||||
} else {
|
||||
format!("npm:{first}@{version_req}")
|
||||
}
|
||||
} else if string_value.contains(":") {
|
||||
bail!("Unexpected package json dependency string: \"{string_value}\" in {}", arc.path.display());
|
||||
} else {
|
||||
version_req.to_string()
|
||||
};
|
||||
property
|
||||
.set_value(jsonc_parser::cst::CstInputValue::String(new_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (_, updater) in config_updaters {
|
||||
updater.commit()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_or_create_updater<'a>(
|
||||
config_updaters: &'a mut HashMap<std::path::PathBuf, ConfigUpdater>,
|
||||
location: &DepLocation,
|
||||
) -> Result<&'a mut ConfigUpdater, AnyError> {
|
||||
match config_updaters.entry(location.file_path().into_owned()) {
|
||||
std::collections::hash_map::Entry::Occupied(occupied_entry) => {
|
||||
Ok(occupied_entry.into_mut())
|
||||
}
|
||||
std::collections::hash_map::Entry::Vacant(vacant_entry) => {
|
||||
let updater = ConfigUpdater::new(
|
||||
location.config_kind(),
|
||||
location.file_path().into_owned(),
|
||||
)?;
|
||||
Ok(vacant_entry.insert(updater))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cst_string_literal(
|
||||
property: &jsonc_parser::cst::CstObjectProp,
|
||||
) -> Option<String> {
|
||||
// TODO(nathanwhit): ensure this unwrap is safe
|
||||
let value = property.value().unwrap();
|
||||
let Some(string) = value.as_string_lit() else {
|
||||
log::warn!("malformed entry");
|
||||
return None;
|
||||
};
|
||||
let Ok(string_value) = string.decoded_value() else {
|
||||
log::warn!("malformed string: {string:?}");
|
||||
return None;
|
||||
};
|
||||
Some(string_value)
|
||||
}
|
||||
|
||||
fn parse_req_reference(
|
||||
input: &str,
|
||||
kind: DepKind,
|
||||
) -> Result<
|
||||
PackageReqReference,
|
||||
deno_semver::package::PackageReqReferenceParseError,
|
||||
> {
|
||||
Ok(match kind {
|
||||
DepKind::Npm => NpmPackageReqReference::from_str(input)?.into_inner(),
|
||||
DepKind::Jsr => JsrPackageReqReference::from_str(input)?.into_inner(),
|
||||
})
|
||||
}
|
661
cli/tools/registry/pm/outdated.rs
Normal file
661
cli/tools/registry/pm/outdated.rs
Normal file
|
@ -0,0 +1,661 @@
|
|||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use deno_core::error::AnyError;
|
||||
use deno_semver::package::PackageNv;
|
||||
use deno_semver::package::PackageReq;
|
||||
use deno_semver::VersionReq;
|
||||
use deno_terminal::colors;
|
||||
|
||||
use crate::args::CacheSetting;
|
||||
use crate::args::CliOptions;
|
||||
use crate::args::Flags;
|
||||
use crate::args::OutdatedFlags;
|
||||
use crate::factory::CliFactory;
|
||||
use crate::file_fetcher::FileFetcher;
|
||||
use crate::jsr::JsrFetchResolver;
|
||||
use crate::npm::NpmFetchResolver;
|
||||
use crate::tools::registry::pm::deps::DepKind;
|
||||
|
||||
use super::deps::Dep;
|
||||
use super::deps::DepManager;
|
||||
use super::deps::DepManagerArgs;
|
||||
use super::deps::PackageLatestVersion;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct OutdatedPackage {
|
||||
kind: DepKind,
|
||||
latest: String,
|
||||
semver_compatible: String,
|
||||
current: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[allow(clippy::print_stdout)]
|
||||
fn print_outdated_table(packages: &[OutdatedPackage]) {
|
||||
const HEADINGS: &[&str] = &["Package", "Current", "Update", "Latest"];
|
||||
|
||||
let mut longest_package = 0;
|
||||
let mut longest_current = 0;
|
||||
let mut longest_update = 0;
|
||||
let mut longest_latest = 0;
|
||||
|
||||
for package in packages {
|
||||
let name_len = package.kind.scheme().len() + 1 + package.name.len();
|
||||
longest_package = longest_package.max(name_len);
|
||||
longest_current = longest_current.max(package.current.len());
|
||||
longest_update = longest_update.max(package.semver_compatible.len());
|
||||
longest_latest = longest_latest.max(package.latest.len());
|
||||
}
|
||||
|
||||
let package_column_width = longest_package.max(HEADINGS[0].len()) + 2;
|
||||
let current_column_width = longest_current.max(HEADINGS[1].len()) + 2;
|
||||
let update_column_width = longest_update.max(HEADINGS[2].len()) + 2;
|
||||
let latest_column_width = longest_latest.max(HEADINGS[3].len()) + 2;
|
||||
|
||||
let package_fill = "─".repeat(package_column_width);
|
||||
let current_fill = "─".repeat(current_column_width);
|
||||
let update_fill = "─".repeat(update_column_width);
|
||||
let latest_fill = "─".repeat(latest_column_width);
|
||||
|
||||
println!("┌{package_fill}┬{current_fill}┬{update_fill}┬{latest_fill}┐");
|
||||
println!(
|
||||
"│ {}{} │ {}{} │ {}{} │ {}{} │",
|
||||
colors::intense_blue(HEADINGS[0]),
|
||||
" ".repeat(package_column_width - 2 - HEADINGS[0].len()),
|
||||
colors::intense_blue(HEADINGS[1]),
|
||||
" ".repeat(current_column_width - 2 - HEADINGS[1].len()),
|
||||
colors::intense_blue(HEADINGS[2]),
|
||||
" ".repeat(update_column_width - 2 - HEADINGS[2].len()),
|
||||
colors::intense_blue(HEADINGS[3]),
|
||||
" ".repeat(latest_column_width - 2 - HEADINGS[3].len())
|
||||
);
|
||||
for package in packages {
|
||||
println!("├{package_fill}┼{current_fill}┼{update_fill}┼{latest_fill}┤",);
|
||||
|
||||
print!(
|
||||
"│ {:<package_column_width$} ",
|
||||
format!("{}:{}", package.kind.scheme(), package.name),
|
||||
package_column_width = package_column_width - 2
|
||||
);
|
||||
print!(
|
||||
"│ {:<current_column_width$} ",
|
||||
package.current,
|
||||
current_column_width = current_column_width - 2
|
||||
);
|
||||
print!(
|
||||
"│ {:<update_column_width$} ",
|
||||
package.semver_compatible,
|
||||
update_column_width = update_column_width - 2
|
||||
);
|
||||
println!(
|
||||
"│ {:<latest_column_width$} │",
|
||||
package.latest,
|
||||
latest_column_width = latest_column_width - 2
|
||||
);
|
||||
}
|
||||
|
||||
println!("└{package_fill}┴{current_fill}┴{update_fill}┴{latest_fill}┘",);
|
||||
}
|
||||
|
||||
fn print_outdated(
|
||||
deps: &mut DepManager,
|
||||
compatible: bool,
|
||||
) -> Result<(), AnyError> {
|
||||
let mut outdated = Vec::new();
|
||||
let mut seen = std::collections::BTreeSet::new();
|
||||
for (dep_id, resolved, latest_versions) in
|
||||
deps.deps_with_resolved_latest_versions()
|
||||
{
|
||||
let dep = deps.get_dep(dep_id);
|
||||
|
||||
let Some(resolved) = resolved else { continue };
|
||||
|
||||
let latest = {
|
||||
let preferred = if compatible {
|
||||
&latest_versions.semver_compatible
|
||||
} else {
|
||||
&latest_versions.latest
|
||||
};
|
||||
if let Some(v) = preferred {
|
||||
v
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if latest > &resolved
|
||||
&& seen.insert((dep.kind, dep.req.name.clone(), resolved.version.clone()))
|
||||
{
|
||||
outdated.push(OutdatedPackage {
|
||||
kind: dep.kind,
|
||||
name: dep.req.name.clone(),
|
||||
current: resolved.version.to_string(),
|
||||
latest: latest_versions
|
||||
.latest
|
||||
.map(|l| l.version.to_string())
|
||||
.unwrap_or_default(),
|
||||
semver_compatible: latest_versions
|
||||
.semver_compatible
|
||||
.map(|l| l.version.to_string())
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if !outdated.is_empty() {
|
||||
outdated.sort();
|
||||
print_outdated_table(&outdated);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn outdated(
|
||||
flags: Arc<Flags>,
|
||||
update_flags: OutdatedFlags,
|
||||
) -> Result<(), AnyError> {
|
||||
let factory = CliFactory::from_flags(flags.clone());
|
||||
let cli_options = factory.cli_options()?;
|
||||
let workspace = cli_options.workspace();
|
||||
let http_client = factory.http_client_provider();
|
||||
let deps_http_cache = factory.global_http_cache()?;
|
||||
let mut file_fetcher = FileFetcher::new(
|
||||
deps_http_cache.clone(),
|
||||
CacheSetting::RespectHeaders,
|
||||
true,
|
||||
http_client.clone(),
|
||||
Default::default(),
|
||||
None,
|
||||
);
|
||||
file_fetcher.set_download_log_level(log::Level::Trace);
|
||||
let file_fetcher = Arc::new(file_fetcher);
|
||||
let npm_fetch_resolver = Arc::new(NpmFetchResolver::new(
|
||||
file_fetcher.clone(),
|
||||
cli_options.npmrc().clone(),
|
||||
));
|
||||
let jsr_fetch_resolver =
|
||||
Arc::new(JsrFetchResolver::new(file_fetcher.clone()));
|
||||
|
||||
let args = dep_manager_args(
|
||||
&factory,
|
||||
cli_options,
|
||||
npm_fetch_resolver.clone(),
|
||||
jsr_fetch_resolver.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let filter_set = filter::FilterSet::from_filter_strings(
|
||||
update_flags.filters.iter().map(|s| s.as_str()),
|
||||
)?;
|
||||
|
||||
let filter_fn = |alias: Option<&str>, req: &PackageReq, _: DepKind| {
|
||||
if filter_set.is_empty() {
|
||||
return true;
|
||||
}
|
||||
let name = alias.unwrap_or(&req.name);
|
||||
filter_set.matches(name)
|
||||
};
|
||||
let mut deps = if update_flags.recursive {
|
||||
super::deps::DepManager::from_workspace(workspace, filter_fn, args)?
|
||||
} else {
|
||||
super::deps::DepManager::from_workspace_dir(
|
||||
&cli_options.start_dir,
|
||||
filter_fn,
|
||||
args,
|
||||
)?
|
||||
};
|
||||
|
||||
deps.resolve_versions().await?;
|
||||
|
||||
match update_flags.kind {
|
||||
crate::args::OutdatedKind::Update { latest } => {
|
||||
update(deps, latest, &filter_set, flags).await?;
|
||||
}
|
||||
crate::args::OutdatedKind::PrintOutdated { compatible } => {
|
||||
print_outdated(&mut deps, compatible)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn choose_new_version_req(
|
||||
dep: &Dep,
|
||||
resolved: Option<&PackageNv>,
|
||||
latest_versions: &PackageLatestVersion,
|
||||
update_to_latest: bool,
|
||||
filter_set: &filter::FilterSet,
|
||||
) -> Option<VersionReq> {
|
||||
let explicit_version_req = filter_set
|
||||
.matching_filter(dep.alias.as_deref().unwrap_or(&dep.req.name))
|
||||
.version_spec()
|
||||
.cloned();
|
||||
|
||||
if let Some(version_req) = explicit_version_req {
|
||||
if let Some(resolved) = resolved {
|
||||
// todo(nathanwhit): handle tag
|
||||
if version_req.tag().is_none() && version_req.matches(&resolved.version) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(version_req)
|
||||
} else {
|
||||
let preferred = if update_to_latest {
|
||||
latest_versions.latest.as_ref()?
|
||||
} else {
|
||||
latest_versions.semver_compatible.as_ref()?
|
||||
};
|
||||
if preferred.version <= resolved?.version {
|
||||
return None;
|
||||
}
|
||||
Some(
|
||||
VersionReq::parse_from_specifier(
|
||||
format!("^{}", preferred.version).as_str(),
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async fn update(
|
||||
mut deps: DepManager,
|
||||
update_to_latest: bool,
|
||||
filter_set: &filter::FilterSet,
|
||||
flags: Arc<Flags>,
|
||||
) -> Result<(), AnyError> {
|
||||
let mut updated = Vec::new();
|
||||
|
||||
for (dep_id, resolved, latest_versions) in deps
|
||||
.deps_with_resolved_latest_versions()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
let dep = deps.get_dep(dep_id);
|
||||
let new_version_req = choose_new_version_req(
|
||||
dep,
|
||||
resolved.as_ref(),
|
||||
&latest_versions,
|
||||
update_to_latest,
|
||||
filter_set,
|
||||
);
|
||||
let Some(new_version_req) = new_version_req else {
|
||||
continue;
|
||||
};
|
||||
|
||||
updated.push((
|
||||
dep_id,
|
||||
format!("{}:{}", dep.kind.scheme(), dep.req.name),
|
||||
deps.resolved_version(dep.id).cloned(),
|
||||
new_version_req.clone(),
|
||||
));
|
||||
|
||||
deps.update_dep(dep_id, new_version_req);
|
||||
}
|
||||
|
||||
deps.commit_changes()?;
|
||||
|
||||
if !updated.is_empty() {
|
||||
let factory = super::npm_install_after_modification(
|
||||
flags.clone(),
|
||||
Some(deps.jsr_fetch_resolver.clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut updated_to_versions = HashSet::new();
|
||||
let cli_options = factory.cli_options()?;
|
||||
let args = dep_manager_args(
|
||||
&factory,
|
||||
cli_options,
|
||||
deps.npm_fetch_resolver.clone(),
|
||||
deps.jsr_fetch_resolver.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut deps = deps.reloaded_after_modification(args);
|
||||
deps.resolve_current_versions().await?;
|
||||
for (dep_id, package_name, maybe_current_version, new_version_req) in
|
||||
updated
|
||||
{
|
||||
if let Some(nv) = deps.resolved_version(dep_id) {
|
||||
updated_to_versions.insert((
|
||||
package_name,
|
||||
maybe_current_version,
|
||||
nv.version.clone(),
|
||||
));
|
||||
} else {
|
||||
log::warn!(
|
||||
"Failed to resolve version for new version requirement: {} -> {}",
|
||||
package_name,
|
||||
new_version_req
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Updated {} dependenc{}:",
|
||||
updated_to_versions.len(),
|
||||
if updated_to_versions.len() == 1 {
|
||||
"y"
|
||||
} else {
|
||||
"ies"
|
||||
}
|
||||
);
|
||||
let mut updated_to_versions =
|
||||
updated_to_versions.into_iter().collect::<Vec<_>>();
|
||||
updated_to_versions.sort_by(|(k, _, _), (k2, _, _)| k.cmp(k2));
|
||||
let max_name = updated_to_versions
|
||||
.iter()
|
||||
.map(|(name, _, _)| name.len())
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
let max_old = updated_to_versions
|
||||
.iter()
|
||||
.map(|(_, maybe_current, _)| {
|
||||
maybe_current
|
||||
.as_ref()
|
||||
.map(|v| v.version.to_string().len())
|
||||
.unwrap_or(0)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
let max_new = updated_to_versions
|
||||
.iter()
|
||||
.map(|(_, _, new_version)| new_version.to_string().len())
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
|
||||
for (package_name, maybe_current_version, new_version) in
|
||||
updated_to_versions
|
||||
{
|
||||
let current_version = if let Some(current_version) = maybe_current_version
|
||||
{
|
||||
current_version.version.to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
log::info!(
|
||||
" - {}{} {}{} -> {}{}",
|
||||
format!(
|
||||
"{}{}",
|
||||
colors::gray(package_name[0..4].to_string()),
|
||||
package_name[4..].to_string()
|
||||
),
|
||||
" ".repeat(max_name - package_name.len()),
|
||||
" ".repeat(max_old - current_version.len()),
|
||||
colors::gray(¤t_version),
|
||||
" ".repeat(max_new - new_version.to_string().len()),
|
||||
colors::green(&new_version),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log::info!(
|
||||
"All {}dependencies are up to date.",
|
||||
if filter_set.is_empty() {
|
||||
""
|
||||
} else {
|
||||
"matching "
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn dep_manager_args(
|
||||
factory: &CliFactory,
|
||||
cli_options: &CliOptions,
|
||||
npm_fetch_resolver: Arc<NpmFetchResolver>,
|
||||
jsr_fetch_resolver: Arc<JsrFetchResolver>,
|
||||
) -> Result<DepManagerArgs, AnyError> {
|
||||
Ok(DepManagerArgs {
|
||||
module_load_preparer: factory.module_load_preparer().await?.clone(),
|
||||
jsr_fetch_resolver,
|
||||
npm_fetch_resolver,
|
||||
npm_resolver: factory.npm_resolver().await?.clone(),
|
||||
permissions_container: factory.root_permissions_container()?.clone(),
|
||||
main_module_graph_container: factory
|
||||
.main_module_graph_container()
|
||||
.await?
|
||||
.clone(),
|
||||
lockfile: cli_options.maybe_lockfile().cloned(),
|
||||
})
|
||||
}
|
||||
|
||||
mod filter {
|
||||
use deno_core::anyhow::anyhow;
|
||||
use deno_core::anyhow::Context;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_semver::VersionReq;
|
||||
|
||||
enum FilterKind {
|
||||
Exclude,
|
||||
Include,
|
||||
}
|
||||
pub struct Filter {
|
||||
kind: FilterKind,
|
||||
regex: regex::Regex,
|
||||
version_spec: Option<VersionReq>,
|
||||
}
|
||||
|
||||
fn pattern_to_regex(pattern: &str) -> Result<regex::Regex, AnyError> {
|
||||
let escaped = regex::escape(pattern);
|
||||
let unescaped_star = escaped.replace(r"\*", ".*");
|
||||
Ok(regex::Regex::new(&format!("^{}$", unescaped_star))?)
|
||||
}
|
||||
|
||||
impl Filter {
|
||||
pub fn version_spec(&self) -> Option<&VersionReq> {
|
||||
self.version_spec.as_ref()
|
||||
}
|
||||
pub fn from_str(input: &str) -> Result<Self, AnyError> {
|
||||
let (kind, first_idx) = if input.starts_with('!') {
|
||||
(FilterKind::Exclude, 1)
|
||||
} else {
|
||||
(FilterKind::Include, 0)
|
||||
};
|
||||
let s = &input[first_idx..];
|
||||
let (pattern, version_spec) =
|
||||
if let Some(scope_name) = s.strip_prefix('@') {
|
||||
if let Some(idx) = scope_name.find('@') {
|
||||
let (pattern, version_spec) = s.split_at(idx + 1);
|
||||
(
|
||||
pattern,
|
||||
Some(
|
||||
VersionReq::parse_from_specifier(
|
||||
version_spec.trim_start_matches('@'),
|
||||
)
|
||||
.with_context(|| format!("Invalid filter \"{input}\""))?,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
(s, None)
|
||||
}
|
||||
} else {
|
||||
let mut parts = s.split('@');
|
||||
let Some(pattern) = parts.next() else {
|
||||
return Err(anyhow!("Invalid filter \"{input}\""));
|
||||
};
|
||||
(
|
||||
pattern,
|
||||
parts
|
||||
.next()
|
||||
.map(VersionReq::parse_from_specifier)
|
||||
.transpose()
|
||||
.with_context(|| format!("Invalid filter \"{input}\""))?,
|
||||
)
|
||||
};
|
||||
|
||||
Ok(Filter {
|
||||
kind,
|
||||
regex: pattern_to_regex(pattern)
|
||||
.with_context(|| format!("Invalid filter \"{input}\""))?,
|
||||
version_spec,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn matches(&self, name: &str) -> bool {
|
||||
self.regex.is_match(name)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FilterSet {
|
||||
filters: Vec<Filter>,
|
||||
has_exclude: bool,
|
||||
has_include: bool,
|
||||
}
|
||||
impl FilterSet {
|
||||
pub fn from_filter_strings<'a>(
|
||||
filter_strings: impl IntoIterator<Item = &'a str>,
|
||||
) -> Result<Self, AnyError> {
|
||||
let filters = filter_strings
|
||||
.into_iter()
|
||||
.map(Filter::from_str)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let has_exclude = filters
|
||||
.iter()
|
||||
.any(|f| matches!(f.kind, FilterKind::Exclude));
|
||||
let has_include = filters
|
||||
.iter()
|
||||
.any(|f| matches!(f.kind, FilterKind::Include));
|
||||
Ok(FilterSet {
|
||||
filters,
|
||||
has_exclude,
|
||||
has_include,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.filters.is_empty()
|
||||
}
|
||||
|
||||
pub fn matches(&self, name: &str) -> bool {
|
||||
self.matching_filter(name).is_included()
|
||||
}
|
||||
|
||||
pub fn matching_filter(&self, name: &str) -> MatchResult<'_> {
|
||||
if self.filters.is_empty() {
|
||||
return MatchResult::Included;
|
||||
}
|
||||
let mut matched = None;
|
||||
for filter in &self.filters {
|
||||
match filter.kind {
|
||||
FilterKind::Include => {
|
||||
if matched.is_none() && filter.matches(name) {
|
||||
matched = Some(filter);
|
||||
}
|
||||
}
|
||||
FilterKind::Exclude => {
|
||||
if filter.matches(name) {
|
||||
return MatchResult::Excluded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(filter) = matched {
|
||||
MatchResult::Matches(filter)
|
||||
} else if self.has_exclude && !self.has_include {
|
||||
MatchResult::Included
|
||||
} else {
|
||||
MatchResult::Excluded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum MatchResult<'a> {
|
||||
Matches(&'a Filter),
|
||||
Included,
|
||||
Excluded,
|
||||
}
|
||||
|
||||
impl MatchResult<'_> {
|
||||
pub fn version_spec(&self) -> Option<&VersionReq> {
|
||||
match self {
|
||||
MatchResult::Matches(filter) => filter.version_spec(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn is_included(&self) -> bool {
|
||||
matches!(self, MatchResult::Included | MatchResult::Matches(_))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
fn matches_filters<'a, 'b>(
|
||||
filters: impl IntoIterator<Item = &'a str>,
|
||||
name: &str,
|
||||
) -> bool {
|
||||
let filters = super::FilterSet::from_filter_strings(filters).unwrap();
|
||||
filters.matches(name)
|
||||
}
|
||||
|
||||
fn version_spec(s: &str) -> deno_semver::VersionReq {
|
||||
deno_semver::VersionReq::parse_from_specifier(s).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_glob() {
|
||||
assert!(matches_filters(["foo*"], "foo"));
|
||||
assert!(matches_filters(["foo*"], "foobar"));
|
||||
assert!(!matches_filters(["foo*"], "barfoo"));
|
||||
|
||||
assert!(matches_filters(["*foo"], "foo"));
|
||||
assert!(matches_filters(["*foo"], "barfoo"));
|
||||
assert!(!matches_filters(["*foo"], "foobar"));
|
||||
|
||||
assert!(matches_filters(["@scope/foo*"], "@scope/foobar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_glob_with_version() {
|
||||
assert!(matches_filters(["foo*@1"], "foo",));
|
||||
assert!(matches_filters(["foo*@1"], "foobar",));
|
||||
assert!(matches_filters(["foo*@1"], "foo-bar",));
|
||||
assert!(!matches_filters(["foo*@1"], "barfoo",));
|
||||
assert!(matches_filters(["@scope/*@1"], "@scope/foo"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn glob_exclude() {
|
||||
assert!(!matches_filters(["!foo*"], "foo"));
|
||||
assert!(!matches_filters(["!foo*"], "foobar"));
|
||||
assert!(matches_filters(["!foo*"], "barfoo"));
|
||||
|
||||
assert!(!matches_filters(["!*foo"], "foo"));
|
||||
assert!(!matches_filters(["!*foo"], "barfoo"));
|
||||
assert!(matches_filters(["!*foo"], "foobar"));
|
||||
|
||||
assert!(!matches_filters(["!@scope/foo*"], "@scope/foobar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_globs() {
|
||||
assert!(matches_filters(["foo*", "bar*"], "foo"));
|
||||
assert!(matches_filters(["foo*", "bar*"], "bar"));
|
||||
assert!(!matches_filters(["foo*", "bar*"], "baz"));
|
||||
|
||||
assert!(matches_filters(["foo*", "!bar*"], "foo"));
|
||||
assert!(!matches_filters(["foo*", "!bar*"], "bar"));
|
||||
assert!(matches_filters(["foo*", "!bar*"], "foobar"));
|
||||
assert!(!matches_filters(["foo*", "!*bar"], "foobar"));
|
||||
assert!(!matches_filters(["foo*", "!*bar"], "baz"));
|
||||
|
||||
let filters =
|
||||
super::FilterSet::from_filter_strings(["foo*@1", "bar*@2"]).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
filters.matching_filter("foo").version_spec().cloned(),
|
||||
Some(version_spec("1"))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
filters.matching_filter("bar").version_spec().cloned(),
|
||||
Some(version_spec("2"))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
4
tests/registry/jsr/@denotest/add/0.2.1/mod.ts
Normal file
4
tests/registry/jsr/@denotest/add/0.2.1/mod.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
// This is renamed to `add()` in 1.0.0.
|
||||
export function sum(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
8
tests/registry/jsr/@denotest/add/0.2.1_meta.json
Normal file
8
tests/registry/jsr/@denotest/add/0.2.1_meta.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"exports": {
|
||||
".": "./mod.ts"
|
||||
},
|
||||
"moduleGraph1": {
|
||||
"/mod.ts": {}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
"yanked": true
|
||||
},
|
||||
"1.0.0": {},
|
||||
"0.2.0": {}
|
||||
"0.2.0": {},
|
||||
"0.2.1": {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from "jsr:@denotest/add@1";
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"a": 1
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from "jsr:@denotest/subtract@1";
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"exports": {
|
||||
"./add": "./add.ts",
|
||||
"./subtract": "./subtract.ts",
|
||||
"./data-json": "./data.json"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from "jsr:@denotest/add@1";
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"a": 1
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from "jsr:@denotest/subtract@1";
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"exports": {
|
||||
"./add": "./add.ts",
|
||||
"./subtract": "./subtract.ts",
|
||||
"./data-json": "./data.json"
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"versions": {
|
||||
"1.0.0": {}
|
||||
"1.0.0": {},
|
||||
"0.5.0": {},
|
||||
"0.2.0": {}
|
||||
}
|
||||
}
|
||||
|
|
3
tests/registry/jsr/@denotest/subtract/0.2.0/mod.ts
Normal file
3
tests/registry/jsr/@denotest/subtract/0.2.0/mod.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export function sub(a: number, b: number): number {
|
||||
return a - b;
|
||||
}
|
8
tests/registry/jsr/@denotest/subtract/0.2.0_meta.json
Normal file
8
tests/registry/jsr/@denotest/subtract/0.2.0_meta.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"exports": {
|
||||
".": "./mod.ts"
|
||||
},
|
||||
"moduleGraph1": {
|
||||
"/mod.ts": {}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"latest": "1.0.0",
|
||||
"versions": {
|
||||
"1.0.0": {}
|
||||
"1.0.0": {},
|
||||
"0.2.0": {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "@denotest/has-patch-versions",
|
||||
"version": "0.1.0"
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "@denotest/has-patch-versions",
|
||||
"version": "0.1.1"
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "@denotest/has-patch-versions",
|
||||
"version": "0.2.0"
|
||||
}
|
101
tests/specs/update/deno_json/__test__.jsonc
Normal file
101
tests/specs/update/deno_json/__test__.jsonc
Normal file
|
@ -0,0 +1,101 @@
|
|||
{
|
||||
"tempDir": true,
|
||||
"tests": {
|
||||
// just to make sure install doesn't change the lockfile
|
||||
"sanity_lockfile_up_to_date": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"eval",
|
||||
"const now = Deno.readTextFileSync('./deno.lock'); console.log(now.trim());"
|
||||
],
|
||||
"output": "deno.lock.orig.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"print_outdated": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "outdated",
|
||||
"output": "outdated.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"print_outdated_compatible": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "outdated --compatible",
|
||||
"output": "outdated_compatible.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"update_compatible": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "outdated --update",
|
||||
"output": "./update_compatible/update.out"
|
||||
},
|
||||
{
|
||||
"args": "-A print_file.ts ./deno.json",
|
||||
"output": "./update_compatible/deno.json.out"
|
||||
},
|
||||
{
|
||||
"args": "-A print_file.ts ./deno.lock",
|
||||
"output": "./update_compatible/deno.lock.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"update_latest": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "outdated --update --latest",
|
||||
"output": "update_latest/update.out"
|
||||
},
|
||||
{
|
||||
"args": "-A print_file.ts ./deno.json",
|
||||
"output": "./update_latest/deno.json.out"
|
||||
},
|
||||
{
|
||||
"args": "-A print_file.ts ./deno.lock",
|
||||
"output": "./update_latest/deno.lock.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"update_filtered": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "outdated --update --latest @denotest/add @denotest/b* !@denotest/breaking* @denotest/with-subpath@0.5.0",
|
||||
"output": "filtered/update.out"
|
||||
},
|
||||
{
|
||||
"args": "-A print_file.ts ./deno.json",
|
||||
"output": "filtered/deno.json.out"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
19
tests/specs/update/deno_json/deno.json
Normal file
19
tests/specs/update/deno_json/deno.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"imports": {
|
||||
"@denotest/add": "jsr:@denotest/add@^0.2.0",
|
||||
"@denotest/add/": "jsr:/@denotest/add@^0.2.0/",
|
||||
"@denotest/subtract": "jsr:@denotest/subtract@^0.2.0",
|
||||
"@denotest/with-subpath": "jsr:@denotest/multiple-exports@0.2.0/data-json",
|
||||
"@denotest/breaking-change-between-versions": "npm:@denotest/breaking-change-between-versions@1.0.0",
|
||||
"@denotest/bin": "npm:@denotest/bin@0.6.0",
|
||||
"@denotest/has-patch-versions": "npm:@denotest/has-patch-versions@^0.1.0"
|
||||
},
|
||||
"scopes": {
|
||||
"/foo/": {
|
||||
"@denotest/add": "jsr:@denotest/add@^0.2.0",
|
||||
"@denotest/add/": "jsr:/@denotest/add@^0.2.0/",
|
||||
"@denotest/subtract": "jsr:@denotest/subtract@^0.2.0",
|
||||
"@denotest/with-subpath": "jsr:@denotest/multiple-exports@0.2.0/data-json"
|
||||
}
|
||||
}
|
||||
}
|
43
tests/specs/update/deno_json/deno.lock
Normal file
43
tests/specs/update/deno_json/deno.lock
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"jsr:@denotest/add@0.2": "0.2.0",
|
||||
"jsr:@denotest/multiple-exports@0.2.0": "0.2.0",
|
||||
"jsr:@denotest/subtract@0.2": "0.2.0",
|
||||
"npm:@denotest/bin@0.6.0": "0.6.0",
|
||||
"npm:@denotest/breaking-change-between-versions@1.0.0": "1.0.0",
|
||||
"npm:@denotest/has-patch-versions@0.1": "0.1.0"
|
||||
},
|
||||
"jsr": {
|
||||
"@denotest/add@0.2.0": {
|
||||
"integrity": "a9076d30ecb42b2fc6dd95e7055fbf4e6358b53f550741bd7f60089d19f68848"
|
||||
},
|
||||
"@denotest/multiple-exports@0.2.0": {
|
||||
"integrity": "efe9748a0c0939c7ac245fee04acc0c42bd7a61874ff71a360c4543e4f5f6b36"
|
||||
},
|
||||
"@denotest/subtract@0.2.0": {
|
||||
"integrity": "c9650fc559ab2430effc0c7fb1540e3aa89888fbdd926335ccfdeac57eb3a64d"
|
||||
}
|
||||
},
|
||||
"npm": {
|
||||
"@denotest/bin@0.6.0": {
|
||||
"integrity": "sha512-vCNpxFgQN4fw4ZOp63nbTX1ilcDqNpvXCvYyC8nmfxQQAezsEt095I/YXwMIoMGzWtjCvlMf9kVEYfLuT5oEGQ=="
|
||||
},
|
||||
"@denotest/breaking-change-between-versions@1.0.0": {
|
||||
"integrity": "sha512-bzMGYx+DxxPlI74n/VsDAN7Db1BY7Sz2XqxXruMo9dEznsBZu7Ez3i8YQ8n0leTxAiiMk1RCG4zQHPG1aj3xRw=="
|
||||
},
|
||||
"@denotest/has-patch-versions@0.1.0": {
|
||||
"integrity": "sha512-H/MBo0jKDdMsX4AAGEGQbZj70nfNe3oUNZXbohYHhqf9EfpLnXp/7FC29ZdfV4+p6VjEcOGdCtXc6rilE6iYpg=="
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@denotest/add@0.2",
|
||||
"jsr:@denotest/multiple-exports@0.2.0",
|
||||
"jsr:@denotest/subtract@0.2",
|
||||
"npm:@denotest/bin@0.6.0",
|
||||
"npm:@denotest/breaking-change-between-versions@1.0.0",
|
||||
"npm:@denotest/has-patch-versions@0.1"
|
||||
]
|
||||
}
|
||||
}
|
43
tests/specs/update/deno_json/deno.lock.orig.out
Normal file
43
tests/specs/update/deno_json/deno.lock.orig.out
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"jsr:@denotest/add@0.2": "0.2.0",
|
||||
"jsr:@denotest/multiple-exports@0.2.0": "0.2.0",
|
||||
"jsr:@denotest/subtract@0.2": "0.2.0",
|
||||
"npm:@denotest/bin@0.6.0": "0.6.0",
|
||||
"npm:@denotest/breaking-change-between-versions@1.0.0": "1.0.0",
|
||||
"npm:@denotest/has-patch-versions@0.1": "0.1.0"
|
||||
},
|
||||
"jsr": {
|
||||
"@denotest/add@0.2.0": {
|
||||
"integrity": "a9076d30ecb42b2fc6dd95e7055fbf4e6358b53f550741bd7f60089d19f68848"
|
||||
},
|
||||
"@denotest/multiple-exports@0.2.0": {
|
||||
"integrity": "efe9748a0c0939c7ac245fee04acc0c42bd7a61874ff71a360c4543e4f5f6b36"
|
||||
},
|
||||
"@denotest/subtract@0.2.0": {
|
||||
"integrity": "c9650fc559ab2430effc0c7fb1540e3aa89888fbdd926335ccfdeac57eb3a64d"
|
||||
}
|
||||
},
|
||||
"npm": {
|
||||
"@denotest/bin@0.6.0": {
|
||||
"integrity": "sha512-vCNpxFgQN4fw4ZOp63nbTX1ilcDqNpvXCvYyC8nmfxQQAezsEt095I/YXwMIoMGzWtjCvlMf9kVEYfLuT5oEGQ=="
|
||||
},
|
||||
"@denotest/breaking-change-between-versions@1.0.0": {
|
||||
"integrity": "sha512-bzMGYx+DxxPlI74n/VsDAN7Db1BY7Sz2XqxXruMo9dEznsBZu7Ez3i8YQ8n0leTxAiiMk1RCG4zQHPG1aj3xRw=="
|
||||
},
|
||||
"@denotest/has-patch-versions@0.1.0": {
|
||||
"integrity": "sha512-H/MBo0jKDdMsX4AAGEGQbZj70nfNe3oUNZXbohYHhqf9EfpLnXp/7FC29ZdfV4+p6VjEcOGdCtXc6rilE6iYpg=="
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@denotest/add@0.2",
|
||||
"jsr:@denotest/multiple-exports@0.2.0",
|
||||
"jsr:@denotest/subtract@0.2",
|
||||
"npm:@denotest/bin@0.6.0",
|
||||
"npm:@denotest/breaking-change-between-versions@1.0.0",
|
||||
"npm:@denotest/has-patch-versions@0.1"
|
||||
]
|
||||
}
|
||||
}
|
19
tests/specs/update/deno_json/filtered/deno.json.out
Normal file
19
tests/specs/update/deno_json/filtered/deno.json.out
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"imports": {
|
||||
"@denotest/add": "jsr:@denotest/add@^1.0.0",
|
||||
"@denotest/add/": "jsr:/@denotest/add@^1.0.0/",
|
||||
"@denotest/subtract": "jsr:@denotest/subtract@^0.2.0",
|
||||
"@denotest/with-subpath": "jsr:@denotest/multiple-exports@0.5.0/data-json",
|
||||
"@denotest/breaking-change-between-versions": "npm:@denotest/breaking-change-between-versions@1.0.0",
|
||||
"@denotest/bin": "npm:@denotest/bin@^1.0.0",
|
||||
"@denotest/has-patch-versions": "npm:@denotest/has-patch-versions@^0.1.0"
|
||||
},
|
||||
"scopes": {
|
||||
"/foo/": {
|
||||
"@denotest/add": "jsr:@denotest/add@^1.0.0",
|
||||
"@denotest/add/": "jsr:/@denotest/add@^1.0.0/",
|
||||
"@denotest/subtract": "jsr:@denotest/subtract@^0.2.0",
|
||||
"@denotest/with-subpath": "jsr:@denotest/multiple-exports@0.5.0/data-json"
|
||||
}
|
||||
}
|
||||
}
|
10
tests/specs/update/deno_json/filtered/update.out
Normal file
10
tests/specs/update/deno_json/filtered/update.out
Normal file
|
@ -0,0 +1,10 @@
|
|||
[UNORDERED_START]
|
||||
Download http://localhost:4260/@denotest/bin/1.0.0.tgz
|
||||
Download http://127.0.0.1:4250/@denotest/multiple-exports/0.5.0_meta.json
|
||||
Download http://127.0.0.1:4250/@denotest/multiple-exports/0.5.0/data.json
|
||||
Download http://127.0.0.1:4250/@denotest/add/1.0.0/mod.ts
|
||||
[UNORDERED_END]
|
||||
Updated 3 dependencies:
|
||||
- jsr:@denotest/add 0.2.0 -> 1.0.0
|
||||
- jsr:@denotest/multiple-exports 0.2.0 -> 0.5.0
|
||||
- npm:@denotest/bin 0.6.0 -> 1.0.0
|
15
tests/specs/update/deno_json/outdated.out
Normal file
15
tests/specs/update/deno_json/outdated.out
Normal file
|
@ -0,0 +1,15 @@
|
|||
┌────────────────────────────────────────────────┬─────────┬────────┬────────┐
|
||||
│ Package │ Current │ Update │ Latest │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ jsr:@denotest/multiple-exports │ 0.2.0 │ 0.2.0 │ 1.0.0 │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ jsr:@denotest/subtract │ 0.2.0 │ 0.2.0 │ 1.0.0 │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ jsr:@denotest/add │ 0.2.0 │ 0.2.1 │ 1.0.0 │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ npm:@denotest/has-patch-versions │ 0.1.0 │ 0.1.1 │ 0.2.0 │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ npm:@denotest/bin │ 0.6.0 │ 0.6.0 │ 1.0.0 │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ npm:@denotest/breaking-change-between-versions │ 1.0.0 │ 1.0.0 │ 2.0.0 │
|
||||
└────────────────────────────────────────────────┴─────────┴────────┴────────┘
|
7
tests/specs/update/deno_json/outdated_compatible.out
Normal file
7
tests/specs/update/deno_json/outdated_compatible.out
Normal file
|
@ -0,0 +1,7 @@
|
|||
┌──────────────────────────────────┬─────────┬────────┬────────┐
|
||||
│ Package │ Current │ Update │ Latest │
|
||||
├──────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ jsr:@denotest/add │ 0.2.0 │ 0.2.1 │ 1.0.0 │
|
||||
├──────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ npm:@denotest/has-patch-versions │ 0.1.0 │ 0.1.1 │ 0.2.0 │
|
||||
└──────────────────────────────────┴─────────┴────────┴────────┘
|
2
tests/specs/update/deno_json/print_file.ts
Normal file
2
tests/specs/update/deno_json/print_file.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
const file = Deno.args[0];
|
||||
console.log(Deno.readTextFileSync(file).trim());
|
19
tests/specs/update/deno_json/update_compatible/deno.json.out
Normal file
19
tests/specs/update/deno_json/update_compatible/deno.json.out
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"imports": {
|
||||
"@denotest/add": "jsr:@denotest/add@^0.2.1",
|
||||
"@denotest/add/": "jsr:/@denotest/add@^0.2.1/",
|
||||
"@denotest/subtract": "jsr:@denotest/subtract@^0.2.0",
|
||||
"@denotest/with-subpath": "jsr:@denotest/multiple-exports@0.2.0/data-json",
|
||||
"@denotest/breaking-change-between-versions": "npm:@denotest/breaking-change-between-versions@1.0.0",
|
||||
"@denotest/bin": "npm:@denotest/bin@0.6.0",
|
||||
"@denotest/has-patch-versions": "npm:@denotest/has-patch-versions@^0.1.1"
|
||||
},
|
||||
"scopes": {
|
||||
"/foo/": {
|
||||
"@denotest/add": "jsr:@denotest/add@^0.2.1",
|
||||
"@denotest/add/": "jsr:/@denotest/add@^0.2.1/",
|
||||
"@denotest/subtract": "jsr:@denotest/subtract@^0.2.0",
|
||||
"@denotest/with-subpath": "jsr:@denotest/multiple-exports@0.2.0/data-json"
|
||||
}
|
||||
}
|
||||
}
|
43
tests/specs/update/deno_json/update_compatible/deno.lock.out
Normal file
43
tests/specs/update/deno_json/update_compatible/deno.lock.out
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"jsr:@denotest/add@~0.2.1": "0.2.1",
|
||||
"jsr:@denotest/multiple-exports@0.2.0": "0.2.0",
|
||||
"jsr:@denotest/subtract@0.2": "0.2.0",
|
||||
"npm:@denotest/bin@0.6.0": "0.6.0",
|
||||
"npm:@denotest/breaking-change-between-versions@1.0.0": "1.0.0",
|
||||
"npm:@denotest/has-patch-versions@~0.1.1": "0.1.1"
|
||||
},
|
||||
"jsr": {
|
||||
"@denotest/add@0.2.1": {
|
||||
"integrity": "a9076d30ecb42b2fc6dd95e7055fbf4e6358b53f550741bd7f60089d19f68848"
|
||||
},
|
||||
"@denotest/multiple-exports@0.2.0": {
|
||||
"integrity": "efe9748a0c0939c7ac245fee04acc0c42bd7a61874ff71a360c4543e4f5f6b36"
|
||||
},
|
||||
"@denotest/subtract@0.2.0": {
|
||||
"integrity": "c9650fc559ab2430effc0c7fb1540e3aa89888fbdd926335ccfdeac57eb3a64d"
|
||||
}
|
||||
},
|
||||
"npm": {
|
||||
"@denotest/bin@0.6.0": {
|
||||
"integrity": "sha512-vCNpxFgQN4fw4ZOp63nbTX1ilcDqNpvXCvYyC8nmfxQQAezsEt095I/YXwMIoMGzWtjCvlMf9kVEYfLuT5oEGQ=="
|
||||
},
|
||||
"@denotest/breaking-change-between-versions@1.0.0": {
|
||||
"integrity": "sha512-bzMGYx+DxxPlI74n/VsDAN7Db1BY7Sz2XqxXruMo9dEznsBZu7Ez3i8YQ8n0leTxAiiMk1RCG4zQHPG1aj3xRw=="
|
||||
},
|
||||
"@denotest/has-patch-versions@0.1.1": {
|
||||
"integrity": "sha512-slUqYhu6DrPiSdNzmW5aMdW2/osIYnDP0yY3CwgLzAiyK0/cwb0epSpTSyZEmTKXA3rezxxC7ASSsnD34uH1/w=="
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@denotest/add@~0.2.1",
|
||||
"jsr:@denotest/multiple-exports@0.2.0",
|
||||
"jsr:@denotest/subtract@0.2",
|
||||
"npm:@denotest/bin@0.6.0",
|
||||
"npm:@denotest/breaking-change-between-versions@1.0.0",
|
||||
"npm:@denotest/has-patch-versions@~0.1.1"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
[UNORDERED_START]
|
||||
Download http://127.0.0.1:4250/@denotest/add/0.2.1/mod.ts
|
||||
Download http://localhost:4260/@denotest/has-patch-versions/0.1.1.tgz
|
||||
[UNORDERED_END]
|
||||
Updated 2 dependencies:
|
||||
- jsr:@denotest/add 0.2.0 -> 0.2.1
|
||||
- npm:@denotest/has-patch-versions 0.1.0 -> 0.1.1
|
19
tests/specs/update/deno_json/update_latest/deno.json.out
Normal file
19
tests/specs/update/deno_json/update_latest/deno.json.out
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"imports": {
|
||||
"@denotest/add": "jsr:@denotest/add@^1.0.0",
|
||||
"@denotest/add/": "jsr:/@denotest/add@^1.0.0/",
|
||||
"@denotest/subtract": "jsr:@denotest/subtract@^1.0.0",
|
||||
"@denotest/with-subpath": "jsr:@denotest/multiple-exports@^1.0.0/data-json",
|
||||
"@denotest/breaking-change-between-versions": "npm:@denotest/breaking-change-between-versions@^2.0.0",
|
||||
"@denotest/bin": "npm:@denotest/bin@^1.0.0",
|
||||
"@denotest/has-patch-versions": "npm:@denotest/has-patch-versions@^0.2.0"
|
||||
},
|
||||
"scopes": {
|
||||
"/foo/": {
|
||||
"@denotest/add": "jsr:@denotest/add@^1.0.0",
|
||||
"@denotest/add/": "jsr:/@denotest/add@^1.0.0/",
|
||||
"@denotest/subtract": "jsr:@denotest/subtract@^1.0.0",
|
||||
"@denotest/with-subpath": "jsr:@denotest/multiple-exports@^1.0.0/data-json"
|
||||
}
|
||||
}
|
||||
}
|
43
tests/specs/update/deno_json/update_latest/deno.lock.out
Normal file
43
tests/specs/update/deno_json/update_latest/deno.lock.out
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"jsr:@denotest/add@1": "1.0.0",
|
||||
"jsr:@denotest/multiple-exports@1": "1.0.0",
|
||||
"jsr:@denotest/subtract@1": "1.0.0",
|
||||
"npm:@denotest/bin@1": "1.0.0",
|
||||
"npm:@denotest/breaking-change-between-versions@2": "2.0.0",
|
||||
"npm:@denotest/has-patch-versions@0.2": "0.2.0"
|
||||
},
|
||||
"jsr": {
|
||||
"@denotest/add@1.0.0": {
|
||||
"integrity": "3b2e675c1ad7fba2a45bc251992e01aff08a3c974ac09079b11e6a5b95d4bfcb"
|
||||
},
|
||||
"@denotest/multiple-exports@1.0.0": {
|
||||
"integrity": "efe9748a0c0939c7ac245fee04acc0c42bd7a61874ff71a360c4543e4f5f6b36"
|
||||
},
|
||||
"@denotest/subtract@1.0.0": {
|
||||
"integrity": "e178a7101c073e93d9efa6833d5cbf83bc1bc8d509b7c2a5ecbf74265e917597"
|
||||
}
|
||||
},
|
||||
"npm": {
|
||||
"@denotest/bin@1.0.0": {
|
||||
"integrity": "sha512-ZtrWnYYPIzw4a9H1uNeZRZRWuLCpHZZU/SllIyFLqcTLH/3zdRI8UH4Z1Kf+8N++bWGO3fg8Ev4vvS1LoLlidg=="
|
||||
},
|
||||
"@denotest/breaking-change-between-versions@2.0.0": {
|
||||
"integrity": "sha512-3eQpPhhJYbSHaAmpgyVT8IM3+MkxcAQl90Uw8zmuTiFs64Wt3HGzSz74cwPlvfqqesRktm8fBZMmrtxVo3ENzw=="
|
||||
},
|
||||
"@denotest/has-patch-versions@0.2.0": {
|
||||
"integrity": "sha512-7XIVGrBMyqnts5/wcc7dn7rVl4IWrCiGUT2GLDSLWnogOMIZBapJJLW9n8Leq1bDTJ3U6aDTEFKz9fVSOwZfLQ=="
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@denotest/add@1",
|
||||
"jsr:@denotest/multiple-exports@1",
|
||||
"jsr:@denotest/subtract@1",
|
||||
"npm:@denotest/bin@1",
|
||||
"npm:@denotest/breaking-change-between-versions@2",
|
||||
"npm:@denotest/has-patch-versions@0.2"
|
||||
]
|
||||
}
|
||||
}
|
16
tests/specs/update/deno_json/update_latest/update.out
Normal file
16
tests/specs/update/deno_json/update_latest/update.out
Normal file
|
@ -0,0 +1,16 @@
|
|||
[UNORDERED_START]
|
||||
Download http://127.0.0.1:4250/@denotest/add/1.0.0/mod.ts
|
||||
Download http://127.0.0.1:4250/@denotest/subtract/1.0.0/mod.ts
|
||||
Download http://127.0.0.1:4250/@denotest/multiple-exports/1.0.0_meta.json
|
||||
Download http://127.0.0.1:4250/@denotest/multiple-exports/1.0.0/data.json
|
||||
Download http://localhost:4260/@denotest/has-patch-versions/0.2.0.tgz
|
||||
Download http://localhost:4260/@denotest/bin/1.0.0.tgz
|
||||
Download http://localhost:4260/@denotest/breaking-change-between-versions/2.0.0.tgz
|
||||
[UNORDERED_END]
|
||||
Updated 6 dependencies:
|
||||
- jsr:@denotest/add 0.2.0 -> 1.0.0
|
||||
- jsr:@denotest/multiple-exports 0.2.0 -> 1.0.0
|
||||
- jsr:@denotest/subtract 0.2.0 -> 1.0.0
|
||||
- npm:@denotest/bin 0.6.0 -> 1.0.0
|
||||
- npm:@denotest/breaking-change-between-versions 1.0.0 -> 2.0.0
|
||||
- npm:@denotest/has-patch-versions 0.1.0 -> 0.2.0
|
153
tests/specs/update/mixed_workspace/__test__.jsonc
Normal file
153
tests/specs/update/mixed_workspace/__test__.jsonc
Normal file
|
@ -0,0 +1,153 @@
|
|||
{
|
||||
"tempDir": true,
|
||||
"tests": {
|
||||
// just to make sure install doesn't change the lockfile
|
||||
"sanity_lockfile_up_to_date": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"eval",
|
||||
"const now = Deno.readTextFileSync('./deno.lock'); console.log(now.trim());"
|
||||
],
|
||||
"output": "deno.lock.orig.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"print_outdated_root": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "outdated",
|
||||
"output": "print_outdated/root.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"print_outdated_recursive": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "outdated --recursive",
|
||||
"output": "print_outdated/recursive.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"print_outdated_subdir": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"cwd": "member-a",
|
||||
"args": "outdated",
|
||||
"output": "print_outdated/member_a.out"
|
||||
},
|
||||
{
|
||||
"cwd": "member-b",
|
||||
"args": "outdated",
|
||||
"output": "print_outdated/member_b.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"update_latest_root": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "outdated --update --latest",
|
||||
"output": "update_latest/root/update.out"
|
||||
},
|
||||
{
|
||||
"args": "-A print_file.ts ./deno.json",
|
||||
"output": "./update_latest/root/deno.json.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"update_latest_subdir": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"cwd": "member-a",
|
||||
"args": "outdated --update --latest",
|
||||
"output": "update_latest/subdir/member_a.out"
|
||||
},
|
||||
{
|
||||
"args": "-A print_file.ts ./member-a/deno.json",
|
||||
"output": "update_latest/subdir/member_a_deno.json.out"
|
||||
},
|
||||
{
|
||||
"cwd": "member-b",
|
||||
"args": "outdated --update --latest",
|
||||
"output": "update_latest/subdir/member_b.out"
|
||||
},
|
||||
{
|
||||
"args": "-A print_file.ts ./member-b/package.json",
|
||||
"output": "update_latest/subdir/member_b_package.json.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"update_latest_recursive": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "outdated --update --latest --recursive",
|
||||
"output": "update_latest/recursive/update.out"
|
||||
},
|
||||
{
|
||||
"args": "-A print_file.ts ./deno.json",
|
||||
"output": "update_latest/root/deno.json.out"
|
||||
},
|
||||
{
|
||||
"args": "-A print_file.ts ./member-a/deno.json",
|
||||
"output": "update_latest/subdir/member_a_deno.json.out"
|
||||
},
|
||||
{
|
||||
"args": "-A print_file.ts ./member-b/package.json",
|
||||
"output": "update_latest/subdir/member_b_package.json.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"update_filtered": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "outdated --update --latest --recursive @denotest/add @denotest/sub* !@denotest/breaking* aliased @denotest/with-subpath@0.5.0",
|
||||
"output": "filtered/update.out"
|
||||
},
|
||||
{
|
||||
"args": "-A print_file.ts ./deno.json",
|
||||
"output": "./update_latest/root/deno.json.out"
|
||||
},
|
||||
{
|
||||
"args": "-A print_file.ts ./member-a/deno.json",
|
||||
"output": "./filtered/member_a_deno.json.out"
|
||||
},
|
||||
{
|
||||
"args": "-A print_file.ts ./member-b/package.json",
|
||||
"output": "./filtered/member_b_package.json.out"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
6
tests/specs/update/mixed_workspace/deno.json
Normal file
6
tests/specs/update/mixed_workspace/deno.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"workspace": ["./member-a", "./member-b"],
|
||||
"imports": {
|
||||
"@denotest/subtract": "jsr:@denotest/subtract@^0.2.0"
|
||||
}
|
||||
}
|
55
tests/specs/update/mixed_workspace/deno.lock
Normal file
55
tests/specs/update/mixed_workspace/deno.lock
Normal file
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"jsr:@denotest/add@0.2": "0.2.1",
|
||||
"jsr:@denotest/multiple-exports@0.2.0": "0.2.0",
|
||||
"jsr:@denotest/subtract@0.2": "0.2.0",
|
||||
"npm:@denotest/bin@0.6.0": "0.6.0",
|
||||
"npm:@denotest/breaking-change-between-versions@1.0.0": "1.0.0",
|
||||
"npm:@denotest/has-patch-versions@0.1.0": "0.1.0"
|
||||
},
|
||||
"jsr": {
|
||||
"@denotest/add@0.2.1": {
|
||||
"integrity": "a9076d30ecb42b2fc6dd95e7055fbf4e6358b53f550741bd7f60089d19f68848"
|
||||
},
|
||||
"@denotest/multiple-exports@0.2.0": {
|
||||
"integrity": "efe9748a0c0939c7ac245fee04acc0c42bd7a61874ff71a360c4543e4f5f6b36"
|
||||
},
|
||||
"@denotest/subtract@0.2.0": {
|
||||
"integrity": "c9650fc559ab2430effc0c7fb1540e3aa89888fbdd926335ccfdeac57eb3a64d"
|
||||
}
|
||||
},
|
||||
"npm": {
|
||||
"@denotest/bin@0.6.0": {
|
||||
"integrity": "sha512-vCNpxFgQN4fw4ZOp63nbTX1ilcDqNpvXCvYyC8nmfxQQAezsEt095I/YXwMIoMGzWtjCvlMf9kVEYfLuT5oEGQ=="
|
||||
},
|
||||
"@denotest/breaking-change-between-versions@1.0.0": {
|
||||
"integrity": "sha512-bzMGYx+DxxPlI74n/VsDAN7Db1BY7Sz2XqxXruMo9dEznsBZu7Ez3i8YQ8n0leTxAiiMk1RCG4zQHPG1aj3xRw=="
|
||||
},
|
||||
"@denotest/has-patch-versions@0.1.0": {
|
||||
"integrity": "sha512-H/MBo0jKDdMsX4AAGEGQbZj70nfNe3oUNZXbohYHhqf9EfpLnXp/7FC29ZdfV4+p6VjEcOGdCtXc6rilE6iYpg=="
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@denotest/subtract@0.2"
|
||||
],
|
||||
"members": {
|
||||
"member-a": {
|
||||
"dependencies": [
|
||||
"jsr:@denotest/add@0.2",
|
||||
"jsr:@denotest/multiple-exports@0.2.0",
|
||||
"npm:@denotest/breaking-change-between-versions@1.0.0"
|
||||
]
|
||||
},
|
||||
"member-b": {
|
||||
"packageJson": {
|
||||
"dependencies": [
|
||||
"npm:@denotest/bin@0.6.0",
|
||||
"npm:@denotest/has-patch-versions@0.1.0"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
55
tests/specs/update/mixed_workspace/deno.lock.orig.out
Normal file
55
tests/specs/update/mixed_workspace/deno.lock.orig.out
Normal file
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"jsr:@denotest/add@0.2": "0.2.1",
|
||||
"jsr:@denotest/multiple-exports@0.2.0": "0.2.0",
|
||||
"jsr:@denotest/subtract@0.2": "0.2.0",
|
||||
"npm:@denotest/bin@0.6.0": "0.6.0",
|
||||
"npm:@denotest/breaking-change-between-versions@1.0.0": "1.0.0",
|
||||
"npm:@denotest/has-patch-versions@0.1.0": "0.1.0"
|
||||
},
|
||||
"jsr": {
|
||||
"@denotest/add@0.2.1": {
|
||||
"integrity": "a9076d30ecb42b2fc6dd95e7055fbf4e6358b53f550741bd7f60089d19f68848"
|
||||
},
|
||||
"@denotest/multiple-exports@0.2.0": {
|
||||
"integrity": "efe9748a0c0939c7ac245fee04acc0c42bd7a61874ff71a360c4543e4f5f6b36"
|
||||
},
|
||||
"@denotest/subtract@0.2.0": {
|
||||
"integrity": "c9650fc559ab2430effc0c7fb1540e3aa89888fbdd926335ccfdeac57eb3a64d"
|
||||
}
|
||||
},
|
||||
"npm": {
|
||||
"@denotest/bin@0.6.0": {
|
||||
"integrity": "sha512-vCNpxFgQN4fw4ZOp63nbTX1ilcDqNpvXCvYyC8nmfxQQAezsEt095I/YXwMIoMGzWtjCvlMf9kVEYfLuT5oEGQ=="
|
||||
},
|
||||
"@denotest/breaking-change-between-versions@1.0.0": {
|
||||
"integrity": "sha512-bzMGYx+DxxPlI74n/VsDAN7Db1BY7Sz2XqxXruMo9dEznsBZu7Ez3i8YQ8n0leTxAiiMk1RCG4zQHPG1aj3xRw=="
|
||||
},
|
||||
"@denotest/has-patch-versions@0.1.0": {
|
||||
"integrity": "sha512-H/MBo0jKDdMsX4AAGEGQbZj70nfNe3oUNZXbohYHhqf9EfpLnXp/7FC29ZdfV4+p6VjEcOGdCtXc6rilE6iYpg=="
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@denotest/subtract@0.2"
|
||||
],
|
||||
"members": {
|
||||
"member-a": {
|
||||
"dependencies": [
|
||||
"jsr:@denotest/add@0.2",
|
||||
"jsr:@denotest/multiple-exports@0.2.0",
|
||||
"npm:@denotest/breaking-change-between-versions@1.0.0"
|
||||
]
|
||||
},
|
||||
"member-b": {
|
||||
"packageJson": {
|
||||
"dependencies": [
|
||||
"npm:@denotest/bin@0.6.0",
|
||||
"npm:@denotest/has-patch-versions@0.1.0"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "@denotest/member-a",
|
||||
"exports": "./mod.ts",
|
||||
"imports": {
|
||||
"@denotest/add": "jsr:@denotest/add@^1.0.0",
|
||||
"@denotest/add/": "jsr:/@denotest/add@^1.0.0/",
|
||||
"@denotest/with-subpath": "jsr:@denotest/multiple-exports@0.5.0/data-json",
|
||||
"@denotest/breaking-change-between-versions": "npm:@denotest/breaking-change-between-versions@1.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@denotest/member-b",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@denotest/has-patch-versions": "0.1.0",
|
||||
"aliased": "npm:@denotest/bin@^1.0.0"
|
||||
}
|
||||
}
|
12
tests/specs/update/mixed_workspace/filtered/update.out
Normal file
12
tests/specs/update/mixed_workspace/filtered/update.out
Normal file
|
@ -0,0 +1,12 @@
|
|||
[UNORDERED_START]
|
||||
Download http://localhost:4260/@denotest/bin/1.0.0.tgz
|
||||
Download http://127.0.0.1:4250/@denotest/multiple-exports/0.5.0_meta.json
|
||||
Download http://127.0.0.1:4250/@denotest/multiple-exports/0.5.0/data.json
|
||||
Download http://127.0.0.1:4250/@denotest/add/1.0.0/mod.ts
|
||||
Download http://127.0.0.1:4250/@denotest/subtract/1.0.0/mod.ts
|
||||
[UNORDERED_END]
|
||||
Updated 4 dependencies:
|
||||
- jsr:@denotest/add 0.2.1 -> 1.0.0
|
||||
- jsr:@denotest/multiple-exports 0.2.0 -> 0.5.0
|
||||
- jsr:@denotest/subtract 0.2.0 -> 1.0.0
|
||||
- npm:@denotest/bin 0.6.0 -> 1.0.0
|
10
tests/specs/update/mixed_workspace/member-a/deno.json
Normal file
10
tests/specs/update/mixed_workspace/member-a/deno.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "@denotest/member-a",
|
||||
"exports": "./mod.ts",
|
||||
"imports": {
|
||||
"@denotest/add": "jsr:@denotest/add@^0.2.0",
|
||||
"@denotest/add/": "jsr:/@denotest/add@^0.2.0/",
|
||||
"@denotest/with-subpath": "jsr:@denotest/multiple-exports@0.2.0/data-json",
|
||||
"@denotest/breaking-change-between-versions": "npm:@denotest/breaking-change-between-versions@1.0.0"
|
||||
}
|
||||
}
|
0
tests/specs/update/mixed_workspace/member-a/mod.ts
Normal file
0
tests/specs/update/mixed_workspace/member-a/mod.ts
Normal file
8
tests/specs/update/mixed_workspace/member-b/package.json
Normal file
8
tests/specs/update/mixed_workspace/member-b/package.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@denotest/member-b",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@denotest/has-patch-versions": "0.1.0",
|
||||
"aliased": "npm:@denotest/bin@0.6.0"
|
||||
}
|
||||
}
|
2
tests/specs/update/mixed_workspace/print_file.ts
Normal file
2
tests/specs/update/mixed_workspace/print_file.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
const file = Deno.args[0];
|
||||
console.log(Deno.readTextFileSync(file).trim());
|
|
@ -0,0 +1,9 @@
|
|||
┌────────────────────────────────────────────────┬─────────┬────────┬────────┐
|
||||
│ Package │ Current │ Update │ Latest │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ jsr:@denotest/multiple-exports │ 0.2.0 │ 0.2.0 │ 1.0.0 │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ jsr:@denotest/add │ 0.2.1 │ 0.2.1 │ 1.0.0 │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ npm:@denotest/breaking-change-between-versions │ 1.0.0 │ 1.0.0 │ 2.0.0 │
|
||||
└────────────────────────────────────────────────┴─────────┴────────┴────────┘
|
|
@ -0,0 +1,7 @@
|
|||
┌──────────────────────────────────┬─────────┬────────┬────────┐
|
||||
│ Package │ Current │ Update │ Latest │
|
||||
├──────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ npm:@denotest/has-patch-versions │ 0.1.0 │ 0.1.0 │ 0.2.0 │
|
||||
├──────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ npm:@denotest/bin │ 0.6.0 │ 0.6.0 │ 1.0.0 │
|
||||
└──────────────────────────────────┴─────────┴────────┴────────┘
|
|
@ -0,0 +1,15 @@
|
|||
┌────────────────────────────────────────────────┬─────────┬────────┬────────┐
|
||||
│ Package │ Current │ Update │ Latest │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ jsr:@denotest/multiple-exports │ 0.2.0 │ 0.2.0 │ 1.0.0 │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ jsr:@denotest/subtract │ 0.2.0 │ 0.2.0 │ 1.0.0 │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ jsr:@denotest/add │ 0.2.1 │ 0.2.1 │ 1.0.0 │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ npm:@denotest/has-patch-versions │ 0.1.0 │ 0.1.0 │ 0.2.0 │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ npm:@denotest/bin │ 0.6.0 │ 0.6.0 │ 1.0.0 │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ npm:@denotest/breaking-change-between-versions │ 1.0.0 │ 1.0.0 │ 2.0.0 │
|
||||
└────────────────────────────────────────────────┴─────────┴────────┴────────┘
|
|
@ -0,0 +1,5 @@
|
|||
┌────────────────────────┬─────────┬────────┬────────┐
|
||||
│ Package │ Current │ Update │ Latest │
|
||||
├────────────────────────┼─────────┼────────┼────────┤
|
||||
│ jsr:@denotest/subtract │ 0.2.0 │ 0.2.0 │ 1.0.0 │
|
||||
└────────────────────────┴─────────┴────────┴────────┘
|
|
@ -0,0 +1,16 @@
|
|||
[UNORDERED_START]
|
||||
Download http://localhost:4260/@denotest/breaking-change-between-versions/2.0.0.tgz
|
||||
Download http://localhost:4260/@denotest/has-patch-versions/0.2.0.tgz
|
||||
Download http://localhost:4260/@denotest/bin/1.0.0.tgz
|
||||
Download http://127.0.0.1:4250/@denotest/multiple-exports/1.0.0_meta.json
|
||||
Download http://127.0.0.1:4250/@denotest/multiple-exports/1.0.0/data.json
|
||||
Download http://127.0.0.1:4250/@denotest/subtract/1.0.0/mod.ts
|
||||
Download http://127.0.0.1:4250/@denotest/add/1.0.0/mod.ts
|
||||
[UNORDERED_END]
|
||||
Updated 6 dependencies:
|
||||
- jsr:@denotest/add 0.2.1 -> 1.0.0
|
||||
- jsr:@denotest/multiple-exports 0.2.0 -> 1.0.0
|
||||
- jsr:@denotest/subtract 0.2.0 -> 1.0.0
|
||||
- npm:@denotest/bin 0.6.0 -> 1.0.0
|
||||
- npm:@denotest/breaking-change-between-versions 1.0.0 -> 2.0.0
|
||||
- npm:@denotest/has-patch-versions 0.1.0 -> 0.2.0
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"workspace": ["./member-a", "./member-b"],
|
||||
"imports": {
|
||||
"@denotest/subtract": "jsr:@denotest/subtract@^1.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
Download http://127.0.0.1:4250/@denotest/subtract/1.0.0/mod.ts
|
||||
Updated 1 dependency:
|
||||
- jsr:@denotest/subtract 0.2.0 -> 1.0.0
|
|
@ -0,0 +1,10 @@
|
|||
[UNORDERED_START]
|
||||
Download http://localhost:4260/@denotest/breaking-change-between-versions/2.0.0.tgz
|
||||
Download http://127.0.0.1:4250/@denotest/multiple-exports/1.0.0_meta.json
|
||||
Download http://127.0.0.1:4250/@denotest/multiple-exports/1.0.0/data.json
|
||||
Download http://127.0.0.1:4250/@denotest/add/1.0.0/mod.ts
|
||||
[UNORDERED_END]
|
||||
Updated 3 dependencies:
|
||||
- jsr:@denotest/add 0.2.1 -> 1.0.0
|
||||
- jsr:@denotest/multiple-exports 0.2.0 -> 1.0.0
|
||||
- npm:@denotest/breaking-change-between-versions 1.0.0 -> 2.0.0
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "@denotest/member-a",
|
||||
"exports": "./mod.ts",
|
||||
"imports": {
|
||||
"@denotest/add": "jsr:@denotest/add@^1.0.0",
|
||||
"@denotest/add/": "jsr:/@denotest/add@^1.0.0/",
|
||||
"@denotest/with-subpath": "jsr:@denotest/multiple-exports@^1.0.0/data-json",
|
||||
"@denotest/breaking-change-between-versions": "npm:@denotest/breaking-change-between-versions@^2.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
[UNORDERED_START]
|
||||
Download http://localhost:4260/@denotest/bin/1.0.0.tgz
|
||||
Download http://localhost:4260/@denotest/has-patch-versions/0.2.0.tgz
|
||||
[UNORDERED_END]
|
||||
Updated 2 dependencies:
|
||||
- npm:@denotest/bin 0.6.0 -> 1.0.0
|
||||
- npm:@denotest/has-patch-versions 0.1.0 -> 0.2.0
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@denotest/member-b",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@denotest/has-patch-versions": "^0.2.0",
|
||||
"aliased": "npm:@denotest/bin@^1.0.0"
|
||||
}
|
||||
}
|
100
tests/specs/update/package_json/__test__.jsonc
Normal file
100
tests/specs/update/package_json/__test__.jsonc
Normal file
|
@ -0,0 +1,100 @@
|
|||
{
|
||||
"tempDir": true,
|
||||
"tests": {
|
||||
"sanity_lockfile_up_to_date": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"eval",
|
||||
"const now = Deno.readTextFileSync('./deno.lock'); console.log(now.trim());"
|
||||
],
|
||||
"output": "deno.lock.orig.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"print_outdated": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "outdated",
|
||||
"output": "outdated.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"print_outdated_compatible": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "outdated --compatible",
|
||||
"output": "outdated_compatible.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"update_compatible": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "outdated --update",
|
||||
"output": "./update_compatible/update.out"
|
||||
},
|
||||
{
|
||||
"args": "-A ./print_file.ts ./package.json",
|
||||
"output": "./update_compatible/package.json.out"
|
||||
},
|
||||
{
|
||||
"args": "-A ./print_file.ts ./deno.lock",
|
||||
"output": "./update_compatible/deno.lock.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"update_latest": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "outdated --update --latest",
|
||||
"output": "update_latest/update.out"
|
||||
},
|
||||
{
|
||||
"args": "-A ./print_file.ts ./package.json",
|
||||
"output": "update_latest/package.json.out"
|
||||
},
|
||||
{
|
||||
"args": "-A ./print_file.ts ./deno.lock",
|
||||
"output": "update_latest/deno.lock.out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"update_filtered": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "install",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "outdated --update --latest @denotest/has-patch* aliased@0.7.0",
|
||||
"output": "filtered/update.out"
|
||||
},
|
||||
{
|
||||
"args": "-A print_file.ts ./package.json",
|
||||
"output": "filtered/package.json.out"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
28
tests/specs/update/package_json/deno.lock
Normal file
28
tests/specs/update/package_json/deno.lock
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"npm:@denotest/bin@0.6": "0.6.0",
|
||||
"npm:@denotest/breaking-change-between-versions@1.0.0": "1.0.0",
|
||||
"npm:@denotest/has-patch-versions@0.1": "0.1.0"
|
||||
},
|
||||
"npm": {
|
||||
"@denotest/bin@0.6.0": {
|
||||
"integrity": "sha512-vCNpxFgQN4fw4ZOp63nbTX1ilcDqNpvXCvYyC8nmfxQQAezsEt095I/YXwMIoMGzWtjCvlMf9kVEYfLuT5oEGQ=="
|
||||
},
|
||||
"@denotest/breaking-change-between-versions@1.0.0": {
|
||||
"integrity": "sha512-bzMGYx+DxxPlI74n/VsDAN7Db1BY7Sz2XqxXruMo9dEznsBZu7Ez3i8YQ8n0leTxAiiMk1RCG4zQHPG1aj3xRw=="
|
||||
},
|
||||
"@denotest/has-patch-versions@0.1.0": {
|
||||
"integrity": "sha512-H/MBo0jKDdMsX4AAGEGQbZj70nfNe3oUNZXbohYHhqf9EfpLnXp/7FC29ZdfV4+p6VjEcOGdCtXc6rilE6iYpg=="
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"packageJson": {
|
||||
"dependencies": [
|
||||
"npm:@denotest/bin@0.6",
|
||||
"npm:@denotest/breaking-change-between-versions@1.0.0",
|
||||
"npm:@denotest/has-patch-versions@0.1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
28
tests/specs/update/package_json/deno.lock.orig.out
Normal file
28
tests/specs/update/package_json/deno.lock.orig.out
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"npm:@denotest/bin@0.6": "0.6.0",
|
||||
"npm:@denotest/breaking-change-between-versions@1.0.0": "1.0.0",
|
||||
"npm:@denotest/has-patch-versions@0.1": "0.1.0"
|
||||
},
|
||||
"npm": {
|
||||
"@denotest/bin@0.6.0": {
|
||||
"integrity": "sha512-vCNpxFgQN4fw4ZOp63nbTX1ilcDqNpvXCvYyC8nmfxQQAezsEt095I/YXwMIoMGzWtjCvlMf9kVEYfLuT5oEGQ=="
|
||||
},
|
||||
"@denotest/breaking-change-between-versions@1.0.0": {
|
||||
"integrity": "sha512-bzMGYx+DxxPlI74n/VsDAN7Db1BY7Sz2XqxXruMo9dEznsBZu7Ez3i8YQ8n0leTxAiiMk1RCG4zQHPG1aj3xRw=="
|
||||
},
|
||||
"@denotest/has-patch-versions@0.1.0": {
|
||||
"integrity": "sha512-H/MBo0jKDdMsX4AAGEGQbZj70nfNe3oUNZXbohYHhqf9EfpLnXp/7FC29ZdfV4+p6VjEcOGdCtXc6rilE6iYpg=="
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"packageJson": {
|
||||
"dependencies": [
|
||||
"npm:@denotest/bin@0.6",
|
||||
"npm:@denotest/breaking-change-between-versions@1.0.0",
|
||||
"npm:@denotest/has-patch-versions@0.1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@denotest/has-patch-versions": "^0.2.0",
|
||||
"@denotest/breaking-change-between-versions": "1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aliased": "npm:@denotest/bin@0.7.0"
|
||||
}
|
||||
}
|
9
tests/specs/update/package_json/filtered/update.out
Normal file
9
tests/specs/update/package_json/filtered/update.out
Normal file
|
@ -0,0 +1,9 @@
|
|||
[UNORDERED_START]
|
||||
Download http://localhost:4260/@denotest/bin/0.7.0.tgz
|
||||
Download http://localhost:4260/@denotest/has-patch-versions/0.2.0.tgz
|
||||
Initialize @denotest/has-patch-versions@0.2.0
|
||||
Initialize @denotest/bin@0.7.0
|
||||
[UNORDERED_END]
|
||||
Updated 2 dependencies:
|
||||
- npm:@denotest/bin 0.6.0 -> 0.7.0
|
||||
- npm:@denotest/has-patch-versions 0.1.0 -> 0.2.0
|
9
tests/specs/update/package_json/outdated.out
Normal file
9
tests/specs/update/package_json/outdated.out
Normal file
|
@ -0,0 +1,9 @@
|
|||
┌────────────────────────────────────────────────┬─────────┬────────┬────────┐
|
||||
│ Package │ Current │ Update │ Latest │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ npm:@denotest/has-patch-versions │ 0.1.0 │ 0.1.1 │ 0.2.0 │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ npm:@denotest/bin │ 0.6.0 │ 0.6.0 │ 1.0.0 │
|
||||
├────────────────────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ npm:@denotest/breaking-change-between-versions │ 1.0.0 │ 1.0.0 │ 2.0.0 │
|
||||
└────────────────────────────────────────────────┴─────────┴────────┴────────┘
|
5
tests/specs/update/package_json/outdated_compatible.out
Normal file
5
tests/specs/update/package_json/outdated_compatible.out
Normal file
|
@ -0,0 +1,5 @@
|
|||
┌──────────────────────────────────┬─────────┬────────┬────────┐
|
||||
│ Package │ Current │ Update │ Latest │
|
||||
├──────────────────────────────────┼─────────┼────────┼────────┤
|
||||
│ npm:@denotest/has-patch-versions │ 0.1.0 │ 0.1.1 │ 0.2.0 │
|
||||
└──────────────────────────────────┴─────────┴────────┴────────┘
|
9
tests/specs/update/package_json/package.json
Normal file
9
tests/specs/update/package_json/package.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@denotest/has-patch-versions": "^0.1.0",
|
||||
"@denotest/breaking-change-between-versions": "1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aliased": "npm:@denotest/bin@^0.6.0"
|
||||
}
|
||||
}
|
2
tests/specs/update/package_json/print_file.ts
Normal file
2
tests/specs/update/package_json/print_file.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
const file = Deno.args[0];
|
||||
console.log(Deno.readTextFileSync(file).trim());
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"npm:@denotest/bin@0.6": "0.6.0",
|
||||
"npm:@denotest/breaking-change-between-versions@1.0.0": "1.0.0",
|
||||
"npm:@denotest/has-patch-versions@~0.1.1": "0.1.1"
|
||||
},
|
||||
"npm": {
|
||||
"@denotest/bin@0.6.0": {
|
||||
"integrity": "sha512-vCNpxFgQN4fw4ZOp63nbTX1ilcDqNpvXCvYyC8nmfxQQAezsEt095I/YXwMIoMGzWtjCvlMf9kVEYfLuT5oEGQ=="
|
||||
},
|
||||
"@denotest/breaking-change-between-versions@1.0.0": {
|
||||
"integrity": "sha512-bzMGYx+DxxPlI74n/VsDAN7Db1BY7Sz2XqxXruMo9dEznsBZu7Ez3i8YQ8n0leTxAiiMk1RCG4zQHPG1aj3xRw=="
|
||||
},
|
||||
"@denotest/has-patch-versions@0.1.1": {
|
||||
"integrity": "sha512-slUqYhu6DrPiSdNzmW5aMdW2/osIYnDP0yY3CwgLzAiyK0/cwb0epSpTSyZEmTKXA3rezxxC7ASSsnD34uH1/w=="
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"packageJson": {
|
||||
"dependencies": [
|
||||
"npm:@denotest/bin@0.6",
|
||||
"npm:@denotest/breaking-change-between-versions@1.0.0",
|
||||
"npm:@denotest/has-patch-versions@~0.1.1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@denotest/has-patch-versions": "^0.1.1",
|
||||
"@denotest/breaking-change-between-versions": "1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aliased": "npm:@denotest/bin@^0.6.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
Download http://localhost:4260/@denotest/has-patch-versions/0.1.1.tgz
|
||||
Initialize @denotest/has-patch-versions@0.1.1
|
||||
Updated 1 dependency:
|
||||
- npm:@denotest/has-patch-versions 0.1.0 -> 0.1.1
|
28
tests/specs/update/package_json/update_latest/deno.lock.out
Normal file
28
tests/specs/update/package_json/update_latest/deno.lock.out
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"npm:@denotest/bin@1": "1.0.0",
|
||||
"npm:@denotest/breaking-change-between-versions@2": "2.0.0",
|
||||
"npm:@denotest/has-patch-versions@0.2": "0.2.0"
|
||||
},
|
||||
"npm": {
|
||||
"@denotest/bin@1.0.0": {
|
||||
"integrity": "sha512-ZtrWnYYPIzw4a9H1uNeZRZRWuLCpHZZU/SllIyFLqcTLH/3zdRI8UH4Z1Kf+8N++bWGO3fg8Ev4vvS1LoLlidg=="
|
||||
},
|
||||
"@denotest/breaking-change-between-versions@2.0.0": {
|
||||
"integrity": "sha512-3eQpPhhJYbSHaAmpgyVT8IM3+MkxcAQl90Uw8zmuTiFs64Wt3HGzSz74cwPlvfqqesRktm8fBZMmrtxVo3ENzw=="
|
||||
},
|
||||
"@denotest/has-patch-versions@0.2.0": {
|
||||
"integrity": "sha512-7XIVGrBMyqnts5/wcc7dn7rVl4IWrCiGUT2GLDSLWnogOMIZBapJJLW9n8Leq1bDTJ3U6aDTEFKz9fVSOwZfLQ=="
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"packageJson": {
|
||||
"dependencies": [
|
||||
"npm:@denotest/bin@1",
|
||||
"npm:@denotest/breaking-change-between-versions@2",
|
||||
"npm:@denotest/has-patch-versions@0.2"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@denotest/has-patch-versions": "^0.2.0",
|
||||
"@denotest/breaking-change-between-versions": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aliased": "npm:@denotest/bin@^1.0.0"
|
||||
}
|
||||
}
|
14
tests/specs/update/package_json/update_latest/update.out
Normal file
14
tests/specs/update/package_json/update_latest/update.out
Normal file
|
@ -0,0 +1,14 @@
|
|||
[UNORDERED_START]
|
||||
Download http://localhost:4260/@denotest/bin/1.0.0.tgz
|
||||
Download http://localhost:4260/@denotest/has-patch-versions/0.2.0.tgz
|
||||
Download http://localhost:4260/@denotest/breaking-change-between-versions/2.0.0.tgz
|
||||
Initialize @denotest/has-patch-versions@0.2.0
|
||||
Initialize @denotest/breaking-change-between-versions@2.0.0
|
||||
Initialize @denotest/bin@1.0.0
|
||||
[UNORDERED_END]
|
||||
Updated 3 dependencies:
|
||||
[UNORDERED_START]
|
||||
- npm:@denotest/bin 0.6.0 -> 1.0.0
|
||||
- npm:@denotest/breaking-change-between-versions 1.0.0 -> 2.0.0
|
||||
- npm:@denotest/has-patch-versions 0.1.0 -> 0.2.0
|
||||
[UNORDERED_END]
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
|
@ -189,6 +190,60 @@ impl TestNpmRegistry {
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: extracted out partially from the `tar` crate, all credits to the original authors
|
||||
fn append_dir_all<W: std::io::Write>(
|
||||
builder: &mut tar::Builder<W>,
|
||||
path: &Path,
|
||||
src_path: &Path,
|
||||
) -> Result<()> {
|
||||
builder.follow_symlinks(true);
|
||||
let mode = tar::HeaderMode::Deterministic;
|
||||
builder.mode(mode);
|
||||
let mut stack = vec![(src_path.to_path_buf(), true, false)];
|
||||
let mut entries = Vec::new();
|
||||
while let Some((src, is_dir, is_symlink)) = stack.pop() {
|
||||
let dest = path.join(src.strip_prefix(src_path).unwrap());
|
||||
// In case of a symlink pointing to a directory, is_dir is false, but src.is_dir() will return true
|
||||
if is_dir || (is_symlink && src.is_dir()) {
|
||||
for entry in fs::read_dir(&src)? {
|
||||
let entry = entry?;
|
||||
let file_type = entry.file_type()?;
|
||||
stack.push((entry.path(), file_type.is_dir(), file_type.is_symlink()));
|
||||
}
|
||||
if dest != Path::new("") {
|
||||
entries.push((src, dest));
|
||||
}
|
||||
} else {
|
||||
entries.push((src, dest));
|
||||
}
|
||||
}
|
||||
entries.sort_by(|(_, a), (_, b)| a.cmp(b));
|
||||
for (src, dest) in entries {
|
||||
let mut header = tar::Header::new_gnu();
|
||||
let metadata = src.metadata().with_context(|| {
|
||||
format!("trying to get metadata for {}", src.display())
|
||||
})?;
|
||||
header.set_metadata_in_mode(&metadata, mode);
|
||||
// this is what `tar` sets the mtime to on unix in deterministic mode, on windows it uses a different
|
||||
// value, which causes the tarball to have a different hash on windows. force it to be the same
|
||||
// to ensure the same output on all platforms
|
||||
header.set_mtime(1153704088);
|
||||
|
||||
let data = if src.is_file() {
|
||||
Box::new(
|
||||
fs::File::open(&src)
|
||||
.with_context(|| format!("trying to open file {}", src.display()))?,
|
||||
) as Box<dyn std::io::Read>
|
||||
} else {
|
||||
Box::new(std::io::empty()) as Box<dyn std::io::Read>
|
||||
};
|
||||
builder
|
||||
.append_data(&mut header, dest, data)
|
||||
.with_context(|| "appending data")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_npm_package(
|
||||
registry_hostname: &str,
|
||||
local_path: &str,
|
||||
|
@ -228,11 +283,14 @@ fn get_npm_package(
|
|||
GzEncoder::new(&mut tarball_bytes, Compression::default());
|
||||
{
|
||||
let mut builder = Builder::new(&mut encoder);
|
||||
builder
|
||||
.append_dir_all("package", &version_folder)
|
||||
.with_context(|| {
|
||||
format!("Error adding tarball for directory: {}", version_folder)
|
||||
})?;
|
||||
append_dir_all(
|
||||
&mut builder,
|
||||
Path::new("package"),
|
||||
version_folder.as_path(),
|
||||
)
|
||||
.with_context(|| {
|
||||
format!("Error adding tarball for directory {}", version_folder,)
|
||||
})?;
|
||||
builder.finish()?;
|
||||
}
|
||||
encoder.finish()?;
|
||||
|
|
Loading…
Reference in a new issue