@@ -956,6 +956,8 @@ static DENO_HELP: &str = cstr!(
956956 <p(245)>deno run main.ts | deno run --allow-net=google.com main.ts | deno main.ts</>
957957 <g>serve</> Run a server
958958 <p(245)>deno serve main.ts</>
959+ <g>watch</> Run a program, watching for changes and hot-replacing modules
960+ <p(245)>deno watch main.ts</>
959961 <g>task</> Run a task defined in the configuration file
960962 <p(245)>deno task dev</>
961963 <g>repl</> Start an interactive Read-Eval-Print Loop (REPL) for Deno
@@ -1303,7 +1305,8 @@ pub fn flags_from_vec_with_initial_cwd(
13031305 "lsp" => lsp_parse(&mut flags, &mut m),
13041306 "outdated" => outdated_parse(&mut flags, &mut m, false)?,
13051307 "repl" => repl_parse(&mut flags, &mut m)?,
1306- "run" => run_parse ( & mut flags, & mut m, app, false ) ?,
1308+ "run" => run_parse(&mut flags, &mut m, app, false, false)?,
1309+ "watch" => run_parse(&mut flags, &mut m, app, false, true)?,
13071310 "serve" => serve_parse(&mut flags, &mut m, app)?,
13081311 "task" => task_parse(&mut flags, &mut m, app)?,
13091312 "test" => test_parse(&mut flags, &mut m)?,
@@ -1334,7 +1337,7 @@ pub fn flags_from_vec_with_initial_cwd(
13341337 });
13351338
13361339 if has_non_globals || matches.contains_id("script_arg") {
1337- run_parse ( & mut flags, & mut matches, app, true ) ?;
1340+ run_parse(&mut flags, &mut matches, app, true, false )?;
13381341 } else {
13391342 handle_repl_flags(
13401343 &mut flags,
@@ -1548,6 +1551,7 @@ pub fn clap_root() -> Command {
15481551 .global(true),
15491552 )
15501553 .subcommand(run_subcommand())
1554+ .subcommand(watch_subcommand())
15511555 .subcommand(serve_subcommand())
15521556 .defer(|cmd| {
15531557 let cmd = cmd
@@ -4083,6 +4087,18 @@ Specifying the filename '-' to read the file from stdin.
40834087<y>Read more:</> <c>https://docs.deno.com/go/run</>"), UnstableArgsConfig::ResolutionAndRuntime), false)
40844088}
40854089
4090+ fn watch_subcommand() -> Command {
4091+ run_args(command("watch", cstr!("Run a JavaScript or TypeScript program, watching for file changes and hot-replacing modules.
4092+
4093+ This is an alias for <c>deno run --watch-hmr</>. The process restarts if hot replacement fails.
4094+ <p(245)>deno watch main.ts</>
4095+
4096+ Local files from the entry point module graph are watched by default. Additional paths can be passed with <c>--watch-hmr</>:
4097+ <p(245)>deno watch --watch-hmr=./templates main.ts</>
4098+
4099+ <y>Read more:</> <c>https://docs.deno.com/go/run</>"), UnstableArgsConfig::ResolutionAndRuntime), false)
4100+ }
4101+
40864102fn serve_host_validator(host: &str) -> Result<String, String> {
40874103 if Url::parse(&format!("internal://{host}:9999")).is_ok() {
40884104 Ok(host.to_owned())
@@ -7725,6 +7741,7 @@ fn run_parse(
77257741 matches: &mut ArgMatches,
77267742 app: Command,
77277743 bare: bool,
7744+ force_hmr: bool,
77287745) -> clap::error::Result<()> {
77297746 runtime_args_parse(flags, matches, true, true, true)?;
77307747 ext_arg_parse(flags, matches);
@@ -7748,7 +7765,30 @@ fn run_parse(
77487765 Some(mut script_arg) => {
77497766 let script = script_arg.next().unwrap();
77507767 flags.argv.extend(script_arg);
7751- let watch = watch_arg_parse_with_paths ( matches) ?;
7768+ let mut watch = watch_arg_parse_with_paths(matches)?;
7769+ // `deno watch` is an alias for `deno run --watch-hmr`, so enable hot
7770+ // module replacement watching by default when no explicit watch flag was
7771+ // passed.
7772+ if force_hmr {
7773+ match &mut watch {
7774+ Some(watch) => watch.hmr = true,
7775+ None => {
7776+ watch = Some(WatchFlagsWithPaths {
7777+ hmr: true,
7778+ paths: vec![],
7779+ no_clear_screen: matches.get_flag("no-clear-screen"),
7780+ exclude: matches
7781+ .remove_many::<String>("watch-exclude")
7782+ .map(|f| {
7783+ f.flat_map(flat_escape_split_commas)
7784+ .collect::<Result<_, _>>()
7785+ })
7786+ .transpose()?
7787+ .unwrap_or_default(),
7788+ });
7789+ }
7790+ }
7791+ }
77527792 if script == "-" && watch.is_some() {
77537793 return Err(clap::Error::raw(
77547794 clap::error::ErrorKind::ArgumentConflict,
@@ -9186,6 +9226,94 @@ mod tests {
91869226 assert!(r.is_err());
91879227 }
91889228
9229+ #[test]
9230+ fn watch_subcommand() {
9231+ // `deno watch script.ts` is an alias for `deno run --watch-hmr script.ts`.
9232+ let r = flags_from_vec(svec!["deno", "watch", "script.ts"]);
9233+ let flags = r.unwrap();
9234+ assert_eq!(
9235+ flags,
9236+ Flags {
9237+ subcommand: DenoSubcommand::Run(RunFlags {
9238+ script: "script.ts".to_string(),
9239+ watch: Some(WatchFlagsWithPaths {
9240+ hmr: true,
9241+ paths: vec![],
9242+ no_clear_screen: false,
9243+ exclude: vec![],
9244+ }),
9245+ bare: false,
9246+ coverage_dir: None,
9247+ print_task_list: false,
9248+ }),
9249+ code_cache_enabled: true,
9250+ ..Flags::default()
9251+ }
9252+ );
9253+
9254+ // Additional watched paths and watch options are still respected.
9255+ let r = flags_from_vec(svec![
9256+ "deno",
9257+ "watch",
9258+ "--watch-hmr=foo.txt",
9259+ "--no-clear-screen",
9260+ "--watch-exclude=bar.txt",
9261+ "script.ts"
9262+ ]);
9263+ let flags = r.unwrap();
9264+ assert_eq!(
9265+ flags,
9266+ Flags {
9267+ subcommand: DenoSubcommand::Run(RunFlags {
9268+ script: "script.ts".to_string(),
9269+ watch: Some(WatchFlagsWithPaths {
9270+ hmr: true,
9271+ paths: vec![String::from("foo.txt")],
9272+ no_clear_screen: true,
9273+ exclude: vec![String::from("bar.txt")],
9274+ }),
9275+ bare: false,
9276+ coverage_dir: None,
9277+ print_task_list: false,
9278+ }),
9279+ code_cache_enabled: true,
9280+ ..Flags::default()
9281+ }
9282+ );
9283+
9284+ // `--watch-exclude` is honored even without an explicit `--watch-hmr` flag.
9285+ let r = flags_from_vec(svec![
9286+ "deno",
9287+ "watch",
9288+ "--watch-exclude=bar.txt",
9289+ "script.ts"
9290+ ]);
9291+ let flags = r.unwrap();
9292+ assert_eq!(
9293+ flags,
9294+ Flags {
9295+ subcommand: DenoSubcommand::Run(RunFlags {
9296+ script: "script.ts".to_string(),
9297+ watch: Some(WatchFlagsWithPaths {
9298+ hmr: true,
9299+ paths: vec![],
9300+ no_clear_screen: false,
9301+ exclude: vec![String::from("bar.txt")],
9302+ }),
9303+ bare: false,
9304+ coverage_dir: None,
9305+ print_task_list: false,
9306+ }),
9307+ code_cache_enabled: true,
9308+ ..Flags::default()
9309+ }
9310+ );
9311+
9312+ // Reading from stdin while watching is not supported.
9313+ let r = flags_from_vec(svec!["deno", "watch", "-"]);
9314+ assert!(r.is_err());
9315+ }
9316+
91899317 #[test]
91909318 fn run_watch_with_external() {
91919319 let r = flags_from_vec(svec!["deno", "--watch=file1,file2", "script.ts"]);
0 commit comments