summaryrefslogtreecommitdiff
path: root/cli/tools/registry
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tools/registry')
-rw-r--r--cli/tools/registry/mod.rs1
-rw-r--r--cli/tools/registry/pm.rs944
-rw-r--r--cli/tools/registry/pm/cache_deps.rs17
-rw-r--r--cli/tools/registry/tar.rs2
4 files changed, 465 insertions, 499 deletions
diff --git a/cli/tools/registry/mod.rs b/cli/tools/registry/mod.rs
index 4098d62e3..12289c581 100644
--- a/cli/tools/registry/mod.rs
+++ b/cli/tools/registry/mod.rs
@@ -69,6 +69,7 @@ pub use pm::add;
pub use pm::cache_top_level_deps;
pub use pm::remove;
pub use pm::AddCommandName;
+pub use pm::AddRmPackageReq;
use publish_order::PublishOrderGraph;
use unfurl::SpecifierUnfurler;
diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs
index 5dc042620..c1ea2c75e 100644
--- a/cli/tools/registry/pm.rs
+++ b/cli/tools/registry/pm.rs
@@ -1,32 +1,25 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-mod cache_deps;
-
-pub use cache_deps::cache_top_level_deps;
-use deno_semver::jsr::JsrPackageReqReference;
-use deno_semver::npm::NpmPackageReqReference;
-use deno_semver::VersionReq;
-
-use std::borrow::Cow;
+use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
-use deno_ast::TextChange;
-use deno_config::deno_json::FmtOptionsConfig;
-use deno_core::anyhow::anyhow;
use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
use deno_core::futures::StreamExt;
-use deno_core::serde_json;
-use deno_core::ModuleSpecifier;
-use deno_runtime::deno_node;
+use deno_path_util::url_to_file_path;
+use deno_semver::jsr::JsrPackageReqReference;
+use deno_semver::npm::NpmPackageReqReference;
+use deno_semver::package::PackageNv;
use deno_semver::package::PackageReq;
-use indexmap::IndexMap;
-use jsonc_parser::ast::ObjectProp;
-use jsonc_parser::ast::Value;
-use yoke::Yoke;
+use deno_semver::Version;
+use deno_semver::VersionReq;
+use jsonc_parser::cst::CstObject;
+use jsonc_parser::cst::CstObjectProp;
+use jsonc_parser::cst::CstRootNode;
+use jsonc_parser::json;
use crate::args::AddFlags;
use crate::args::CacheSetting;
@@ -38,236 +31,181 @@ use crate::file_fetcher::FileFetcher;
use crate::jsr::JsrFetchResolver;
use crate::npm::NpmFetchResolver;
-enum DenoConfigFormat {
- Json,
- Jsonc,
-}
+mod cache_deps;
-impl DenoConfigFormat {
- fn from_specifier(spec: &ModuleSpecifier) -> Result<Self, AnyError> {
- let file_name = spec
- .path_segments()
- .ok_or_else(|| anyhow!("Empty path in deno config specifier: {spec}"))?
- .last()
- .unwrap();
- match file_name {
- "deno.json" => Ok(Self::Json),
- "deno.jsonc" => Ok(Self::Jsonc),
- _ => bail!("Unsupported deno config file: {file_name}"),
- }
- }
-}
+pub use cache_deps::cache_top_level_deps;
-struct DenoConfig {
- config: Arc<deno_config::deno_json::ConfigFile>,
- format: DenoConfigFormat,
- imports: IndexMap<String, String>,
+#[derive(Debug, Copy, Clone)]
+enum ConfigKind {
+ DenoJson,
+ PackageJson,
}
-fn deno_json_imports(
- config: &deno_config::deno_json::ConfigFile,
-) -> Result<IndexMap<String, String>, AnyError> {
- Ok(
- config
- .json
- .imports
- .clone()
- .map(|imports| {
- serde_json::from_value(imports)
- .map_err(|err| anyhow!("Malformed \"imports\" configuration: {err}"))
- })
- .transpose()?
- .unwrap_or_default(),
- )
+struct ConfigUpdater {
+ kind: ConfigKind,
+ cst: CstRootNode,
+ root_object: CstObject,
+ path: PathBuf,
+ modified: bool,
}
-impl DenoConfig {
- fn from_options(options: &CliOptions) -> Result<Option<Self>, AnyError> {
- let start_dir = &options.start_dir;
- if let Some(config) = start_dir.maybe_deno_json() {
- Ok(Some(Self {
- imports: deno_json_imports(config)?,
- config: config.clone(),
- format: DenoConfigFormat::from_specifier(&config.specifier)?,
- }))
- } else {
- Ok(None)
- }
- }
- fn add(&mut self, selected: SelectedPackage) {
- self.imports.insert(
- selected.import_name,
- format!("{}@{}", selected.package_name, selected.version_req),
- );
+impl ConfigUpdater {
+ fn new(
+ kind: ConfigKind,
+ config_file_path: PathBuf,
+ ) -> Result<Self, AnyError> {
+ let config_file_contents = std::fs::read_to_string(&config_file_path)
+ .with_context(|| {
+ format!("Reading config file '{}'", config_file_path.display())
+ })?;
+ let cst = CstRootNode::parse(&config_file_contents, &Default::default())
+ .with_context(|| {
+ format!("Parsing config file '{}'", config_file_path.display())
+ })?;
+ let root_object = cst.object_value_or_set();
+ Ok(Self {
+ kind,
+ cst,
+ root_object,
+ path: config_file_path,
+ modified: false,
+ })
}
- fn remove(&mut self, package: &str) -> bool {
- self.imports.shift_remove(package).is_some()
+ fn display_path(&self) -> String {
+ deno_path_util::url_from_file_path(&self.path)
+ .map(|u| u.to_string())
+ .unwrap_or_else(|_| self.path.display().to_string())
}
- fn take_import_fields(
- &mut self,
- ) -> Vec<(&'static str, IndexMap<String, String>)> {
- vec![("imports", std::mem::take(&mut self.imports))]
+ fn obj(&self) -> &CstObject {
+ &self.root_object
}
-}
-impl NpmConfig {
- fn from_options(options: &CliOptions) -> Result<Option<Self>, AnyError> {
- let start_dir = &options.start_dir;
- if let Some(pkg_json) = start_dir.maybe_pkg_json() {
- Ok(Some(Self {
- dependencies: pkg_json.dependencies.clone().unwrap_or_default(),
- dev_dependencies: pkg_json.dev_dependencies.clone().unwrap_or_default(),
- config: pkg_json.clone(),
- fmt_options: None,
- }))
- } else {
- Ok(None)
- }
+ fn contents(&self) -> String {
+ self.cst.to_string()
}
fn add(&mut self, selected: SelectedPackage, dev: bool) {
- let (name, version) = package_json_dependency_entry(selected);
- if dev {
- self.dependencies.swap_remove(&name);
- self.dev_dependencies.insert(name, version);
- } else {
- self.dev_dependencies.swap_remove(&name);
- self.dependencies.insert(name, version);
+ fn insert_index(object: &CstObject, searching_name: &str) -> usize {
+ object
+ .properties()
+ .into_iter()
+ .take_while(|prop| {
+ let prop_name =
+ prop.name().and_then(|name| name.decoded_value().ok());
+ match prop_name {
+ Some(current_name) => {
+ searching_name.cmp(&current_name) == std::cmp::Ordering::Greater
+ }
+ None => true,
+ }
+ })
+ .count()
}
- }
- fn remove(&mut self, package: &str) -> bool {
- let in_deps = self.dependencies.shift_remove(package).is_some();
- let in_dev_deps = self.dev_dependencies.shift_remove(package).is_some();
- in_deps || in_dev_deps
- }
-
- fn take_import_fields(
- &mut self,
- ) -> Vec<(&'static str, IndexMap<String, String>)> {
- vec![
- ("dependencies", std::mem::take(&mut self.dependencies)),
- (
- "devDependencies",
- std::mem::take(&mut self.dev_dependencies),
- ),
- ]
- }
-}
-
-struct NpmConfig {
- config: Arc<deno_node::PackageJson>,
- fmt_options: Option<FmtOptionsConfig>,
- dependencies: IndexMap<String, String>,
- dev_dependencies: IndexMap<String, String>,
-}
-
-enum DenoOrPackageJson {
- Deno(DenoConfig),
- Npm(NpmConfig),
-}
-
-impl From<DenoConfig> for DenoOrPackageJson {
- fn from(config: DenoConfig) -> Self {
- Self::Deno(config)
- }
-}
-
-impl From<NpmConfig> for DenoOrPackageJson {
- fn from(config: NpmConfig) -> Self {
- Self::Npm(config)
- }
-}
+ match self.kind {
+ ConfigKind::DenoJson => {
+ let imports = self.root_object.object_value_or_set("imports");
+ let value =
+ format!("{}@{}", selected.package_name, selected.version_req);
+ if let Some(prop) = imports.get(&selected.import_name) {
+ prop.set_value(json!(value));
+ } else {
+ let index = insert_index(&imports, &selected.import_name);
+ imports.insert(index, &selected.import_name, json!(value));
+ }
+ }
+ ConfigKind::PackageJson => {
+ let deps_prop = self.root_object.get("dependencies");
+ let dev_deps_prop = self.root_object.get("devDependencies");
+
+ let dependencies = if dev {
+ self
+ .root_object
+ .object_value("devDependencies")
+ .unwrap_or_else(|| {
+ let index = deps_prop
+ .as_ref()
+ .map(|p| p.property_index() + 1)
+ .unwrap_or_else(|| self.root_object.properties().len());
+ self
+ .root_object
+ .insert(index, "devDependencies", json!({}))
+ .object_value_or_set()
+ })
+ } else {
+ self
+ .root_object
+ .object_value("dependencies")
+ .unwrap_or_else(|| {
+ let index = dev_deps_prop
+ .as_ref()
+ .map(|p| p.property_index())
+ .unwrap_or_else(|| self.root_object.properties().len());
+ self
+ .root_object
+ .insert(index, "dependencies", json!({}))
+ .object_value_or_set()
+ })
+ };
+ let other_dependencies = if dev {
+ deps_prop.and_then(|p| p.value().and_then(|v| v.as_object()))
+ } else {
+ dev_deps_prop.and_then(|p| p.value().and_then(|v| v.as_object()))
+ };
-/// Wrapper around `jsonc_parser::ast::Object` that can be stored in a `Yoke`
-#[derive(yoke::Yokeable)]
-struct JsoncObjectView<'a>(jsonc_parser::ast::Object<'a>);
+ let (alias, value) = package_json_dependency_entry(selected);
-struct ConfigUpdater {
- config: DenoOrPackageJson,
- // the `Yoke` is so we can carry the parsed object (which borrows from
- // the source) along with the source itself
- ast: Yoke<JsoncObjectView<'static>, String>,
- path: PathBuf,
- modified: bool,
-}
+ if let Some(other) = other_dependencies {
+ if let Some(prop) = other.get(&alias) {
+ remove_prop_and_maybe_parent_prop(prop);
+ }
+ }
-impl ConfigUpdater {
- fn obj(&self) -> &jsonc_parser::ast::Object<'_> {
- &self.ast.get().0
- }
- fn contents(&self) -> &str {
- self.ast.backing_cart()
- }
- async fn maybe_new(
- config: Option<impl Into<DenoOrPackageJson>>,
- ) -> Result<Option<Self>, AnyError> {
- if let Some(config) = config {
- Ok(Some(Self::new(config.into()).await?))
- } else {
- Ok(None)
- }
- }
- async fn new(config: DenoOrPackageJson) -> Result<Self, AnyError> {
- let specifier = config.specifier();
- if specifier.scheme() != "file" {
- bail!("Can't update a remote configuration file");
- }
- let config_file_path = specifier.to_file_path().map_err(|_| {
- anyhow!("Specifier {specifier:?} is an invalid file path")
- })?;
- let config_file_contents = {
- let contents = tokio::fs::read_to_string(&config_file_path)
- .await
- .with_context(|| {
- format!("Reading config file at: {}", config_file_path.display())
- })?;
- if contents.trim().is_empty() {
- "{}\n".into()
- } else {
- contents
+ if let Some(prop) = dependencies.get(&alias) {
+ prop.set_value(json!(value));
+ } else {
+ let index = insert_index(&dependencies, &alias);
+ dependencies.insert(index, &alias, json!(value));
+ }
}
- };
- let ast = Yoke::try_attach_to_cart(config_file_contents, |contents| {
- let ast = jsonc_parser::parse_to_ast(
- contents,
- &Default::default(),
- &Default::default(),
- )
- .with_context(|| {
- format!("Failed to parse config file at {}", specifier)
- })?;
- let obj = match ast.value {
- Some(Value::Object(obj)) => obj,
- _ => bail!(
- "Failed to update config file at {}, expected an object",
- specifier
- ),
- };
- Ok(JsoncObjectView(obj))
- })?;
- Ok(Self {
- config,
- ast,
- path: config_file_path,
- modified: false,
- })
- }
-
- fn add(&mut self, selected: SelectedPackage, dev: bool) {
- match &mut self.config {
- DenoOrPackageJson::Deno(deno) => deno.add(selected),
- DenoOrPackageJson::Npm(npm) => npm.add(selected, dev),
}
+
self.modified = true;
}
fn remove(&mut self, package: &str) -> bool {
- let removed = match &mut self.config {
- DenoOrPackageJson::Deno(deno) => deno.remove(package),
- DenoOrPackageJson::Npm(npm) => npm.remove(package),
+ let removed = match self.kind {
+ ConfigKind::DenoJson => {
+ if let Some(prop) = self
+ .root_object
+ .object_value("imports")
+ .and_then(|i| i.get(package))
+ {
+ remove_prop_and_maybe_parent_prop(prop);
+ true
+ } else {
+ false
+ }
+ }
+ ConfigKind::PackageJson => {
+ let deps = [
+ self
+ .root_object
+ .object_value("dependencies")
+ .and_then(|deps| deps.get(package)),
+ self
+ .root_object
+ .object_value("devDependencies")
+ .and_then(|deps| deps.get(package)),
+ ];
+ let removed = deps.iter().any(|d| d.is_some());
+ for dep in deps.into_iter().flatten() {
+ remove_prop_and_maybe_parent_prop(dep);
+ }
+ removed
+ }
};
if removed {
self.modified = true;
@@ -275,76 +213,28 @@ impl ConfigUpdater {
removed
}
- async fn commit(mut self) -> Result<(), AnyError> {
+ fn commit(&self) -> Result<(), AnyError> {
if !self.modified {
return Ok(());
}
- let import_fields = self.config.take_import_fields();
-
- let fmt_config_options = self.config.fmt_options();
-
- let new_text = update_config_file_content(
- self.obj(),
- self.contents(),
- fmt_config_options,
- import_fields.into_iter().map(|(k, v)| {
- (
- k,
- if v.is_empty() {
- None
- } else {
- Some(generate_imports(v.into_iter().collect()))
- },
- )
- }),
- self.config.file_name(),
- );
-
- tokio::fs::write(&self.path, new_text).await?;
+ let new_text = self.contents();
+ std::fs::write(&self.path, new_text).with_context(|| {
+ format!("failed writing to '{}'", self.path.display())
+ })?;
Ok(())
}
}
-impl DenoOrPackageJson {
- fn specifier(&self) -> Cow<ModuleSpecifier> {
- match self {
- Self::Deno(d, ..) => Cow::Borrowed(&d.config.specifier),
- Self::Npm(n, ..) => Cow::Owned(n.config.specifier()),
- }
- }
-
- fn fmt_options(&self) -> FmtOptionsConfig {
- match self {
- DenoOrPackageJson::Deno(deno, ..) => deno
- .config
- .to_fmt_config()
- .ok()
- .map(|f| f.options)
- .unwrap_or_default(),
- DenoOrPackageJson::Npm(config) => {
- config.fmt_options.clone().unwrap_or_default()
- }
- }
- }
-
- fn take_import_fields(
- &mut self,
- ) -> Vec<(&'static str, IndexMap<String, String>)> {
- match self {
- Self::Deno(d) => d.take_import_fields(),
- Self::Npm(n) => n.take_import_fields(),
- }
- }
-
- fn file_name(&self) -> &'static str {
- match self {
- DenoOrPackageJson::Deno(config) => match config.format {
- DenoConfigFormat::Json => "deno.json",
- DenoConfigFormat::Jsonc => "deno.jsonc",
- },
- DenoOrPackageJson::Npm(..) => "package.json",
- }
+fn remove_prop_and_maybe_parent_prop(prop: CstObjectProp) {
+ let parent = prop.parent().unwrap().as_object().unwrap();
+ prop.remove();
+ if parent.properties().is_empty() {
+ let parent_property = parent.parent().unwrap();
+ let root_object = parent_property.parent().unwrap().as_object().unwrap();
+ // remove the property
+ parent_property.remove();
+ root_object.ensure_multiline();
}
}
@@ -363,7 +253,14 @@ fn package_json_dependency_entry(
selected: SelectedPackage,
) -> (String, String) {
if let Some(npm_package) = selected.package_name.strip_prefix("npm:") {
- (npm_package.into(), selected.version_req)
+ if selected.import_name == npm_package {
+ (npm_package.into(), selected.version_req)
+ } else {
+ (
+ selected.import_name,
+ format!("npm:{}@{}", npm_package, selected.version_req),
+ )
+ }
} else if let Some(jsr_package) = selected.package_name.strip_prefix("jsr:") {
let jsr_package = jsr_package.strip_prefix('@').unwrap_or(jsr_package);
let scope_replaced = jsr_package.replace('/', "__");
@@ -393,21 +290,45 @@ impl std::fmt::Display for AddCommandName {
fn load_configs(
flags: &Arc<Flags>,
-) -> Result<(CliFactory, Option<NpmConfig>, Option<DenoConfig>), AnyError> {
+ has_jsr_specifiers: impl FnOnce() -> bool,
+) -> Result<(CliFactory, Option<ConfigUpdater>, Option<ConfigUpdater>), AnyError>
+{
let cli_factory = CliFactory::from_flags(flags.clone());
let options = cli_factory.cli_options()?;
- let npm_config = NpmConfig::from_options(options)?;
- let (cli_factory, deno_config) = match DenoConfig::from_options(options)? {
+ let start_dir = &options.start_dir;
+ let npm_config = match start_dir.maybe_pkg_json() {
+ Some(pkg_json) => Some(ConfigUpdater::new(
+ ConfigKind::PackageJson,
+ pkg_json.path.clone(),
+ )?),
+ None => None,
+ };
+ let deno_config = match start_dir.maybe_deno_json() {
+ Some(deno_json) => Some(ConfigUpdater::new(
+ ConfigKind::DenoJson,
+ url_to_file_path(&deno_json.specifier)?,
+ )?),
+ None => None,
+ };
+
+ let (cli_factory, deno_config) = match deno_config {
Some(config) => (cli_factory, Some(config)),
- None if npm_config.is_some() => (cli_factory, None),
- None => {
+ None if npm_config.is_some() && !has_jsr_specifiers() => {
+ (cli_factory, None)
+ }
+ _ => {
let factory = create_deno_json(flags, options)?;
let options = factory.cli_options()?.clone();
+ let deno_json = options
+ .start_dir
+ .maybe_deno_json()
+ .expect("Just created deno.json");
(
factory,
- Some(
- DenoConfig::from_options(&options)?.expect("Just created deno.json"),
- ),
+ Some(ConfigUpdater::new(
+ ConfigKind::DenoJson,
+ url_to_file_path(&deno_json.specifier)?,
+ )?),
)
}
};
@@ -415,18 +336,26 @@ fn load_configs(
Ok((cli_factory, npm_config, deno_config))
}
+fn path_distance(a: &Path, b: &Path) -> usize {
+ let diff = pathdiff::diff_paths(a, b);
+ let Some(diff) = diff else {
+ return usize::MAX;
+ };
+ diff.components().count()
+}
+
pub async fn add(
flags: Arc<Flags>,
add_flags: AddFlags,
cmd_name: AddCommandName,
) -> Result<(), AnyError> {
- let (cli_factory, npm_config, deno_config) = load_configs(&flags)?;
- let mut npm_config = ConfigUpdater::maybe_new(npm_config).await?;
- let mut deno_config = ConfigUpdater::maybe_new(deno_config).await?;
+ let (cli_factory, mut npm_config, mut deno_config) =
+ load_configs(&flags, || {
+ add_flags.packages.iter().any(|s| s.starts_with("jsr:"))
+ })?;
if let Some(deno) = &deno_config {
- let specifier = deno.config.specifier();
- if deno.obj().get_string("importMap").is_some() {
+ if deno.obj().get("importMap").is_some() {
bail!(
concat!(
"`deno {}` is not supported when configuration file contains an \"importMap\" field. ",
@@ -434,11 +363,26 @@ pub async fn add(
" at {}",
),
cmd_name,
- specifier
+ deno.display_path(),
);
}
}
+ let start_dir = cli_factory.cli_options()?.start_dir.dir_path();
+
+ // only prefer to add npm deps to `package.json` if there isn't a closer deno.json.
+ // example: if deno.json is in the CWD and package.json is in the parent, we should add
+ // npm deps to deno.json, since it's closer
+ let prefer_npm_config = match (npm_config.as_ref(), deno_config.as_ref()) {
+ (Some(npm), Some(deno)) => {
+ let npm_distance = path_distance(&npm.path, &start_dir);
+ let deno_distance = path_distance(&deno.path, &start_dir);
+ npm_distance <= deno_distance
+ }
+ (Some(_), None) => true,
+ (None, _) => false,
+ };
+
let http_client = cli_factory.http_client_provider();
let deps_http_cache = cli_factory.global_http_cache()?;
let mut deps_file_fetcher = FileFetcher::new(
@@ -449,16 +393,20 @@ pub async fn add(
Default::default(),
None,
);
+
+ let npmrc = cli_factory.cli_options().unwrap().npmrc();
+
deps_file_fetcher.set_download_log_level(log::Level::Trace);
let deps_file_fetcher = Arc::new(deps_file_fetcher);
let jsr_resolver = Arc::new(JsrFetchResolver::new(deps_file_fetcher.clone()));
- let npm_resolver = Arc::new(NpmFetchResolver::new(deps_file_fetcher));
+ let npm_resolver =
+ Arc::new(NpmFetchResolver::new(deps_file_fetcher, npmrc.clone()));
let mut selected_packages = Vec::with_capacity(add_flags.packages.len());
let mut package_reqs = Vec::with_capacity(add_flags.packages.len());
for entry_text in add_flags.packages.iter() {
- let req = AddPackageReq::parse(entry_text).with_context(|| {
+ let req = AddRmPackageReq::parse(entry_text).with_context(|| {
format!("Failed to parse package required: {}", entry_text)
})?;
@@ -509,15 +457,32 @@ pub async fn add(
match package_and_version {
PackageAndVersion::NotFound {
package: package_name,
- found_npm_package,
+ help,
package_req,
- } => {
- if found_npm_package {
- bail!("{} was not found, but a matching npm package exists. Did you mean `{}`?", crate::colors::red(package_name), crate::colors::yellow(format!("deno {cmd_name} npm:{package_req}")));
- } else {
- bail!("{} was not found.", crate::colors::red(package_name));
+ } => match help {
+ Some(NotFoundHelp::NpmPackage) => {
+ bail!(
+ "{} was not found, but a matching npm package exists. Did you mean `{}`?",
+ crate::colors::red(package_name),
+ crate::colors::yellow(format!("deno {cmd_name} npm:{package_req}"))
+ );
}
- }
+ Some(NotFoundHelp::JsrPackage) => {
+ bail!(
+ "{} was not found, but a matching jsr package exists. Did you mean `{}`?",
+ crate::colors::red(package_name),
+ crate::colors::yellow(format!("deno {cmd_name} jsr:{package_req}"))
+ )
+ }
+ Some(NotFoundHelp::PreReleaseVersion(version)) => {
+ bail!(
+ "{} has only pre-release versions available. Try specifying a version: `{}`",
+ crate::colors::red(&package_name),
+ crate::colors::yellow(format!("deno {cmd_name} {package_name}@^{version}"))
+ )
+ }
+ None => bail!("{} was not found.", crate::colors::red(package_name)),
+ },
PackageAndVersion::Selected(selected) => {
selected_packages.push(selected);
}
@@ -533,7 +498,7 @@ pub async fn add(
selected_package.selected_version
);
- if selected_package.package_name.starts_with("npm:") {
+ if selected_package.package_name.starts_with("npm:") && prefer_npm_config {
if let Some(npm) = &mut npm_config {
npm.add(selected_package, dev);
} else {
@@ -546,18 +511,11 @@ pub async fn add(
}
}
- let mut commit_futures = vec![];
if let Some(npm) = npm_config {
- commit_futures.push(npm.commit());
+ npm.commit()?;
}
if let Some(deno) = deno_config {
- commit_futures.push(deno.commit());
- }
- let commit_futures =
- deno_core::futures::future::join_all(commit_futures).await;
-
- for result in commit_futures {
- result.context("Failed to update configuration file")?;
+ deno.commit()?;
}
npm_install_after_modification(flags, Some(jsr_resolver)).await?;
@@ -572,87 +530,161 @@ struct SelectedPackage {
selected_version: String,
}
+enum NotFoundHelp {
+ NpmPackage,
+ JsrPackage,
+ PreReleaseVersion(Version),
+}
+
enum PackageAndVersion {
NotFound {
package: String,
- found_npm_package: bool,
package_req: PackageReq,
+ help: Option<NotFoundHelp>,
},
Selected(SelectedPackage),
}
+fn best_version<'a>(
+ versions: impl Iterator<Item = &'a Version>,
+) -> Option<&'a Version> {
+ let mut maybe_best_version: Option<&Version> = None;
+ for version in versions {
+ let is_best_version = maybe_best_version
+ .as_ref()
+ .map(|best_version| (*best_version).cmp(version).is_lt())
+ .unwrap_or(true);
+ if is_best_version {
+ maybe_best_version = Some(version);
+ }
+ }
+ maybe_best_version
+}
+
+trait PackageInfoProvider {
+ const SPECIFIER_PREFIX: &str;
+ /// The help to return if a package is found by this provider
+ const HELP: NotFoundHelp;
+ async fn req_to_nv(&self, req: &PackageReq) -> Option<PackageNv>;
+ async fn latest_version<'a>(&self, req: &PackageReq) -> Option<Version>;
+}
+
+impl PackageInfoProvider for Arc<JsrFetchResolver> {
+ const HELP: NotFoundHelp = NotFoundHelp::JsrPackage;
+ const SPECIFIER_PREFIX: &str = "jsr";
+ async fn req_to_nv(&self, req: &PackageReq) -> Option<PackageNv> {
+ (**self).req_to_nv(req).await
+ }
+
+ async fn latest_version<'a>(&self, req: &PackageReq) -> Option<Version> {
+ let info = self.package_info(&req.name).await?;
+ best_version(
+ info
+ .versions
+ .iter()
+ .filter(|(_, version_info)| !version_info.yanked)
+ .map(|(version, _)| version),
+ )
+ .cloned()
+ }
+}
+
+impl PackageInfoProvider for Arc<NpmFetchResolver> {
+ const HELP: NotFoundHelp = NotFoundHelp::NpmPackage;
+ const SPECIFIER_PREFIX: &str = "npm";
+ async fn req_to_nv(&self, req: &PackageReq) -> Option<PackageNv> {
+ (**self).req_to_nv(req).await
+ }
+
+ async fn latest_version<'a>(&self, req: &PackageReq) -> Option<Version> {
+ let info = self.package_info(&req.name).await?;
+ best_version(info.versions.keys()).cloned()
+ }
+}
+
async fn find_package_and_select_version_for_req(
jsr_resolver: Arc<JsrFetchResolver>,
npm_resolver: Arc<NpmFetchResolver>,
- add_package_req: AddPackageReq,
+ add_package_req: AddRmPackageReq,
) -> Result<PackageAndVersion, AnyError> {
- match add_package_req.value {
- AddPackageReqValue::Jsr(req) => {
- let jsr_prefixed_name = format!("jsr:{}", &req.name);
- let Some(nv) = jsr_resolver.req_to_nv(&req).await else {
- if npm_resolver.req_to_nv(&req).await.is_some() {
+ async fn select<T: PackageInfoProvider, S: PackageInfoProvider>(
+ main_resolver: T,
+ fallback_resolver: S,
+ add_package_req: AddRmPackageReq,
+ ) -> Result<PackageAndVersion, AnyError> {
+ let req = match &add_package_req.value {
+ AddRmPackageReqValue::Jsr(req) => req,
+ AddRmPackageReqValue::Npm(req) => req,
+ };
+ let prefixed_name = format!("{}:{}", T::SPECIFIER_PREFIX, req.name);
+ let help_if_found_in_fallback = S::HELP;
+ let Some(nv) = main_resolver.req_to_nv(req).await else {
+ if fallback_resolver.req_to_nv(req).await.is_some() {
+ // it's in the other registry
+ return Ok(PackageAndVersion::NotFound {
+ package: prefixed_name,
+ help: Some(help_if_found_in_fallback),
+ package_req: req.clone(),
+ });
+ }
+ if req.version_req.version_text() == "*" {
+ if let Some(pre_release_version) =
+ main_resolver.latest_version(req).await
+ {
return Ok(PackageAndVersion::NotFound {
- package: jsr_prefixed_name,
- found_npm_package: true,
- package_req: req,
+ package: prefixed_name,
+ package_req: req.clone(),
+ help: Some(NotFoundHelp::PreReleaseVersion(
+ pre_release_version.clone(),
+ )),
});
}
+ }
- return Ok(PackageAndVersion::NotFound {
- package: jsr_prefixed_name,
- found_npm_package: false,
- package_req: req,
- });
- };
- let range_symbol = if req.version_req.version_text().starts_with('~') {
- '~'
- } else {
- '^'
- };
- Ok(PackageAndVersion::Selected(SelectedPackage {
- import_name: add_package_req.alias,
- package_name: jsr_prefixed_name,
- version_req: format!("{}{}", range_symbol, &nv.version),
- selected_version: nv.version.to_string(),
- }))
+ return Ok(PackageAndVersion::NotFound {
+ package: prefixed_name,
+ help: None,
+ package_req: req.clone(),
+ });
+ };
+ let range_symbol = if req.version_req.version_text().starts_with('~') {
+ "~"
+ } else if req.version_req.version_text() == nv.version.to_string() {
+ ""
+ } else {
+ "^"
+ };
+ Ok(PackageAndVersion::Selected(SelectedPackage {
+ import_name: add_package_req.alias,
+ package_name: prefixed_name,
+ version_req: format!("{}{}", range_symbol, &nv.version),
+ selected_version: nv.version.to_string(),
+ }))
+ }
+
+ match &add_package_req.value {
+ AddRmPackageReqValue::Jsr(_) => {
+ select(jsr_resolver, npm_resolver, add_package_req).await
}
- AddPackageReqValue::Npm(req) => {
- let npm_prefixed_name = format!("npm:{}", &req.name);
- let Some(nv) = npm_resolver.req_to_nv(&req).await else {
- return Ok(PackageAndVersion::NotFound {
- package: npm_prefixed_name,
- found_npm_package: false,
- package_req: req,
- });
- };
- let range_symbol = if req.version_req.version_text().starts_with('~') {
- '~'
- } else {
- '^'
- };
- Ok(PackageAndVersion::Selected(SelectedPackage {
- import_name: add_package_req.alias,
- package_name: npm_prefixed_name,
- version_req: format!("{}{}", range_symbol, &nv.version),
- selected_version: nv.version.to_string(),
- }))
+ AddRmPackageReqValue::Npm(_) => {
+ select(npm_resolver, jsr_resolver, add_package_req).await
}
}
}
#[derive(Debug, PartialEq, Eq)]
-enum AddPackageReqValue {
+enum AddRmPackageReqValue {
Jsr(PackageReq),
Npm(PackageReq),
}
#[derive(Debug, PartialEq, Eq)]
-struct AddPackageReq {
+pub struct AddRmPackageReq {
alias: String,
- value: AddPackageReqValue,
+ value: AddRmPackageReqValue,
}
-impl AddPackageReq {
+impl AddRmPackageReq {
pub fn parse(entry_text: &str) -> Result<Result<Self, PackageReq>, AnyError> {
enum Prefix {
Jsr,
@@ -707,9 +739,9 @@ impl AddPackageReq {
let req_ref =
JsrPackageReqReference::from_str(&format!("jsr:{}", entry_text))?;
let package_req = req_ref.into_inner().req;
- Ok(Ok(AddPackageReq {
+ Ok(Ok(AddRmPackageReq {
alias: maybe_alias.unwrap_or_else(|| package_req.name.to_string()),
- value: AddPackageReqValue::Jsr(package_req),
+ value: AddRmPackageReqValue::Jsr(package_req),
}))
}
Prefix::Npm => {
@@ -727,49 +759,48 @@ impl AddPackageReq {
deno_semver::RangeSetOrTag::Tag("latest".into()),
);
}
- Ok(Ok(AddPackageReq {
+ Ok(Ok(AddRmPackageReq {
alias: maybe_alias.unwrap_or_else(|| package_req.name.to_string()),
- value: AddPackageReqValue::Npm(package_req),
+ value: AddRmPackageReqValue::Npm(package_req),
}))
}
}
}
}
-fn generate_imports(mut packages_to_version: Vec<(String, String)>) -> String {
- packages_to_version.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
- let mut contents = vec![];
- let len = packages_to_version.len();
- for (index, (package, version)) in packages_to_version.iter().enumerate() {
- // TODO(bartlomieju): fix it, once we start support specifying version on the cli
- contents.push(format!("\"{}\": \"{}\"", package, version));
- if index != len - 1 {
- contents.push(",".to_string());
- }
- }
- contents.join("\n")
-}
-
pub async fn remove(
flags: Arc<Flags>,
remove_flags: RemoveFlags,
) -> Result<(), AnyError> {
- let (_, npm_config, deno_config) = load_configs(&flags)?;
+ let (_, npm_config, deno_config) = load_configs(&flags, || false)?;
- let mut configs = [
- ConfigUpdater::maybe_new(npm_config).await?,
- ConfigUpdater::maybe_new(deno_config).await?,
- ];
+ let mut configs = [npm_config, deno_config];
let mut removed_packages = vec![];
for package in &remove_flags.packages {
- let mut removed = false;
+ let req = AddRmPackageReq::parse(package).with_context(|| {
+ format!("Failed to parse package required: {}", package)
+ })?;
+ let mut parsed_pkg_name = None;
for config in configs.iter_mut().flatten() {
- removed |= config.remove(package);
+ match &req {
+ Ok(rm_pkg) => {
+ if config.remove(&rm_pkg.alias) && parsed_pkg_name.is_none() {
+ parsed_pkg_name = Some(rm_pkg.alias.clone());
+ }
+ }
+ Err(pkg) => {
+ // An alias or a package name without registry/version
+ // constraints. Try to remove the package anyway.
+ if config.remove(&pkg.name) && parsed_pkg_name.is_none() {
+ parsed_pkg_name = Some(pkg.name.clone());
+ }
+ }
+ }
}
- if removed {
- removed_packages.push(package.clone());
+ if let Some(pkg) = parsed_pkg_name {
+ removed_packages.push(pkg);
}
}
@@ -780,7 +811,7 @@ pub async fn remove(
log::info!("Removed {}", crate::colors::green(package));
}
for config in configs.into_iter().flatten() {
- config.commit().await?;
+ config.commit()?;
}
npm_install_after_modification(flags, None).await?;
@@ -807,88 +838,11 @@ async fn npm_install_after_modification(
// npm install
cache_deps::cache_top_level_deps(&cli_factory, jsr_resolver).await?;
- Ok(())
-}
-
-fn update_config_file_content<
- I: IntoIterator<Item = (&'static str, Option<String>)>,
->(
- obj: &jsonc_parser::ast::Object,
- config_file_contents: &str,
- fmt_options: FmtOptionsConfig,
- entries: I,
- file_name: &str,
-) -> String {
- let mut text_changes = vec![];
- for (key, value) in entries {
- match obj.properties.iter().enumerate().find_map(|(idx, k)| {
- if k.name.as_str() == key {
- Some((idx, k))
- } else {
- None
- }
- }) {
- Some((
- idx,
- ObjectProp {
- value: Value::Object(lit),
- range,
- ..
- },
- )) => {
- if let Some(value) = value {
- text_changes.push(TextChange {
- range: (lit.range.start + 1)..(lit.range.end - 1),
- new_text: value,
- })
- } else {
- text_changes.push(TextChange {
- // remove field entirely, making sure to
- // remove the comma if it's not the last field
- range: range.start..(if idx == obj.properties.len() - 1 {
- range.end
- } else {
- obj.properties[idx + 1].range.start
- }),
- new_text: "".to_string(),
- })
- }
- }
-
- // need to add field
- None => {
- if let Some(value) = value {
- let insert_position = obj.range.end - 1;
- text_changes.push(TextChange {
- range: insert_position..insert_position,
- // NOTE(bartlomieju): adding `\n` here to force the formatter to always
- // produce a config file that is multiline, like so:
- // ```
- // {
- // "imports": {
- // "<package_name>": "<registry>:<package_name>@<semver>"
- // }
- // }
- new_text: format!("\"{key}\": {{\n {value} }}"),
- })
- }
- }
- // we verified the shape of `imports`/`dependencies` above
- Some(_) => unreachable!(),
- }
+ if let Some(lockfile) = cli_factory.cli_options()?.maybe_lockfile() {
+ lockfile.write_if_changed()?;
}
- let new_text =
- deno_ast::apply_text_changes(config_file_contents, text_changes);
-
- crate::tools::fmt::format_json(
- &PathBuf::from(file_name),
- &new_text,
- &fmt_options,
- )
- .ok()
- .map(|formatted_text| formatted_text.unwrap_or_else(|| new_text.clone()))
- .unwrap_or(new_text)
+ Ok(())
}
#[cfg(test)]
@@ -898,48 +852,52 @@ mod test {
#[test]
fn test_parse_add_package_req() {
assert_eq!(
- AddPackageReq::parse("jsr:foo").unwrap().unwrap(),
- AddPackageReq {
+ AddRmPackageReq::parse("jsr:foo").unwrap().unwrap(),
+ AddRmPackageReq {
alias: "foo".to_string(),
- value: AddPackageReqValue::Jsr(PackageReq::from_str("foo").unwrap())
+ value: AddRmPackageReqValue::Jsr(PackageReq::from_str("foo").unwrap())
}
);
assert_eq!(
- AddPackageReq::parse("alias@jsr:foo").unwrap().unwrap(),
- AddPackageReq {
+ AddRmPackageReq::parse("alias@jsr:foo").unwrap().unwrap(),
+ AddRmPackageReq {
alias: "alias".to_string(),
- value: AddPackageReqValue::Jsr(PackageReq::from_str("foo").unwrap())
+ value: AddRmPackageReqValue::Jsr(PackageReq::from_str("foo").unwrap())
}
);
assert_eq!(
- AddPackageReq::parse("@alias/pkg@npm:foo").unwrap().unwrap(),
- AddPackageReq {
+ AddRmPackageReq::parse("@alias/pkg@npm:foo")
+ .unwrap()
+ .unwrap(),
+ AddRmPackageReq {
alias: "@alias/pkg".to_string(),
- value: AddPackageReqValue::Npm(
+ value: AddRmPackageReqValue::Npm(
PackageReq::from_str("foo@latest").unwrap()
)
}
);
assert_eq!(
- AddPackageReq::parse("@alias/pkg@jsr:foo").unwrap().unwrap(),
- AddPackageReq {
+ AddRmPackageReq::parse("@alias/pkg@jsr:foo")
+ .unwrap()
+ .unwrap(),
+ AddRmPackageReq {
alias: "@alias/pkg".to_string(),
- value: AddPackageReqValue::Jsr(PackageReq::from_str("foo").unwrap())
+ value: AddRmPackageReqValue::Jsr(PackageReq::from_str("foo").unwrap())
}
);
assert_eq!(
- AddPackageReq::parse("alias@jsr:foo@^1.5.0")
+ AddRmPackageReq::parse("alias@jsr:foo@^1.5.0")
.unwrap()
.unwrap(),
- AddPackageReq {
+ AddRmPackageReq {
alias: "alias".to_string(),
- value: AddPackageReqValue::Jsr(
+ value: AddRmPackageReqValue::Jsr(
PackageReq::from_str("foo@^1.5.0").unwrap()
)
}
);
assert_eq!(
- AddPackageReq::parse("@scope/pkg@tag")
+ AddRmPackageReq::parse("@scope/pkg@tag")
.unwrap()
.unwrap_err()
.to_string(),
diff --git a/cli/tools/registry/pm/cache_deps.rs b/cli/tools/registry/pm/cache_deps.rs
index c8258e600..d3c8da868 100644
--- a/cli/tools/registry/pm/cache_deps.rs
+++ b/cli/tools/registry/pm/cache_deps.rs
@@ -44,7 +44,11 @@ pub async fn cache_top_level_deps(
let mut seen_reqs = std::collections::HashSet::new();
- for entry in import_map.imports().entries() {
+ for entry in import_map.imports().entries().chain(
+ import_map
+ .scopes()
+ .flat_map(|scope| scope.imports.entries()),
+ ) {
let Some(specifier) = entry.value else {
continue;
};
@@ -75,6 +79,13 @@ pub async fn cache_top_level_deps(
if entry.key.ends_with('/') && specifier.as_str().ends_with('/') {
continue;
}
+ if specifier.scheme() == "file" {
+ if let Ok(path) = specifier.to_file_path() {
+ if !path.is_file() {
+ continue;
+ }
+ }
+ }
roots.push(specifier.clone());
}
}
@@ -82,10 +93,6 @@ pub async fn cache_top_level_deps(
while let Some(info_future) = info_futures.next().await {
if let Some((specifier, info)) = info_future {
- if info.export(".").is_some() {
- roots.push(specifier.clone());
- continue;
- }
let exports = info.exports();
for (k, _) in exports {
if let Ok(spec) = specifier.join(k) {
diff --git a/cli/tools/registry/tar.rs b/cli/tools/registry/tar.rs
index aca125e00..6d1801ce6 100644
--- a/cli/tools/registry/tar.rs
+++ b/cli/tools/registry/tar.rs
@@ -120,7 +120,7 @@ fn resolve_content_maybe_unfurling(
| MediaType::Unknown
| MediaType::Json
| MediaType::Wasm
- | MediaType::TsBuildInfo => {
+ | MediaType::Css => {
// not unfurlable data
return Ok(data);
}