Repository: denoland/deno
Pull request: feat(init/npm): add --yes flag to bypass prompt

Closes #31498

Changed files:
filename: cli/args/flags.rs
patch: @@ -287,6 +287,7 @@ pub struct InitFlags {
   pub dir: Option<String>,
   pub lib: bool,
   pub serve: bool,
+  pub yes: bool,
 }
 
 #[derive(Clone, Debug, Eq, PartialEq)]
@@ -3165,6 +3166,14 @@ fn init_subcommand() -> Command {
             .conflicts_with("lib")
             .action(ArgAction::SetTrue),
         )
+        .arg(
+          Arg::new("yes")
+            .short('y')
+            .long("yes")
+            .requires("npm")
+            .help("Bypass the prompt and run with full permissions")
+            .action(ArgAction::SetTrue),
+        )
     },
   )
 }
@@ -5936,6 +5945,7 @@ fn init_parse(
 ) -> Result<(), clap::Error> {
   let mut lib = matches.get_flag("lib");
   let mut serve = matches.get_flag("serve");
+  let mut yes = matches.get_flag("yes");
   let mut dir = None;
   let mut package = None;
   let mut package_args = vec![];
@@ -5955,6 +5965,7 @@ fn init_parse(
         let inner_matches = init_subcommand().try_get_matches_from_mut(args)?;
         lib = inner_matches.get_flag("lib");
         serve = inner_matches.get_flag("serve");
+        yes = inner_matches.get_flag("yes");
       }
     }
   }
@@ -5965,6 +5976,7 @@ fn init_parse(
     dir,
     lib,
     serve,
+    yes,
   });
 
   Ok(())
@@ -12589,6 +12601,7 @@ mod tests {
           dir: None,
           lib: false,
           serve: false,
+          yes: false,
         }),
         ..Flags::default()
       }
@@ -12604,6 +12617,7 @@ mod tests {
           dir: Some(String::from("foo")),
           lib: false,
           serve: false,
+          yes: false,
         }),
         ..Flags::default()
       }
@@ -12619,6 +12633,7 @@ mod tests {
           dir: None,
           lib: false,
           serve: false,
+          yes: false,
         }),
         log_level: Some(Level::Error),
         ..Flags::default()
@@ -12635,6 +12650,7 @@ mod tests {
           dir: None,
           lib: true,
           serve: false,
+          yes: false,
         }),
         ..Flags::default()
       }
@@ -12650,6 +12666,7 @@ mod tests {
           dir: None,
           lib: false,
           serve: true,
+          yes: false,
         }),
         ..Flags::default()
       }
@@ -12665,6 +12682,7 @@ mod tests {
           dir: Some(String::from("foo")),
           lib: true,
           serve: false,
+          yes: false,
         }),
         ..Flags::default()
       }
@@ -12686,6 +12704,7 @@ mod tests {
           dir: None,
           lib: false,
           serve: false,
+          yes: false,
         }),
         ..Flags::default()
       }
@@ -12701,6 +12720,7 @@ mod tests {
           dir: None,
           lib: false,
           serve: false,
+          yes: false,
         }),
         ..Flags::default()
       }
@@ -12716,6 +12736,23 @@ mod tests {
           dir: None,
           lib: false,
           serve: false,
+          yes: false,
+        }),
+        ..Flags::default()
+      }
+    );
+
+    let r = flags_from_vec(svec!["deno", "init", "--npm", "--yes", "vite"]);
+    assert_eq!(
+      r.unwrap(),
+      Flags {
+        subcommand: DenoSubcommand::Init(InitFlags {
+          package: Some("vite".to_string()),
+          package_args: vec![],
+          dir: None,
+          lib: false,
+          serve: false,
+          yes: true,
         }),
         ..Flags::default()
       }

filename: cli/tools/init/mod.rs
patch: @@ -30,9 +30,13 @@ use crate::util::progress_bar::ProgressBar;
 
 pub async fn init_project(init_flags: InitFlags) -> Result<i32, AnyError> {
   if let Some(package) = &init_flags.package {
-    return init_npm(package, init_flags.package_args)
-      .boxed_local()
-      .await;
+    return init_npm(InitNpmOptions {
+      name: package,
+      args: init_flags.package_args,
+      yes: init_flags.yes,
+    })
+    .boxed_local()
+    .await;
   }
 
   let cwd =
@@ -301,43 +305,53 @@ fn npm_name_to_create_package(name: &str) -> String {
   s
 }
 
-async fn init_npm(name: &str, args: Vec<String>) -> Result<i32, AnyError> {
-  let script_name = npm_name_to_create_package(name);
+struct InitNpmOptions<'a> {
+  name: &'a str,
+  args: Vec<String>,
+  yes: bool,
+}
+
+async fn init_npm(options: InitNpmOptions<'_>) -> Result<i32, AnyError> {
+  let script_name = npm_name_to_create_package(options.name);
 
   fn print_manual_usage(script_name: &str, args: &[String]) -> i32 {
     log::info!(
       "{}",
       cformat!(
-        "You can initialize project manually by running <u>deno run {} {}</> and applying desired permissions.",
-        script_name,
-        args.join(" ")
+        "You can initialize project manually by running <u>deno run {}</> and applying desired permissions.",
+        std::iter::once(script_name)
+          .chain(args.iter().map(|a| a.as_ref()))
+          .collect::<Vec<_>>()
+          .join(" ")
       )
     );
     1
   }
 
-  if std::io::stdin().is_terminal() {
-    log::info!(
-      cstr!(
-        "⚠️  Do you fully trust <y>{}</> package? Deno will invoke code from it with all permissions. Do you want to continue? <p(245)>[y/n]</>"
-      ),
-      script_name
-    );
-    loop {
-      let _ = std::io::stdout().write(b"> ")?;
-      std::io::stdout().flush()?;
-      let mut answer = String::new();
-      if std::io::stdin().read_line(&mut answer).is_ok() {
-        let answer = answer.trim().to_ascii_lowercase();
-        if answer != "y" {
-          return Ok(print_manual_usage(&script_name, &args));
-        } else {
-          break;
+  if !options.yes {
+    if std::io::stdin().is_terminal() {
+      log::info!(
+        cstr!(
+          "⚠️  Do you fully trust <y>{}</> package? Deno will invoke code from it with all permissions. Do you want to continue? <p(245)>[y/n]</>"
+        ),
+        script_name
+      );
+      loop {
+        let _ = std::io::stdout().write(b"> ")?;
+        std::io::stdout().flush()?;
+        let mut answer = String::new();
+        if std::io::stdin().read_line(&mut answer).is_ok() {
+          let answer = answer.trim().to_ascii_lowercase();
+          if answer != "y" {
+            return Ok(print_manual_usage(&script_name, &options.args));
+          } else {
+            break;
+          }
         }
       }
+    } else {
+      return Ok(print_manual_usage(&script_name, &options.args));
     }
-  } else {
-    return Ok(print_manual_usage(&script_name, &args));
   }
 
   let temp_node_modules_parent_tempdir = create_temp_node_modules_parent_dir()
@@ -360,7 +374,7 @@ async fn init_npm(name: &str, args: Vec<String>) -> Result<i32, AnyError> {
       ..Default::default()
     },
     allow_scripts: PackagesAllowedScripts::All,
-    argv: args,
+    argv: options.args,
     node_modules_dir: Some(NodeModulesDirMode::Auto),
     subcommand: DenoSubcommand::Run(RunFlags {
       script: script_name,

filename: tests/specs/init/yes_flag/__test__.jsonc
patch: @@ -0,0 +1,14 @@
+{
+  "tempDir": true,
+  "tests": {
+    "missing": {
+      "args": "init --npm @denotest",
+      "output": "missing.out",
+      "exitCode": 1
+    },
+    "yes": {
+      "args": "init --npm --yes @denotest",
+      "output": "init.out"
+    }
+  }
+}

filename: tests/specs/init/yes_flag/init.out
patch: @@ -0,0 +1,2 @@
+[WILDCARD]
+Initialized!

filename: tests/specs/init/yes_flag/missing.out
patch: @@ -0,0 +1 @@
+You can initialize project manually by running deno run npm:@denotest/create and applying desired permissions.