mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -05:00
feat(watch): support watching external files (#13087)
This commit is contained in:
parent
d8e7e3fbe3
commit
5a3ded6611
4 changed files with 123 additions and 28 deletions
90
cli/flags.rs
90
cli/flags.rs
|
@ -251,7 +251,7 @@ pub struct Flags {
|
|||
pub unsafely_ignore_certificate_errors: Option<Vec<String>>,
|
||||
pub v8_flags: Vec<String>,
|
||||
pub version: bool,
|
||||
pub watch: bool,
|
||||
pub watch: Option<Vec<PathBuf>>,
|
||||
}
|
||||
|
||||
fn join_paths(allowlist: &[PathBuf], d: &str) -> String {
|
||||
|
@ -549,7 +549,7 @@ fn bundle_subcommand<'a, 'b>() -> App<'a, 'b> {
|
|||
.required(true),
|
||||
)
|
||||
.arg(Arg::with_name("out_file").takes_value(true).required(false))
|
||||
.arg(watch_arg())
|
||||
.arg(watch_arg(false))
|
||||
.about("Bundle module and dependencies into single file")
|
||||
.long_about(
|
||||
"Output a single JavaScript file with all dependencies.
|
||||
|
@ -882,7 +882,7 @@ Ignore formatting a file by adding an ignore comment at the top of the file:
|
|||
.multiple(true)
|
||||
.required(false),
|
||||
)
|
||||
.arg(watch_arg())
|
||||
.arg(watch_arg(false))
|
||||
.arg(
|
||||
Arg::with_name("options-use-tabs")
|
||||
.long("options-use-tabs")
|
||||
|
@ -1158,7 +1158,7 @@ Ignore linting a file by adding an ignore comment at the top of the file:
|
|||
.multiple(true)
|
||||
.required(false),
|
||||
)
|
||||
.arg(watch_arg())
|
||||
.arg(watch_arg(false))
|
||||
}
|
||||
|
||||
fn repl_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||
|
@ -1177,7 +1177,7 @@ fn repl_subcommand<'a, 'b>() -> App<'a, 'b> {
|
|||
fn run_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||
runtime_args(SubCommand::with_name("run"), true, true)
|
||||
.arg(
|
||||
watch_arg()
|
||||
watch_arg(true)
|
||||
.conflicts_with("inspect")
|
||||
.conflicts_with("inspect-brk"),
|
||||
)
|
||||
|
@ -1305,7 +1305,7 @@ fn test_subcommand<'a, 'b>() -> App<'a, 'b> {
|
|||
.multiple(true),
|
||||
)
|
||||
.arg(
|
||||
watch_arg()
|
||||
watch_arg(false)
|
||||
.conflicts_with("no-run")
|
||||
.conflicts_with("coverage"),
|
||||
)
|
||||
|
@ -1640,14 +1640,29 @@ fn compat_arg<'a, 'b>() -> Arg<'a, 'b> {
|
|||
.help("Node compatibility mode. Currently only enables built-in node modules like 'fs' and globals like 'process'.")
|
||||
}
|
||||
|
||||
fn watch_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name("watch")
|
||||
fn watch_arg<'a, 'b>(takes_files: bool) -> Arg<'a, 'b> {
|
||||
let arg = Arg::with_name("watch")
|
||||
.long("watch")
|
||||
.help("UNSTABLE: Watch for file changes and restart process automatically")
|
||||
.long_help(
|
||||
.help("UNSTABLE: Watch for file changes and restart process automatically");
|
||||
|
||||
if takes_files {
|
||||
arg
|
||||
.value_name("FILES")
|
||||
.min_values(0)
|
||||
.takes_value(true)
|
||||
.use_delimiter(true)
|
||||
.require_equals(true)
|
||||
.long_help(
|
||||
"UNSTABLE: Watch for file changes and restart process automatically.
|
||||
Local files from entry point module graph are watched by default.
|
||||
Additional paths might be watched by passing them as arguments to this flag.",
|
||||
)
|
||||
} else {
|
||||
arg.long_help(
|
||||
"UNSTABLE: Watch for file changes and restart process automatically.
|
||||
Only local files from entry point module graph are watched.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn no_check_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
|
@ -1743,7 +1758,7 @@ fn bundle_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
|||
None
|
||||
};
|
||||
|
||||
flags.watch = matches.is_present("watch");
|
||||
watch_arg_parse(flags, matches, false);
|
||||
|
||||
flags.subcommand = DenoSubcommand::Bundle(BundleFlags {
|
||||
source_file,
|
||||
|
@ -1874,7 +1889,7 @@ fn eval_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
|||
|
||||
fn fmt_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
||||
config_arg_parse(flags, matches);
|
||||
flags.watch = matches.is_present("watch");
|
||||
watch_arg_parse(flags, matches, false);
|
||||
let files = match matches.values_of("files") {
|
||||
Some(f) => f.map(PathBuf::from).collect(),
|
||||
None => vec![],
|
||||
|
@ -1996,7 +2011,7 @@ fn lsp_parse(flags: &mut Flags, _matches: &clap::ArgMatches) {
|
|||
|
||||
fn lint_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
||||
config_arg_parse(flags, matches);
|
||||
flags.watch = matches.is_present("watch");
|
||||
watch_arg_parse(flags, matches, false);
|
||||
let files = match matches.values_of("files") {
|
||||
Some(f) => f.map(PathBuf::from).collect(),
|
||||
None => vec![],
|
||||
|
@ -2061,7 +2076,7 @@ fn run_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
|||
flags.argv.push(v);
|
||||
}
|
||||
|
||||
flags.watch = matches.is_present("watch");
|
||||
watch_arg_parse(flags, matches, true);
|
||||
flags.subcommand = DenoSubcommand::Run(RunFlags { script });
|
||||
}
|
||||
|
||||
|
@ -2135,7 +2150,7 @@ fn test_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
|||
};
|
||||
|
||||
flags.coverage_dir = matches.value_of("coverage").map(String::from);
|
||||
flags.watch = matches.is_present("watch");
|
||||
watch_arg_parse(flags, matches, false);
|
||||
flags.subcommand = DenoSubcommand::Test(TestFlags {
|
||||
no_run,
|
||||
doc,
|
||||
|
@ -2409,6 +2424,20 @@ fn inspect_arg_validate(val: String) -> Result<(), String> {
|
|||
}
|
||||
}
|
||||
|
||||
fn watch_arg_parse(
|
||||
flags: &mut Flags,
|
||||
matches: &clap::ArgMatches,
|
||||
allow_extra: bool,
|
||||
) {
|
||||
if allow_extra {
|
||||
if let Some(f) = matches.values_of("watch") {
|
||||
flags.watch = Some(f.map(PathBuf::from).collect());
|
||||
}
|
||||
} else if matches.is_present("watch") {
|
||||
flags.watch = Some(vec![]);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(ry) move this to utility module and add test.
|
||||
/// Strips fragment part of URL. Panics on bad URL.
|
||||
pub fn resolve_urls(urls: Vec<String>) -> Vec<String> {
|
||||
|
@ -2513,7 +2542,24 @@ mod tests {
|
|||
subcommand: DenoSubcommand::Run(RunFlags {
|
||||
script: "script.ts".to_string(),
|
||||
}),
|
||||
watch: true,
|
||||
watch: Some(vec![]),
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_watch_with_external() {
|
||||
let r =
|
||||
flags_from_vec(svec!["deno", "run", "--watch=file1,file2", "script.ts"]);
|
||||
let flags = r.unwrap();
|
||||
assert_eq!(
|
||||
flags,
|
||||
Flags {
|
||||
subcommand: DenoSubcommand::Run(RunFlags {
|
||||
script: "script.ts".to_string(),
|
||||
}),
|
||||
watch: Some(vec![PathBuf::from("file1"), PathBuf::from("file2")]),
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
@ -2746,7 +2792,7 @@ mod tests {
|
|||
single_quote: None,
|
||||
prose_wrap: None,
|
||||
}),
|
||||
watch: true,
|
||||
watch: Some(vec![]),
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
@ -2773,7 +2819,7 @@ mod tests {
|
|||
single_quote: None,
|
||||
prose_wrap: None,
|
||||
}),
|
||||
watch: true,
|
||||
watch: Some(vec![]),
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
@ -2821,7 +2867,7 @@ mod tests {
|
|||
prose_wrap: None,
|
||||
}),
|
||||
config_path: Some("deno.jsonc".to_string()),
|
||||
watch: true,
|
||||
watch: Some(vec![]),
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
@ -3543,7 +3589,7 @@ mod tests {
|
|||
source_file: "source.ts".to_string(),
|
||||
out_file: None,
|
||||
}),
|
||||
watch: true,
|
||||
watch: Some(vec![]),
|
||||
..Flags::default()
|
||||
}
|
||||
)
|
||||
|
@ -4280,7 +4326,7 @@ mod tests {
|
|||
ignore: vec![],
|
||||
concurrent_jobs: NonZeroUsize::new(1).unwrap(),
|
||||
}),
|
||||
watch: false,
|
||||
watch: None,
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
@ -4303,7 +4349,7 @@ mod tests {
|
|||
ignore: vec![],
|
||||
concurrent_jobs: NonZeroUsize::new(1).unwrap(),
|
||||
}),
|
||||
watch: true,
|
||||
watch: Some(vec![]),
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
|
18
cli/main.rs
18
cli/main.rs
|
@ -577,7 +577,8 @@ async fn lint_command(
|
|||
None
|
||||
};
|
||||
|
||||
tools::lint::lint(maybe_lint_config, lint_flags, flags.watch).await?;
|
||||
tools::lint::lint(maybe_lint_config, lint_flags, flags.watch.is_some())
|
||||
.await?;
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
|
@ -894,7 +895,7 @@ async fn bundle_command(
|
|||
}
|
||||
};
|
||||
|
||||
if flags.watch {
|
||||
if flags.watch.is_some() {
|
||||
file_watcher::watch_func(resolver, operation, "Bundle").await?;
|
||||
} else {
|
||||
let module_graph =
|
||||
|
@ -943,7 +944,8 @@ async fn format_command(
|
|||
return Ok(0);
|
||||
}
|
||||
|
||||
tools::fmt::format(fmt_flags, flags.watch, maybe_fmt_config).await?;
|
||||
tools::fmt::format(fmt_flags, flags.watch.is_some(), maybe_fmt_config)
|
||||
.await?;
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
|
@ -1011,6 +1013,7 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<i32, AnyError> {
|
|||
let script1 = script.clone();
|
||||
let script2 = script.clone();
|
||||
let flags = flags.clone();
|
||||
let watch_flag = flags.watch.clone();
|
||||
async move {
|
||||
let main_module = resolve_url_or_path(&script1)?;
|
||||
let ps = ProcState::build(flags).await?;
|
||||
|
@ -1067,6 +1070,11 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<i32, AnyError> {
|
|||
})
|
||||
.collect();
|
||||
|
||||
// Add the extra files listed in the watch flag
|
||||
if let Some(watch_paths) = watch_flag {
|
||||
paths_to_watch.extend(watch_paths);
|
||||
}
|
||||
|
||||
if let Some(import_map) = ps.flags.import_map_path.as_ref() {
|
||||
paths_to_watch
|
||||
.push(fs_util::resolve_from_cwd(std::path::Path::new(import_map))?);
|
||||
|
@ -1180,7 +1188,7 @@ async fn run_command(
|
|||
return run_from_stdin(flags).await;
|
||||
}
|
||||
|
||||
if flags.watch {
|
||||
if flags.watch.is_some() {
|
||||
return run_with_watch(flags, run_flags.script).await;
|
||||
}
|
||||
|
||||
|
@ -1294,7 +1302,7 @@ async fn test_command(
|
|||
);
|
||||
}
|
||||
|
||||
if flags.watch {
|
||||
if flags.watch.is_some() {
|
||||
tools::test::run_tests_with_watch(
|
||||
flags,
|
||||
test_flags.include,
|
||||
|
|
|
@ -575,6 +575,47 @@ fn run_watch() {
|
|||
check_alive_then_kill(child);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_watch_external_watch_files() {
|
||||
let t = TempDir::new().unwrap();
|
||||
let file_to_watch = t.path().join("file_to_watch.js");
|
||||
write(&file_to_watch, "console.log('Hello world');").unwrap();
|
||||
|
||||
let external_file_to_watch = t.path().join("external_file_to_watch.txt");
|
||||
write(&external_file_to_watch, "Hello world").unwrap();
|
||||
|
||||
let mut watch_arg = "--watch=".to_owned();
|
||||
let external_file_to_watch_str = external_file_to_watch
|
||||
.clone()
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap();
|
||||
watch_arg.push_str(&external_file_to_watch_str);
|
||||
|
||||
let mut child = util::deno_cmd()
|
||||
.current_dir(util::testdata_path())
|
||||
.arg("run")
|
||||
.arg(watch_arg)
|
||||
.arg("--unstable")
|
||||
.arg(&file_to_watch)
|
||||
.env("NO_COLOR", "1")
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child);
|
||||
|
||||
assert_contains!(stdout_lines.next().unwrap(), "Hello world");
|
||||
wait_for("Process finished", &mut stderr_lines);
|
||||
|
||||
// Change content of the external file
|
||||
write(&external_file_to_watch, "Hello world2").unwrap();
|
||||
|
||||
assert_contains!(stderr_lines.next().unwrap(), "Restarting");
|
||||
wait_for("Process finished", &mut stderr_lines);
|
||||
check_alive_then_kill(child);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_watch_load_unload_events() {
|
||||
let t = TempDir::new().unwrap();
|
||||
|
|
|
@ -239,6 +239,6 @@ pub fn compile_to_runtime_flags(
|
|||
unstable: flags.unstable,
|
||||
v8_flags: flags.v8_flags,
|
||||
version: false,
|
||||
watch: false,
|
||||
watch: None,
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue