refactor(cli): make CliNpmResolver a trait (#20732)

This makes `CliNpmResolver` a trait. The terminology used is:

- **managed** - Deno manages the node_modules folder and does an
auto-install (ex. `ManagedCliNpmResolver`)
- **byonm** - "Bring your own node_modules" (ex. `ByonmCliNpmResolver`,
which is in this PR, but unimplemented at the moment)

Part of #18967
This commit is contained in:
David Sherret 2023-09-29 09:26:25 -04:00 committed by GitHub
parent d43e48c4e9
commit 5edd102f3f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 417 additions and 237 deletions

View file

@ -5,7 +5,7 @@ use crate::args::Flags;
use crate::args::TaskFlags;
use crate::colors;
use crate::factory::CliFactory;
use crate::npm::CliNpmResolver;
use crate::npm::ManagedCliNpmResolver;
use crate::util::fs::canonicalize_path;
use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
@ -19,6 +19,7 @@ use deno_task_shell::ShellCommand;
use deno_task_shell::ShellCommandContext;
use indexmap::IndexMap;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use tokio::task::LocalSet;
@ -67,8 +68,6 @@ pub async fn execute_script(
Ok(exit_code)
} else if package_json_scripts.contains_key(task_name) {
let package_json_deps_provider = factory.package_json_deps_provider();
let package_json_deps_installer =
factory.package_json_deps_installer().await?;
let npm_resolver = factory.npm_resolver().await?;
let node_resolver = factory.node_resolver().await?;
@ -85,10 +84,15 @@ pub async fn execute_script(
}
}
package_json_deps_installer
.ensure_top_level_install()
.await?;
npm_resolver.resolve_pending().await?;
// install the npm packages if we're using a managed resolver
if let Some(npm_resolver) = npm_resolver.as_managed() {
let package_json_deps_installer =
factory.package_json_deps_installer().await?;
package_json_deps_installer
.ensure_top_level_install()
.await?;
npm_resolver.resolve_pending().await?;
}
log::info!(
"{} Currently only basic package.json `scripts` are supported. Programs like `rimraf` or `cross-env` will not work correctly. This will be fixed in an upcoming release.",
@ -120,8 +124,16 @@ pub async fn execute_script(
output_task(&task_name, &script);
let seq_list = deno_task_shell::parser::parse(&script)
.with_context(|| format!("Error parsing script '{task_name}'."))?;
let npx_commands = resolve_npm_commands(npm_resolver, node_resolver)?;
let env_vars = collect_env_vars();
let npx_commands = match npm_resolver.as_managed() {
Some(npm_resolver) => {
resolve_npm_commands(npm_resolver, node_resolver)?
}
None => Default::default(),
};
let env_vars = match npm_resolver.node_modules_path() {
Some(dir_path) => collect_env_vars_with_node_modules_dir(&dir_path),
None => collect_env_vars(),
};
let local = LocalSet::new();
let future =
deno_task_shell::execute(seq_list, env_vars, &cwd, npx_commands);
@ -162,6 +174,36 @@ fn output_task(task_name: &str, script: &str) {
);
}
fn collect_env_vars_with_node_modules_dir(
node_modules_dir_path: &Path,
) -> HashMap<String, String> {
let mut env_vars = collect_env_vars();
prepend_to_path(
&mut env_vars,
node_modules_dir_path
.join(".bin")
.to_string_lossy()
.to_string(),
);
env_vars
}
fn prepend_to_path(env_vars: &mut HashMap<String, String>, value: String) {
match env_vars.get_mut("PATH") {
Some(path) => {
if path.is_empty() {
*path = value;
} else {
*path =
format!("{}{}{}", value, if cfg!(windows) { ";" } else { ":" }, path);
}
}
None => {
env_vars.insert("PATH".to_string(), value);
}
}
}
fn collect_env_vars() -> HashMap<String, String> {
// get the starting env vars (the PWD env var will be set by deno_task_shell)
let mut env_vars = std::env::vars().collect::<HashMap<String, String>>();
@ -262,7 +304,7 @@ impl ShellCommand for NpmPackageBinCommand {
}
fn resolve_npm_commands(
npm_resolver: &CliNpmResolver,
npm_resolver: &ManagedCliNpmResolver,
node_resolver: &NodeResolver,
) -> Result<HashMap<String, Rc<dyn ShellCommand>>, AnyError> {
let mut result = HashMap::new();
@ -286,3 +328,36 @@ fn resolve_npm_commands(
}
Ok(result)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_prepend_to_path() {
let mut env_vars = HashMap::new();
prepend_to_path(&mut env_vars, "/example".to_string());
assert_eq!(
env_vars,
HashMap::from([("PATH".to_string(), "/example".to_string())])
);
prepend_to_path(&mut env_vars, "/example2".to_string());
let separator = if cfg!(windows) { ";" } else { ":" };
assert_eq!(
env_vars,
HashMap::from([(
"PATH".to_string(),
format!("/example2{}/example", separator)
)])
);
env_vars.get_mut("PATH").unwrap().clear();
prepend_to_path(&mut env_vars, "/example".to_string());
assert_eq!(
env_vars,
HashMap::from([("PATH".to_string(), "/example".to_string())])
);
}
}