diff --git a/cli/args/flags.rs b/cli/args/flags.rs index afd505f449..77e09e1cbf 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -312,6 +312,7 @@ pub struct Flags { pub lock: Option, pub log_level: Option, pub no_remote: bool, + pub no_npm: bool, pub no_prompt: bool, pub reload: bool, pub seed: Option, @@ -1732,6 +1733,7 @@ fn compile_args(app: Command) -> Command { app .arg(import_map_arg()) .arg(no_remote_arg()) + .arg(no_npm_arg()) .arg(no_config_arg()) .arg(config_arg()) .arg(no_check_arg()) @@ -1746,6 +1748,7 @@ fn compile_args_without_check_args(app: Command) -> Command { app .arg(import_map_arg()) .arg(no_remote_arg()) + .arg(no_npm_arg()) .arg(config_arg()) .arg(no_config_arg()) .arg(reload_arg()) @@ -2140,6 +2143,12 @@ fn no_remote_arg<'a>() -> Arg<'a> { .help("Do not resolve remote modules") } +fn no_npm_arg<'a>() -> Arg<'a> { + Arg::new("no-npm") + .long("no-npm") + .help("Do not resolve npm modules") +} + fn unsafely_ignore_certificate_errors_arg<'a>() -> Arg<'a> { Arg::new("unsafely-ignore-certificate-errors") .long("unsafely-ignore-certificate-errors") @@ -2785,6 +2794,7 @@ fn vendor_parse(flags: &mut Flags, matches: &clap::ArgMatches) { fn compile_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) { import_map_arg_parse(flags, matches); no_remote_arg_parse(flags, matches); + no_npm_arg_parse(flags, matches); config_args_parse(flags, matches); no_check_arg_parse(flags, matches); check_arg_parse(flags, matches); @@ -2799,6 +2809,7 @@ fn compile_args_without_no_check_parse( ) { import_map_arg_parse(flags, matches); no_remote_arg_parse(flags, matches); + no_npm_arg_parse(flags, matches); config_args_parse(flags, matches); reload_arg_parse(flags, matches); lock_args_parse(flags, matches); @@ -3040,6 +3051,12 @@ fn no_remote_arg_parse(flags: &mut Flags, matches: &clap::ArgMatches) { } } +fn no_npm_arg_parse(flags: &mut Flags, matches: &clap::ArgMatches) { + if matches.is_present("no-npm") { + flags.no_npm = true; + } +} + fn inspect_arg_validate(val: &str) -> Result<(), String> { match val.parse::() { Ok(_) => Ok(()), @@ -4998,6 +5015,21 @@ mod tests { ); } + #[test] + fn no_npm() { + let r = flags_from_vec(svec!["deno", "run", "--no-npm", "script.ts"]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Run(RunFlags { + script: "script.ts".to_string(), + }), + no_npm: true, + ..Flags::default() + } + ); + } + #[test] fn cached_only() { let r = flags_from_vec(svec!["deno", "run", "--cached-only", "script.ts"]); diff --git a/cli/args/mod.rs b/cli/args/mod.rs index 261de6c877..e4c0d85560 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -331,6 +331,10 @@ impl CliOptions { self.flags.no_remote } + pub fn no_npm(&self) -> bool { + self.flags.no_npm + } + pub fn permissions_options(&self) -> PermissionsOptions { self.flags.permissions_options() } diff --git a/cli/npm/mod.rs b/cli/npm/mod.rs index 8272974bc0..d0a57c5bc0 100644 --- a/cli/npm/mod.rs +++ b/cli/npm/mod.rs @@ -14,8 +14,8 @@ use std::sync::Arc; use deno_ast::ModuleSpecifier; use deno_core::anyhow::bail; use deno_core::anyhow::Context; +use deno_core::error::custom_error; use deno_core::error::AnyError; - use deno_core::futures; use deno_core::url::Url; use deno_runtime::deno_node::DenoDirNpmResolver; @@ -77,6 +77,7 @@ pub struct GlobalNpmPackageResolver { resolution: Arc, registry_url: Url, unstable: bool, + no_npm: bool, } impl GlobalNpmPackageResolver { @@ -85,12 +86,14 @@ impl GlobalNpmPackageResolver { reload: bool, cache_setting: CacheSetting, unstable: bool, + no_npm: bool, ) -> Self { Self::from_cache( NpmCache::from_deno_dir(dir, cache_setting.clone()), reload, cache_setting, unstable, + no_npm, ) } @@ -99,6 +102,7 @@ impl GlobalNpmPackageResolver { reload: bool, cache_setting: CacheSetting, unstable: bool, + no_npm: bool, ) -> Self { let api = NpmRegistryApi::new(cache.clone(), reload, cache_setting); let registry_url = api.base_url().to_owned(); @@ -109,6 +113,7 @@ impl GlobalNpmPackageResolver { resolution, registry_url, unstable, + no_npm, } } @@ -122,11 +127,28 @@ impl GlobalNpmPackageResolver { &self, packages: Vec, ) -> Result<(), AnyError> { - if !self.unstable && !packages.is_empty() { + assert!(!packages.is_empty()); + + if !self.unstable { bail!( "Unstable use of npm specifiers. The --unstable flag must be provided." ) } + + if self.no_npm { + let fmt_reqs = packages + .iter() + .map(|p| format!("\"{}\"", p)) + .collect::>() + .join(", "); + return Err(custom_error( + "NoNpm", + format!( + "Following npm specifiers were requested: {}; but --no-npm is specified.", + fmt_reqs + ), + )); + } self.resolution.add_package_reqs(packages).await } diff --git a/cli/proc_state.rs b/cli/proc_state.rs index fc349ac258..3505d97d0d 100644 --- a/cli/proc_state.rs +++ b/cli/proc_state.rs @@ -223,6 +223,7 @@ impl ProcState { cli_options.unstable() // don't do the unstable error when in the lsp || matches!(cli_options.sub_command(), DenoSubcommand::Lsp), + cli_options.no_npm(), ); Ok(ProcState(Arc::new(Inner { diff --git a/cli/tests/integration/npm_tests.rs b/cli/tests/integration/npm_tests.rs index b8867868f4..9c16e23c13 100644 --- a/cli/tests/integration/npm_tests.rs +++ b/cli/tests/integration/npm_tests.rs @@ -231,6 +231,81 @@ fn cached_only_after_first_run() { assert_contains!(stdout, "createChalk: chalk"); } +#[test] +fn no_npm_after_first_run() { + let _server = http_server(); + + let deno_dir = util::new_deno_dir(); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--unstable") + .arg("--allow-read") + .arg("--allow-env") + .arg("--no-npm") + .arg("npm/no_npm_after_first_run/main1.ts") + .env("NO_COLOR", "1") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_contains!( + stderr, + "Following npm specifiers were requested: \"chalk@5\"; but --no-npm is specified." + ); + assert!(stdout.is_empty()); + assert!(!output.status.success()); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--unstable") + .arg("--allow-read") + .arg("--allow-env") + .arg("npm/no_npm_after_first_run/main1.ts") + .env("NO_COLOR", "1") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_contains!(stderr, "Download"); + assert_contains!(stdout, "createChalk: chalk"); + assert!(output.status.success()); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--unstable") + .arg("--allow-read") + .arg("--allow-env") + .arg("--no-npm") + .arg("npm/no_npm_after_first_run/main1.ts") + .env("NO_COLOR", "1") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_contains!( + stderr, + "Following npm specifiers were requested: \"chalk@5\"; but --no-npm is specified." + ); + assert!(stdout.is_empty()); + assert!(!output.status.success()); +} + #[test] fn deno_run_cjs_module() { let _server = http_server(); diff --git a/cli/tests/testdata/npm/no_npm_after_first_run/main1.ts b/cli/tests/testdata/npm/no_npm_after_first_run/main1.ts new file mode 100644 index 0000000000..1ccc441a15 --- /dev/null +++ b/cli/tests/testdata/npm/no_npm_after_first_run/main1.ts @@ -0,0 +1,3 @@ +import chalk from "npm:chalk@5"; + +console.log(chalk); diff --git a/cli/tools/installer.rs b/cli/tools/installer.rs index 6928624eaa..d1754562a2 100644 --- a/cli/tools/installer.rs +++ b/cli/tools/installer.rs @@ -321,6 +321,10 @@ fn resolve_shim_data( executable_args.push("--no-remote".to_string()); } + if flags.no_npm { + executable_args.push("--no-npm".to_string()); + } + if flags.lock_write { executable_args.push("--lock-write".to_string()); } diff --git a/cli/tools/standalone.rs b/cli/tools/standalone.rs index 6046654b9e..3a2bed1053 100644 --- a/cli/tools/standalone.rs +++ b/cli/tools/standalone.rs @@ -279,6 +279,7 @@ pub fn compile_to_runtime_flags( .unsafely_ignore_certificate_errors .clone(), no_remote: false, + no_npm: false, no_prompt: flags.no_prompt, reload: false, seed: flags.seed,