summaryrefslogtreecommitdiff
path: root/cli/args/flags.rs
diff options
context:
space:
mode:
authorNathan Whitaker <17734409+nathanwhit@users.noreply.github.com>2024-07-09 20:06:08 -0700
committerGitHub <noreply@github.com>2024-07-10 03:06:08 +0000
commitce7dc2be92499f15b4b0315bfca3ee9d61fc3c5e (patch)
treef2463a8026d6f68d288c04b8671ce26f310de9fe /cli/args/flags.rs
parenteb46296e974c686896486350bb00bf428a84e9fd (diff)
feat(node): Support executing npm package lifecycle scripts (preinstall/install/postinstall) (#24487)
Adds support for running npm package lifecycle scripts, opted into via a new `--allow-scripts` flag. With this PR, when running `deno cache` (or `DENO_FUTURE=1 deno install`) you can specify the `--allow-scripts=pkg1,pkg2` flag to run lifecycle scripts attached to the given packages. Note at the moment this only works when `nodeModulesDir` is true (using the local resolver). When a package with un-run lifecycle scripts is encountered, we emit a warning suggesting things may not work and to try running lifecycle scripts. Additionally, if a package script implicitly requires `node-gyp` and it's not found on the system, we emit a warning. Extra things in this PR: - Extracted out bits of `task.rs` into a separate module for reuse - Added a couple fields to `process.config` in order to support `node-gyp` (it relies on a few variables being there) - Drive by fix to downloading new npm packages to test registry --- TODO: - [x] validation for allow-scripts args (make sure it looks like an npm package) - [x] make allow-scripts matching smarter - [ ] figure out what issues this closes --- Review notes: - This adds a bunch of deps to our test registry due to using `node-gyp`, so it's pretty noisy
Diffstat (limited to 'cli/args/flags.rs')
-rw-r--r--cli/args/flags.rs98
1 files changed, 97 insertions, 1 deletions
diff --git a/cli/args/flags.rs b/cli/args/flags.rs
index b07f3783a..48cfb9240 100644
--- a/cli/args/flags.rs
+++ b/cli/args/flags.rs
@@ -507,6 +507,30 @@ pub enum CaData {
Bytes(Vec<u8>),
}
+// Info needed to run NPM lifecycle scripts
+#[derive(Clone, Debug, Eq, PartialEq, Default)]
+pub struct LifecycleScriptsConfig {
+ pub allowed: PackagesAllowedScripts,
+ pub initial_cwd: Option<PathBuf>,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq, Default)]
+/// The set of npm packages that are allowed to run lifecycle scripts.
+pub enum PackagesAllowedScripts {
+ All,
+ Some(Vec<String>),
+ #[default]
+ None,
+}
+
+fn parse_packages_allowed_scripts(s: &str) -> Result<String, AnyError> {
+ if !s.starts_with("npm:") {
+ bail!("Invalid package for --allow-scripts: '{}'. An 'npm:' specifier is required", s);
+ } else {
+ Ok(s.into())
+ }
+}
+
#[derive(
Clone, Default, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize,
)]
@@ -562,6 +586,7 @@ pub struct Flags {
pub v8_flags: Vec<String>,
pub code_cache_enabled: bool,
pub permissions: PermissionFlags,
+ pub allow_scripts: PackagesAllowedScripts,
}
#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)]
@@ -1502,6 +1527,7 @@ Future runs of this module will trigger no downloads or compilation unless
.value_hint(ValueHint::FilePath),
)
.arg(frozen_lockfile_arg())
+ .arg(allow_scripts_arg())
})
}
@@ -2213,7 +2239,7 @@ The installation root is determined, in order of precedence:
These must be added to the path manually if required.")
.defer(|cmd| {
- let cmd = runtime_args(cmd, true, true).arg(check_arg(true));
+ let cmd = runtime_args(cmd, true, true).arg(check_arg(true)).arg(allow_scripts_arg());
install_args(cmd, true)
})
}
@@ -3728,6 +3754,28 @@ fn unsafely_ignore_certificate_errors_arg() -> Arg {
.value_parser(flags_net::validator)
}
+fn allow_scripts_arg() -> Arg {
+ Arg::new("allow-scripts")
+ .long("allow-scripts")
+ .num_args(0..)
+ .use_value_delimiter(true)
+ .require_equals(true)
+ .value_name("PACKAGE")
+ .value_parser(parse_packages_allowed_scripts)
+ .help("Allow running npm lifecycle scripts for the given packages. Note: Scripts will only be executed when using a node_modules directory (`--node-modules-dir`)")
+}
+
+fn allow_scripts_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
+ let Some(parts) = matches.remove_many::<String>("allow-scripts") else {
+ return;
+ };
+ if parts.len() == 0 {
+ flags.allow_scripts = PackagesAllowedScripts::All;
+ } else {
+ flags.allow_scripts = PackagesAllowedScripts::Some(parts.collect());
+ }
+}
+
fn add_parse(flags: &mut Flags, matches: &mut ArgMatches) {
flags.subcommand = DenoSubcommand::Add(add_parse_inner(matches, None));
}
@@ -3810,6 +3858,7 @@ fn bundle_parse(flags: &mut Flags, matches: &mut ArgMatches) {
fn cache_parse(flags: &mut Flags, matches: &mut ArgMatches) {
compile_args_parse(flags, matches);
frozen_lockfile_arg_parse(flags, matches);
+ allow_scripts_arg_parse(flags, matches);
let files = matches.remove_many::<String>("file").unwrap().collect();
flags.subcommand = DenoSubcommand::Cache(CacheFlags { files });
}
@@ -4096,6 +4145,7 @@ fn install_parse(flags: &mut Flags, matches: &mut ArgMatches) {
let local_flags = matches
.remove_many("cmd")
.map(|packages| add_parse_inner(matches, Some(packages)));
+ allow_scripts_arg_parse(flags, matches);
flags.subcommand = DenoSubcommand::Install(InstallFlags {
global,
kind: InstallKind::Local(local_flags),
@@ -9969,4 +10019,50 @@ mod tests {
);
}
}
+
+ #[test]
+ fn allow_scripts() {
+ let cases = [
+ (Some("--allow-scripts"), Ok(PackagesAllowedScripts::All)),
+ (None, Ok(PackagesAllowedScripts::None)),
+ (
+ Some("--allow-scripts=npm:foo"),
+ Ok(PackagesAllowedScripts::Some(svec!["npm:foo"])),
+ ),
+ (
+ Some("--allow-scripts=npm:foo,npm:bar"),
+ Ok(PackagesAllowedScripts::Some(svec!["npm:foo", "npm:bar"])),
+ ),
+ (Some("--allow-scripts=foo"), Err("Invalid package")),
+ ];
+ for (flag, value) in cases {
+ let mut args = svec!["deno", "cache"];
+ if let Some(flag) = flag {
+ args.push(flag.into());
+ }
+ args.push("script.ts".into());
+ let r = flags_from_vec(args);
+ match value {
+ Ok(value) => {
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Cache(CacheFlags {
+ files: svec!["script.ts"],
+ }),
+ allow_scripts: value,
+ ..Flags::default()
+ }
+ );
+ }
+ Err(e) => {
+ let err = r.unwrap_err();
+ assert!(
+ err.to_string().contains(e),
+ "expected to contain '{e}' got '{err}'"
+ );
+ }
+ }
+ }
+ }
}