FUTURE: initial support for .npmrc file (#23560)

This commit adds initial support for ".npmrc" files.

Currently we only discover ".npmrc" files next to "package.json" files
and discovering these files in user home dir is left for a follow up.

This pass supports "_authToken" and "_auth" configuration
for providing authentication.

LSP support has been left for a follow up PR.

Towards https://github.com/denoland/deno/issues/16105
This commit is contained in:
Bartek Iwańczuk 2024-05-23 22:26:23 +01:00 committed by GitHub
parent 5de30c5323
commit 959739f609
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 611 additions and 164 deletions

View file

@ -13,6 +13,8 @@ use ::import_map::ImportMap;
use deno_ast::SourceMapOption;
use deno_core::resolve_url_or_path;
use deno_graph::GraphKind;
use deno_npm::npm_rc::NpmRc;
use deno_npm::npm_rc::ResolvedNpmRc;
use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot;
use deno_npm::NpmSystemInfo;
use deno_runtime::deno_tls::RootCertStoreProvider;
@ -546,6 +548,83 @@ fn discover_package_json(
Ok(None)
}
/// Discover `.npmrc` file - currently we only support it next to `package.json`
/// or next to `deno.json`.
///
/// In the future we will need to support it in user directory or global directory
/// as per https://docs.npmjs.com/cli/v10/configuring-npm/npmrc#files.
fn discover_npmrc(
maybe_package_json_path: Option<PathBuf>,
maybe_deno_json_path: Option<PathBuf>,
) -> Result<Arc<ResolvedNpmRc>, AnyError> {
if !*DENO_FUTURE {
return Ok(create_default_npmrc());
}
const NPMRC_NAME: &str = ".npmrc";
fn get_env_var(var_name: &str) -> Option<String> {
std::env::var(var_name).ok()
}
fn try_to_read_npmrc(
dir: &Path,
) -> Result<Option<(String, PathBuf)>, AnyError> {
let path = dir.join(NPMRC_NAME);
let maybe_source = match std::fs::read_to_string(&path) {
Ok(source) => Some(source),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => None,
Err(err) => {
bail!("Error loading .npmrc at {}. {:#}", path.display(), err)
}
};
Ok(maybe_source.map(|source| (source, path)))
}
fn try_to_parse_npmrc(
source: String,
path: &Path,
) -> Result<Arc<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")?;
Ok(Arc::new(resolved))
}
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);
}
}
}
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);
}
}
}
log::debug!("No .npmrc file found");
Ok(create_default_npmrc())
}
pub fn create_default_npmrc() -> Arc<ResolvedNpmRc> {
Arc::new(ResolvedNpmRc {
default_config: deno_npm::npm_rc::RegistryConfigWithUrl {
registry_url: npm_registry_url().clone(),
config: Default::default(),
},
scopes: Default::default(),
})
}
struct CliRootCertStoreProvider {
cell: OnceCell<RootCertStore>,
maybe_root_path: Option<PathBuf>,
@ -722,6 +801,7 @@ pub struct CliOptions {
maybe_vendor_folder: Option<PathBuf>,
maybe_config_file: Option<ConfigFile>,
maybe_package_json: Option<PackageJson>,
npmrc: Arc<ResolvedNpmRc>,
maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
overrides: CliOptionOverrides,
maybe_workspace_config: Option<WorkspaceConfig>,
@ -736,6 +816,7 @@ impl CliOptions {
maybe_config_file: Option<ConfigFile>,
maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
maybe_package_json: Option<PackageJson>,
npmrc: Arc<ResolvedNpmRc>,
force_global_cache: bool,
) -> Result<Self, AnyError> {
if let Some(insecure_allowlist) =
@ -798,6 +879,7 @@ impl CliOptions {
maybe_config_file,
maybe_lockfile,
maybe_package_json,
npmrc,
maybe_node_modules_folder,
maybe_vendor_folder,
overrides: Default::default(),
@ -851,6 +933,16 @@ impl CliOptions {
} else {
maybe_package_json = discover_package_json(&flags, None, &initial_cwd)?;
}
let npmrc = discover_npmrc(
maybe_package_json.as_ref().map(|p| p.path.clone()),
maybe_config_file.as_ref().and_then(|cf| {
if cf.specifier.scheme() == "file" {
Some(cf.specifier.to_file_path().unwrap())
} else {
None
}
}),
)?;
let maybe_lock_file = lockfile::discover(
&flags,
@ -863,6 +955,7 @@ impl CliOptions {
maybe_config_file,
maybe_lock_file.map(|l| Arc::new(Mutex::new(l))),
maybe_package_json,
npmrc,
false,
)
}
@ -1172,6 +1265,7 @@ impl CliOptions {
maybe_vendor_folder: self.maybe_vendor_folder.clone(),
maybe_config_file: self.maybe_config_file.clone(),
maybe_package_json: self.maybe_package_json.clone(),
npmrc: self.npmrc.clone(),
maybe_lockfile: self.maybe_lockfile.clone(),
maybe_workspace_config: self.maybe_workspace_config.clone(),
overrides: self.overrides.clone(),
@ -1303,6 +1397,10 @@ impl CliOptions {
&self.maybe_package_json
}
pub fn npmrc(&self) -> &Arc<ResolvedNpmRc> {
&self.npmrc
}
pub fn maybe_package_json_deps(&self) -> Option<PackageJsonDeps> {
if matches!(
self.flags.subcommand,