From c7cc6aeecd4184c53836f4636efdb902231ee965 Mon Sep 17 00:00:00 2001 From: jia wei Date: Tue, 28 Jan 2025 23:25:50 +0800 Subject: [PATCH] fix(npmrc): merge `.npmrc` in user's homedir and project (#27119) --- resolvers/deno/npmrc.rs | 118 +++++++++++++----- .../npm/npmrc_homedir_package_both/.npmrc | 4 + .../npmrc_homedir_package_both/__test__.jsonc | 10 ++ .../npmrc_homedir_package_both/install.out | 8 ++ .../npmrc_homedir_package_both/subdir/.npmrc | 2 + .../npmrc_homedir_package_both/subdir/main.js | 8 ++ .../subdir/package.json | 8 ++ 7 files changed, 124 insertions(+), 34 deletions(-) create mode 100644 tests/specs/npm/npmrc_homedir_package_both/.npmrc create mode 100644 tests/specs/npm/npmrc_homedir_package_both/__test__.jsonc create mode 100644 tests/specs/npm/npmrc_homedir_package_both/install.out create mode 100644 tests/specs/npm/npmrc_homedir_package_both/subdir/.npmrc create mode 100644 tests/specs/npm/npmrc_homedir_package_both/subdir/main.js create mode 100644 tests/specs/npm/npmrc_homedir_package_both/subdir/package.json diff --git a/resolvers/deno/npmrc.rs b/resolvers/deno/npmrc.rs index 7fb5dd482f..2b402cf592 100644 --- a/resolvers/deno/npmrc.rs +++ b/resolvers/deno/npmrc.rs @@ -1,6 +1,7 @@ // Copyright 2018-2025 the Deno authors. MIT license. use std::borrow::Cow; +use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; @@ -103,7 +104,7 @@ fn discover_npmrc( sys: &impl EnvVar, source: &str, path: &Path, - ) -> Result { + ) -> Result { let npmrc = NpmRc::parse(source, &|name| sys.env_var(name).ok()).map_err( |source| { NpmRcParseError { @@ -113,52 +114,50 @@ fn discover_npmrc( } }, )?; - let resolved = - npmrc - .as_resolved(&npm_registry_url(sys)) - .map_err(|source| NpmRcOptionsResolveError { - path: path.to_path_buf(), - source, - })?; log::debug!(".npmrc found at: '{}'", path.display()); - Ok(resolved) + Ok(npmrc) } - // 1. Try `.npmrc` next to `package.json` - if let Some(package_json_path) = maybe_package_json_path { - if let Some(package_json_dir) = package_json_path.parent() { - if let Some((source, path)) = try_to_read_npmrc(sys, package_json_dir)? { - return try_to_parse_npmrc(sys, &source, &path) - .map(|r| (r, Some(path))); + fn merge_npm_rc(project_rc: NpmRc, home_rc: NpmRc) -> NpmRc { + fn merge_maps( + mut project: HashMap, + home: HashMap, + ) -> HashMap { + for (key, value) in home { + project.entry(key).or_insert(value); } + project + } + + NpmRc { + registry: project_rc.registry.or(home_rc.registry), + scope_registries: merge_maps( + project_rc.scope_registries, + home_rc.scope_registries, + ), + registry_configs: merge_maps( + project_rc.registry_configs, + home_rc.registry_configs, + ), } } - // 2. Try `.npmrc` next to `deno.json(c)` - if let Some(deno_json_path) = maybe_deno_json_path { - if let Some(deno_json_dir) = deno_json_path.parent() { - if let Some((source, path)) = try_to_read_npmrc(sys, deno_json_dir)? { - return try_to_parse_npmrc(sys, &source, &path) - .map(|r| (r, Some(path))); - } - } - } + let mut home_npmrc = None; + let mut project_npmrc = None; - // TODO(bartlomieju): update to read both files - one in the project root and one and - // home dir and then merge them. - // 3. Try `.npmrc` in the user's home directory + // 1. Try `.npmrc` in the user's home directory if let Some(home_dir) = sys.env_home_dir() { match try_to_read_npmrc(sys, &home_dir) { Ok(Some((source, path))) => { - return try_to_parse_npmrc(sys, &source, &path) - .map(|r| (r, Some(path))); + let npmrc = try_to_parse_npmrc(sys, &source, &path)?; + home_npmrc = Some((path, npmrc)); } Ok(None) => {} Err(err) if err.source.kind() == std::io::ErrorKind::PermissionDenied => { log::debug!( - "Skipping .npmrc in home directory due to permission denied error. {:#}", - err - ); + "Skipping .npmrc in home directory due to permission denied error. {:#}", + err + ); } Err(err) => { return Err(err.into()); @@ -166,8 +165,59 @@ fn discover_npmrc( } } - log::debug!("No .npmrc file found"); - Ok((create_default_npmrc(sys), None)) + // 2. Try `.npmrc` next to `package.json` + if let Some(package_json_path) = maybe_package_json_path { + if let Some(package_json_dir) = package_json_path.parent() { + if let Some((source, path)) = try_to_read_npmrc(sys, package_json_dir)? { + let npmrc = try_to_parse_npmrc(sys, &source, &path)?; + project_npmrc = Some((path, npmrc)); + } + } + } + + // 3. Try `.npmrc` next to `deno.json(c)` when not found `package.json` + if project_npmrc.is_none() { + if let Some(deno_json_path) = maybe_deno_json_path { + if let Some(deno_json_dir) = deno_json_path.parent() { + if let Some((source, path)) = try_to_read_npmrc(sys, deno_json_dir)? { + let npmrc = try_to_parse_npmrc(sys, &source, &path)?; + project_npmrc = Some((path, npmrc)); + } + } + } + } + + let resolve_npmrc = |path: PathBuf, npm_rc: NpmRc| { + Ok(( + npm_rc + .as_resolved(&npm_registry_url(sys)) + .map_err(|source| NpmRcOptionsResolveError { + path: path.to_path_buf(), + source, + })?, + Some(path), + )) + }; + + match (home_npmrc, project_npmrc) { + (None, None) => { + log::debug!("No .npmrc file found"); + Ok((create_default_npmrc(sys), None)) + } + (None, Some((npmrc_path, project_rc))) => { + log::debug!("Only project .npmrc file found"); + resolve_npmrc(npmrc_path, project_rc) + } + (Some((npmrc_path, home_rc)), None) => { + log::debug!("Only home .npmrc file found"); + resolve_npmrc(npmrc_path, home_rc) + } + (Some((_, home_rc)), Some((npmrc_path, project_rc))) => { + log::debug!("Both home and project .npmrc files found"); + let merged_npmrc = merge_npm_rc(project_rc, home_rc); + resolve_npmrc(npmrc_path, merged_npmrc) + } + } } pub fn create_default_npmrc(sys: &impl EnvVar) -> ResolvedNpmRc { diff --git a/tests/specs/npm/npmrc_homedir_package_both/.npmrc b/tests/specs/npm/npmrc_homedir_package_both/.npmrc new file mode 100644 index 0000000000..13552ad61f --- /dev/null +++ b/tests/specs/npm/npmrc_homedir_package_both/.npmrc @@ -0,0 +1,4 @@ +@denotest:registry=http://localhost:4261/ +//localhost:4261/:_authToken=private-reg-token +@denotest2:registry=http://localhost:4262/ +//localhost:4262/:_authToken=private-reg-token2 diff --git a/tests/specs/npm/npmrc_homedir_package_both/__test__.jsonc b/tests/specs/npm/npmrc_homedir_package_both/__test__.jsonc new file mode 100644 index 0000000000..45a3c6770c --- /dev/null +++ b/tests/specs/npm/npmrc_homedir_package_both/__test__.jsonc @@ -0,0 +1,10 @@ +{ + "tempDir": true, + "envs": { + "HOME": "$PWD/../", + "USERPROFILE": "$PWD\\..\\" + }, + "cwd": "subdir", + "args": "install", + "output": "install.out" +} diff --git a/tests/specs/npm/npmrc_homedir_package_both/install.out b/tests/specs/npm/npmrc_homedir_package_both/install.out new file mode 100644 index 0000000000..4d87e5340e --- /dev/null +++ b/tests/specs/npm/npmrc_homedir_package_both/install.out @@ -0,0 +1,8 @@ +[UNORDERED_START] +Download http://localhost:4262/@denotest2%2fbasic +Download http://localhost:4261/@denotest%2fbasic +Download http://localhost:4262/@denotest2/basic/1.0.0.tgz +Download http://localhost:4261/@denotest/basic/1.0.0.tgz +Initialize @denotest2/basic@1.0.0 +Initialize @denotest/basic@1.0.0 +[UNORDERED_END] diff --git a/tests/specs/npm/npmrc_homedir_package_both/subdir/.npmrc b/tests/specs/npm/npmrc_homedir_package_both/subdir/.npmrc new file mode 100644 index 0000000000..a425e0aaf6 --- /dev/null +++ b/tests/specs/npm/npmrc_homedir_package_both/subdir/.npmrc @@ -0,0 +1,2 @@ +@denotest:registry=http://localhost:4261/ +@denotest2:registry=http://localhost:4262/ diff --git a/tests/specs/npm/npmrc_homedir_package_both/subdir/main.js b/tests/specs/npm/npmrc_homedir_package_both/subdir/main.js new file mode 100644 index 0000000000..66b3936360 --- /dev/null +++ b/tests/specs/npm/npmrc_homedir_package_both/subdir/main.js @@ -0,0 +1,8 @@ +import { getValue, setValue } from "@denotest/basic"; +import * as test from "@denotest2/basic"; + +console.log(getValue()); +setValue(42); +console.log(getValue()); + +console.log(test.getValue()); diff --git a/tests/specs/npm/npmrc_homedir_package_both/subdir/package.json b/tests/specs/npm/npmrc_homedir_package_both/subdir/package.json new file mode 100644 index 0000000000..63356aa2bb --- /dev/null +++ b/tests/specs/npm/npmrc_homedir_package_both/subdir/package.json @@ -0,0 +1,8 @@ +{ + "name": "npmrc_homedir_package_both", + "version": "0.0.1", + "dependencies": { + "@denotest/basic": "1.0.0", + "@denotest2/basic": "1.0.0" + } +}