Repository: denoland/deno
Pull request: feat: `deno init --empty`

Hi dear Deno Team,

First of all, it's not connected to a issue, I'm a web developer that use Deno for years. All each project, each deno init, I firstly delete main_test.ts, after remove @std/assert imports in deno.json. Today, actually few minutes ago I said to myself `Is there no command that does an empty init?` I couldn't find one. So yeah, here, `deno init --empty` command to a real empty project initialize.

I can not tested cargo test because I can not fixed that error
```
   Compiling unicode-xid v0.2.6
error: failed to run custom build command for `libsqlite3-sys v0.35.0`

Caused by:
  process didn't exit successfully: `C:\Users\***\Desktop\Desktop\Code\***\deno\target\debug\build\libsqlite3-sys-ff8d411b39180a77\build-script-build` (exit code: 101)
  --- stdout
  cargo:rerun-if-env-changed=LIBSQLITE3_SYS_USE_PKG_CONFIG
  cargo:rerun-if-env-changed=LIBSQLITE3_SYS_BUNDLING
```

<!--
Before submitting a PR, please read https://docs.deno.com/runtime/manual/references/contributing

1. Give the PR a descriptive title.

  Examples of good title:
    - fix(std/http): Fix race condition in server
    - docs(console): Update docstrings
    - feat(doc): Handle nested reexports

  Examples of bad title:
    - fix #7123
    - update docs
    - fix bugs

2. Ensure there is a related issue and it is referenced in the PR text.
3. Ensure there are tests that cover the changes.
4. Ensure `cargo test` passes.
5. Ensure `./tools/format.js` passes without changing files.
6. Ensure `./tools/lint.js` passes.
7. Open as a draft PR if your work is still in progress. The CI won't run
   all steps, but you can add '[ci]' to a commit message to force it to.
8. If you would like to run the benchmarks on the CI, add the 'ci-bench' label.
-->


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 empty: bool,
   pub yes: bool,
 }
 
@@ -3142,7 +3143,7 @@ fn init_subcommand() -> Command {
           Arg::new("npm")
             .long("npm")
             .help("Generate a npm create-* project")
-            .conflicts_with_all(["lib", "serve"])
+            .conflicts_with_all(["lib", "serve", "empty"])
             .action(ArgAction::SetTrue),
         )
         .arg(
@@ -3158,6 +3159,13 @@ fn init_subcommand() -> Command {
             .conflicts_with("lib")
             .action(ArgAction::SetTrue),
         )
+        .arg(
+          Arg::new("empty")
+            .long("empty")
+            .help("Generate a minimal project with just main.ts and deno.json")
+            .conflicts_with_all(["lib", "serve", "npm"])
+            .action(ArgAction::SetTrue),
+        )
         .arg(
           Arg::new("yes")
             .short('y')
@@ -5935,6 +5943,7 @@ fn init_parse(
 ) -> Result<(), clap::Error> {
   let mut lib = matches.get_flag("lib");
   let mut serve = matches.get_flag("serve");
+  let mut empty = matches.get_flag("empty");
   let mut yes = matches.get_flag("yes");
   let mut dir = None;
   let mut package = None;
@@ -5955,6 +5964,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");
+        empty = inner_matches.get_flag("empty");
         yes = inner_matches.get_flag("yes");
       }
     }
@@ -5966,6 +5976,7 @@ fn init_parse(
     dir,
     lib,
     serve,
+    empty,
     yes,
   });
 
@@ -12591,6 +12602,7 @@ mod tests {
           dir: None,
           lib: false,
           serve: false,
+          empty: false,
           yes: false,
         }),
         ..Flags::default()
@@ -12607,6 +12619,7 @@ mod tests {
           dir: Some(String::from("foo")),
           lib: false,
           serve: false,
+          empty: false,
           yes: false,
         }),
         ..Flags::default()
@@ -12623,6 +12636,7 @@ mod tests {
           dir: None,
           lib: false,
           serve: false,
+          empty: false,
           yes: false,
         }),
         log_level: Some(Level::Error),
@@ -12640,6 +12654,7 @@ mod tests {
           dir: None,
           lib: true,
           serve: false,
+          empty: false,
           yes: false,
         }),
         ..Flags::default()
@@ -12656,6 +12671,7 @@ mod tests {
           dir: None,
           lib: false,
           serve: true,
+          empty: false,
           yes: false,
         }),
         ..Flags::default()
@@ -12672,6 +12688,7 @@ mod tests {
           dir: Some(String::from("foo")),
           lib: true,
           serve: false,
+          empty: false,
           yes: false,
         }),
         ..Flags::default()
@@ -12694,6 +12711,7 @@ mod tests {
           dir: None,
           lib: false,
           serve: false,
+          empty: false,
           yes: false,
         }),
         ..Flags::default()
@@ -12710,6 +12728,7 @@ mod tests {
           dir: None,
           lib: false,
           serve: false,
+          empty: false,
           yes: false,
         }),
         ..Flags::default()
@@ -12726,6 +12745,7 @@ mod tests {
           dir: None,
           lib: false,
           serve: false,
+          empty: false,
           yes: false,
         }),
         ..Flags::default()
@@ -12742,6 +12762,7 @@ mod tests {
           dir: None,
           lib: false,
           serve: false,
+          empty: false,
           yes: true,
         }),
         ..Flags::default()

filename: cli/tools/init/mod.rs
patch: @@ -49,7 +49,24 @@ pub async fn init_project(init_flags: InitFlags) -> Result<i32, AnyError> {
     cwd
   };
 
-  if init_flags.serve {
+  if init_flags.empty {
+    create_file(
+      &dir,
+      "main.ts",
+      r#"console.log('Hello world!');
+"#,
+    )?;
+
+    create_json_file(
+      &dir,
+      "deno.json",
+      &json!({
+        "tasks": {
+          "dev": "deno run --watch main.ts"
+        }
+      }),
+    )?;
+  } else if init_flags.serve {
     create_file(
       &dir,
       "main.ts",
@@ -227,7 +244,16 @@ Deno.test(function addTest() {
     info!("  cd {}", dir);
     info!("");
   }
-  if init_flags.serve {
+  if init_flags.empty {
+    info!("  {}", colors::gray("# Run the program"));
+    info!("  deno run main.ts");
+    info!("");
+    info!(
+      "  {}",
+      colors::gray("# Run the program and watch for file changes")
+    );
+    info!("  deno task dev");
+  } else if init_flags.serve {
     info!("  {}", colors::gray("# Run the server"));
     info!("  deno serve -R main.ts");
     info!("");

filename: tests/integration/init_tests.rs
patch: @@ -171,6 +171,50 @@ Run these commands to get started
   output.assert_matches_text("Log from main.ts that already exists\n");
 }
 
+#[test]
+fn init_subcommand_empty() {
+  let context = TestContextBuilder::for_jsr().use_temp_cwd().build();
+  let cwd = context.temp_dir().path();
+
+  let output = context
+    .new_command()
+    .args("init --empty")
+    .split_output()
+    .run();
+
+  output.assert_exit_code(0);
+
+  let stderr = output.stderr();
+  assert_contains!(stderr, "Project initialized");
+  assert!(!stderr.contains("cd"));
+  assert_contains!(stderr, "deno run main.ts");
+  assert_contains!(stderr, "deno task dev");
+  assert!(!stderr.contains("deno test"));
+
+  let deno_json_path = cwd.join("deno.json");
+  assert!(deno_json_path.exists());
+
+  let deno_json_content = deno_json_path.read_to_string();
+  assert!(!deno_json_content.contains("@std/assert"));
+
+  assert!(cwd.join("main.ts").exists());
+  assert!(!cwd.join("main_test.ts").exists());
+
+  let main_content = cwd.join("main.ts").read_to_string();
+  assert_eq!(main_content, "console.log('Hello world!');\n");
+
+  let output = context
+    .new_command()
+    .env("NO_COLOR", "1")
+    .args("run main.ts")
+    .split_output()
+    .run();
+
+  output.assert_exit_code(0);
+  assert_eq!(output.stdout().as_bytes(), b"Hello world!\n");
+  output.skip_output_check();
+}
+
 #[tokio::test]
 async fn init_subcommand_serve() {
   let context = TestContextBuilder::for_jsr().use_temp_cwd().build();