Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(npmrc): merge .npmrc in user's homedir and project #27119

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 92 additions & 22 deletions cli/args/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,58 +575,128 @@ fn discover_npmrc(
fn try_to_parse_npmrc(
source: String,
path: &Path,
) -> Result<Arc<ResolvedNpmRc>, AnyError> {
) -> Result<ResolvedNpmRc, AnyError> {
let npmrc = NpmRc::parse(&source, &get_env_var).with_context(|| {
format!("Failed to parse .npmrc at {}", path.display())
})?;
let resolved = npmrc
.as_resolved(npm_registry_url())
.context("Failed to resolve .npmrc options")?;
log::debug!(".npmrc found at: '{}'", path.display());
Ok(Arc::new(resolved))
Ok(resolved)
}

// 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(package_json_dir)? {
return try_to_parse_npmrc(source, &path).map(|r| (r, Some(path)));
fn merge_resolved_npm_rc(
rc1: ResolvedNpmRc,
rc2: ResolvedNpmRc,
) -> ResolvedNpmRc {
let mut merged_registry_configs = rc1.registry_configs.clone();
for (key, value) in rc2.registry_configs {
merged_registry_configs.entry(key).or_insert(value);
}

let mut merged_default_config = rc1.default_config.clone();
if let Some(host) = merged_default_config.registry_url.host_str() {
if let Some(config) = merged_registry_configs.get(host) {
merged_default_config.config = config.clone();
} else if let Some(config) =
merged_registry_configs.get(format!("{}/", host).as_str())
{
merged_default_config.config = config.clone();
}
}
}

// 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(deno_json_dir)? {
return try_to_parse_npmrc(source, &path).map(|r| (r, Some(path)));
let mut merged_scopes = rc1.scopes.clone();
for (key, value) in rc2.scopes {
merged_scopes.entry(key).or_insert(value);
}
for data in merged_scopes.values_mut() {
if let (Some(host), Some(port)) =
(data.registry_url.host_str(), data.registry_url.port())
{
let path = data.registry_url.path();
let url = format!("{}:{}{}", host, port, path);
if let Some(config) = merged_registry_configs.get(&url) {
data.config = config.clone();
}
}
}

ResolvedNpmRc {
default_config: merged_default_config,
scopes: merged_scopes,
registry_configs: merged_registry_configs,
}
}

// 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
let mut home_npmrc = None;
let mut project_npmrc = None;
let mut npmrc_path = None;

// 1. Try `.npmrc` in the user's home directory
if let Some(home_dir) = cache::home_dir() {
match try_to_read_npmrc(&home_dir) {
Ok(Some((source, path))) => {
return try_to_parse_npmrc(source, &path).map(|r| (r, Some(path)));
let npmrc = try_to_parse_npmrc(source, &path)?;
home_npmrc = Some(npmrc);
npmrc_path = Some(path);
}
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());
}
}
}

log::debug!("No .npmrc file found");
Ok((create_default_npmrc(), 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(package_json_dir)? {
let npmrc = try_to_parse_npmrc(source, &path)?;
project_npmrc = Some(npmrc);
npmrc_path = Some(path);
}
}
}

// 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(deno_json_dir)? {
let npmrc = try_to_parse_npmrc(source, &path)?;
project_npmrc = Some(npmrc);
npmrc_path = Some(path);
}
}
}
}

match (home_npmrc, project_npmrc) {
(None, None) => {
log::debug!("No .npmrc file found");
Ok((create_default_npmrc(), None))
}
(None, Some(project_rc)) => {
log::debug!("Only project file found");
Ok((project_rc.into(), npmrc_path))
}
(Some(home_rc), None) => {
log::debug!("Only home file found");
Ok((home_rc.into(), npmrc_path))
}
(Some(home_rc), Some(project_rc)) => {
log::debug!("Both home and project files found");
let merged_npmrc = merge_resolved_npm_rc(project_rc, home_rc);
Ok((merged_npmrc.into(), npmrc_path))
}
}
}

pub fn create_default_npmrc() -> Arc<ResolvedNpmRc> {
Expand Down
4 changes: 4 additions & 0 deletions tests/specs/npm/npmrc_homedir_package_both/.npmrc
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions tests/specs/npm/npmrc_homedir_package_both/__test__.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"tempDir": true,
"envs": {
"HOME": "$PWD/../",
"USERPROFILE": "$PWD\\..\\"
},
"cwd": "subdir",
"args": "install",
"output": "install.out"
}
8 changes: 8 additions & 0 deletions tests/specs/npm/npmrc_homedir_package_both/install.out
Original file line number Diff line number Diff line change
@@ -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/[email protected]
Initialize @denotest/[email protected]
[UNORDERED_END]
2 changes: 2 additions & 0 deletions tests/specs/npm/npmrc_homedir_package_both/subdir/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@denotest:registry=http://localhost:4261/
@denotest2:registry=http://localhost:4262/
8 changes: 8 additions & 0 deletions tests/specs/npm/npmrc_homedir_package_both/subdir/main.js
Original file line number Diff line number Diff line change
@@ -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());
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "npmrc_homedir_package_both",
"version": "0.0.1",
"dependencies": {
"@denotest/basic": "1.0.0",
"@denotest2/basic": "1.0.0"
}
}