summaryrefslogtreecommitdiff
path: root/cli/npm/byonm.rs
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2024-09-04 16:00:44 +0200
committerGitHub <noreply@github.com>2024-09-04 14:00:44 +0000
commitc6d1b0a1ccf45b7819b1e6f1efe8687b240f495a (patch)
tree6be2c0c611e4aee950402a34aaedd1c9b6bcaac3 /cli/npm/byonm.rs
parent13911eb8efb77bd14a80412072aecb664aa55fd5 (diff)
fix(byonm): resolve npm deps of jsr deps (#25399)
This allows using npm deps of jsr deps without having to add them to the root package.json. Works by taking the package requirement and scanning the `node_modules/.deno` directory for the best matching package, so it relies on deno's node_modules structure. Additionally to make the transition from package.json to deno.json easier, Deno now: 1. Installs npm deps in a deno.json at the same time as installing npm deps from a package.json. 2. Uses the alias in the import map for `node_modules/<alias>` for better package.json compatiblity.
Diffstat (limited to 'cli/npm/byonm.rs')
-rw-r--r--cli/npm/byonm.rs143
1 files changed, 111 insertions, 32 deletions
diff --git a/cli/npm/byonm.rs b/cli/npm/byonm.rs
index e62650d8a..3249b2ed1 100644
--- a/cli/npm/byonm.rs
+++ b/cli/npm/byonm.rs
@@ -17,6 +17,7 @@ use deno_runtime::deno_node::NodeRequireResolver;
use deno_runtime::deno_node::NpmProcessStateProvider;
use deno_runtime::deno_node::PackageJson;
use deno_semver::package::PackageReq;
+use deno_semver::Version;
use node_resolver::errors::PackageFolderResolveError;
use node_resolver::errors::PackageFolderResolveIoError;
use node_resolver::errors::PackageJsonLoadError;
@@ -29,6 +30,7 @@ use crate::args::NpmProcessStateKind;
use crate::util::fs::canonicalize_path_maybe_not_exists_with_fs;
use deno_runtime::fs_util::specifier_to_file_path;
+use super::managed::normalize_pkg_name_for_node_modules_deno_folder;
use super::CliNpmResolver;
use super::InnerCliNpmResolverRef;
@@ -60,9 +62,7 @@ impl ByonmCliNpmResolver {
) -> Result<Option<Arc<PackageJson>>, PackageJsonLoadError> {
load_pkg_json(&DenoPkgJsonFsAdapter(self.fs.as_ref()), path)
}
-}
-impl ByonmCliNpmResolver {
/// Finds the ancestor package.json that contains the specified dependency.
pub fn find_ancestor_package_json_with_dep(
&self,
@@ -98,7 +98,7 @@ impl ByonmCliNpmResolver {
&self,
req: &PackageReq,
referrer: &ModuleSpecifier,
- ) -> Result<(Arc<PackageJson>, String), AnyError> {
+ ) -> Result<Option<(Arc<PackageJson>, String)>, AnyError> {
fn resolve_alias_from_pkg_json(
req: &PackageReq,
pkg_json: &PackageJson,
@@ -134,7 +134,7 @@ impl ByonmCliNpmResolver {
if let Some(alias) =
resolve_alias_from_pkg_json(req, pkg_json.as_ref())
{
- return Ok((pkg_json, alias));
+ return Ok(Some((pkg_json, alias)));
}
}
current_path = dir_path;
@@ -148,19 +148,65 @@ impl ByonmCliNpmResolver {
if let Some(pkg_json) = self.load_pkg_json(&root_pkg_json_path)? {
if let Some(alias) = resolve_alias_from_pkg_json(req, pkg_json.as_ref())
{
- return Ok((pkg_json, alias));
+ return Ok(Some((pkg_json, alias)));
}
}
}
- bail!(
- concat!(
- "Could not find a matching package for 'npm:{}' in a package.json file. ",
- "You must specify this as a package.json dependency when the ",
- "node_modules folder is not managed by Deno.",
- ),
- req,
+ Ok(None)
+ }
+
+ fn resolve_folder_in_root_node_modules(
+ &self,
+ req: &PackageReq,
+ ) -> Option<PathBuf> {
+ // now check if node_modules/.deno/ matches this constraint
+ let root_node_modules_dir = self.root_node_modules_dir.as_ref()?;
+ let node_modules_deno_dir = root_node_modules_dir.join(".deno");
+ let Ok(entries) = self.fs.read_dir_sync(&node_modules_deno_dir) else {
+ return None;
+ };
+ let search_prefix = format!(
+ "{}@",
+ normalize_pkg_name_for_node_modules_deno_folder(&req.name)
);
+ let mut best_version = None;
+
+ // example entries:
+ // - @denotest+add@1.0.0
+ // - @denotest+add@1.0.0_1
+ for entry in entries {
+ if !entry.is_directory {
+ continue;
+ }
+ let Some(version_and_copy_idx) = entry.name.strip_prefix(&search_prefix)
+ else {
+ continue;
+ };
+ let version = version_and_copy_idx
+ .rsplit_once('_')
+ .map(|(v, _)| v)
+ .unwrap_or(version_and_copy_idx);
+ let Ok(version) = Version::parse_from_npm(version) else {
+ continue;
+ };
+ if req.version_req.matches(&version) {
+ if let Some((best_version_version, _)) = &best_version {
+ if version > *best_version_version {
+ best_version = Some((version, entry.name));
+ }
+ } else {
+ best_version = Some((version, entry.name));
+ }
+ }
+ }
+
+ best_version.map(|(_version, entry_name)| {
+ join_package_name(
+ &node_modules_deno_dir.join(entry_name).join("node_modules"),
+ &req.name,
+ )
+ })
}
}
@@ -288,29 +334,62 @@ impl CliNpmResolver for ByonmCliNpmResolver {
req: &PackageReq,
referrer: &ModuleSpecifier,
) -> Result<PathBuf, AnyError> {
- // resolve the pkg json and alias
- let (pkg_json, alias) =
- self.resolve_pkg_json_and_alias_for_req(req, referrer)?;
- // now try node resolution
- for ancestor in pkg_json.path.parent().unwrap().ancestors() {
- let node_modules_folder = ancestor.join("node_modules");
- let sub_dir = join_package_name(&node_modules_folder, &alias);
- if self.fs.is_dir_sync(&sub_dir) {
- return Ok(canonicalize_path_maybe_not_exists_with_fs(
- &sub_dir,
- self.fs.as_ref(),
- )?);
+ fn node_resolve_dir(
+ fs: &dyn FileSystem,
+ alias: &str,
+ start_dir: &Path,
+ ) -> Result<Option<PathBuf>, AnyError> {
+ for ancestor in start_dir.ancestors() {
+ let node_modules_folder = ancestor.join("node_modules");
+ let sub_dir = join_package_name(&node_modules_folder, alias);
+ if fs.is_dir_sync(&sub_dir) {
+ return Ok(Some(canonicalize_path_maybe_not_exists_with_fs(
+ &sub_dir, fs,
+ )?));
+ }
}
+ Ok(None)
}
- bail!(
- concat!(
- "Could not find \"{}\" in a node_modules folder. ",
- "Deno expects the node_modules/ directory to be up to date. ",
- "Did you forget to run `deno install`?"
- ),
- alias,
- );
+ // now attempt to resolve if it's found in any package.json
+ let maybe_pkg_json_and_alias =
+ self.resolve_pkg_json_and_alias_for_req(req, referrer)?;
+ match maybe_pkg_json_and_alias {
+ Some((pkg_json, alias)) => {
+ // now try node resolution
+ if let Some(resolved) =
+ node_resolve_dir(self.fs.as_ref(), &alias, pkg_json.dir_path())?
+ {
+ return Ok(resolved);
+ }
+
+ bail!(
+ concat!(
+ "Could not find \"{}\" in a node_modules folder. ",
+ "Deno expects the node_modules/ directory to be up to date. ",
+ "Did you forget to run `deno install`?"
+ ),
+ alias,
+ );
+ }
+ None => {
+ // now check if node_modules/.deno/ matches this constraint
+ if let Some(folder) = self.resolve_folder_in_root_node_modules(req) {
+ return Ok(folder);
+ }
+
+ bail!(
+ concat!(
+ "Could not find a matching package for 'npm:{}' in the node_modules ",
+ "directory. Ensure you have all your JSR and npm dependencies listed ",
+ "in your deno.json or package.json, then run `deno install`. Alternatively, ",
+ r#"turn on auto-install by specifying `"nodeModulesDir": "auto"` in your "#,
+ "deno.json file."
+ ),
+ req,
+ );
+ }
+ }
}
fn check_state_hash(&self) -> Option<u64> {