From 71393370832946a33e8a34d2e9cfaad8bf97c91b Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Wed, 21 Aug 2024 06:54:59 -0700 Subject: [PATCH] feat(flags): improve help output and make `deno run` list tasks (#25108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rewrite flag help - use gray for indentation - reorganize permission flags and split them up - make help subcommand act like help flag - `deno run` outputs list of tasks - Fixes #25120 error handling for `deno run` in case of no config file being found needs to be improved --------- Co-authored-by: Bartek IwaƄczuk --- Cargo.lock | 12 +- cli/Cargo.toml | 1 + cli/args/flags.rs | 471 +++++++++++++++++++++++++--------------------- cli/main.rs | 21 ++- cli/tools/task.rs | 17 +- 5 files changed, 300 insertions(+), 222 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65e70f9b41..efda3f836b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,15 +152,16 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] @@ -1146,6 +1147,7 @@ dependencies = [ name = "deno" version = "1.46.0-rc.3" dependencies = [ + "anstream", "async-trait", "base32", "base64 0.21.7", @@ -3871,6 +3873,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index d0b871f493..087b524774 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -83,6 +83,7 @@ libsui = "0.3.0" napi_sym.workspace = true node_resolver.workspace = true +anstream = "0.6.14" async-trait.workspace = true base32.workspace = true base64.workspace = true diff --git a/cli/args/flags.rs b/cli/args/flags.rs index a273d8b370..de63583599 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -9,6 +9,7 @@ use clap::ArgMatches; use clap::ColorChoice; use clap::Command; use clap::ValueHint; +use color_print::cstr; use deno_config::glob::FilePatterns; use deno_config::glob::PathOrPatternSet; use deno_core::anyhow::bail; @@ -376,6 +377,7 @@ pub struct WatchFlagsWithPaths { pub struct TaskFlags { pub cwd: Option, pub task: Option, + pub is_run: bool, } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] @@ -1107,7 +1109,7 @@ impl Flags { } } -static ENV_VARIABLES_HELP: &str = color_print::cstr!( +static ENV_VARIABLES_HELP: &str = cstr!( r#"Environment variables: DENO_AUTH_TOKENS A semi-colon separated list of bearer tokens and hostnames to use when fetching remote modules from private repositories @@ -1132,7 +1134,7 @@ static ENV_VARIABLES_HELP: &str = color_print::cstr!( NPM_CONFIG_REGISTRY URL to use for the npm registry."# ); -static DENO_HELP: &str = color_print::cstr!( +static DENO_HELP: &str = cstr!( "Deno: A modern JavaScript and TypeScript runtime Usage: {usage} @@ -1247,6 +1249,19 @@ pub fn flags_from_vec(args: Vec) -> clap::error::Result { app }; + help_parse(&mut flags, subcommand); + return Ok(flags); + } else if let Some(help_subcommand_matches) = + matches.subcommand_matches("help") + { + app.build(); + let subcommand = + if let Some(sub) = help_subcommand_matches.subcommand_name() { + app.find_subcommand(sub).unwrap().clone() + } else { + app + }; + help_parse(&mut flags, subcommand); return Ok(flags); } @@ -1283,15 +1298,6 @@ pub fn flags_from_vec(args: Vec) -> clap::error::Result { "upgrade" => upgrade_parse(&mut flags, &mut m), "vendor" => vendor_parse(&mut flags, &mut m), "publish" => publish_parse(&mut flags, &mut m), - "help" => { - let subcommand = if let Some((sub, _)) = matches.remove_subcommand() { - app.find_subcommand(sub).unwrap().clone() - } else { - app - }; - - help_parse(&mut flags, subcommand) - } _ => unreachable!(), } } else { @@ -1373,12 +1379,7 @@ fn help_parse(flags: &mut Flags, mut subcommand: Command) { for (mut i, (arg, heading)) in args.into_iter().enumerate() { if let Some(heading) = heading { let heading_i = HEADING_ORDER.iter().position(|h| h == &heading).unwrap(); - i += (if heading == UNSTABLE_HEADING { - // ensures the unstable section is always last - 50 - } else { - heading_i - }) * 100; + i += heading_i * 100; } subcommand = subcommand.mut_arg(arg, |arg| arg.display_order(i)); @@ -1546,11 +1547,10 @@ fn command( fn help_subcommand(app: &Command) -> Command { command("help", None, UnstableArgsConfig::None) - .hide(true) + .disable_version_flag(true) .disable_help_subcommand(true) .subcommands(app.get_subcommands().map(|command| { Command::new(command.get_name().to_owned()) - .hide(true) .disable_help_flag(true) .disable_version_flag(true) })) @@ -1582,13 +1582,15 @@ You can add multiple dependencies at once: fn remove_subcommand() -> Command { command( "remove", - "Remove dependencies from the configuration file. + cstr!( + "Remove dependencies from the configuration file. deno remove @std/path You can remove multiple dependencies at once: deno remove @std/path @std/assert -", +" + ), UnstableArgsConfig::None, ) .defer(|cmd| { @@ -1637,7 +1639,9 @@ glob {*_,*.,}bench.{js,mjs,ts,mts,jsx,tsx}: Arg::new("filter") .long("filter") .allow_hyphen_values(true) - .help("Run benchmarks with this string or pattern in the bench name"), + .help( + "Run benchmarks with this string or regexp pattern in the bench name", + ), ) .arg( Arg::new("files") @@ -1743,7 +1747,7 @@ Unless --reload is specified, this command will not re-download already cached d // past alias for --all Arg::new("remote") .long("remote") - .help("Type-check all modules, including remote") + .help("Type-check all modules, including remote ones") .action(ArgAction::SetTrue) .conflicts_with("no-remote") .hide(true) @@ -1790,11 +1794,11 @@ supported in canary. Arg::new("include") .long("include") .help( - "Includes an additional module in the compiled executable's module - graph. Use this flag if a dynamically imported module or a web worker main - module fails to load in the executable. This flag can be passed multiple - times, to include multiple additional modules.", - ) + cstr!("Includes an additional module in the compiled executable's module graph. + Use this flag if a dynamically imported module or a web worker main module + fails to load in the executable. This flag can be passed multiple times, + to include multiple additional modules.", + )) .action(ArgAction::Append) .value_hint(ValueHint::FilePath) .help_heading(COMPILE_HEADING), @@ -1804,7 +1808,7 @@ supported in canary. .long("output") .short('o') .value_parser(value_parser!(String)) - .help("Output file (defaults to $PWD/)") + .help(cstr!("Output file (defaults to $PWD/<>)")) .value_hint(ValueHint::FilePath) .help_heading(COMPILE_HEADING), ) @@ -1936,10 +1940,9 @@ Generate html reports from lcov: .long("output") .value_parser(value_parser!(String)) .help( - "Exports the coverage report in lcov format to the given file. - Filename should be passed along with '=' For example '--output=foo.lcov' - If no --output arg is specified then the report is written to stdout.", - ) + cstr!("Exports the coverage report in lcov format to the given file. + If no --output arg is specified then the report is written to stdout.", + )) .require_equals(true) .value_hint(ValueHint::FilePath), ) @@ -1952,7 +1955,7 @@ Generate html reports from lcov: .arg( Arg::new("detailed") .long("detailed") - .help("Output coverage report in detailed format in the terminal.") + .help("Output coverage report in detailed format in the terminal") .action(ArgAction::SetTrue), ) .arg( @@ -2019,7 +2022,7 @@ Show documentation for runtime built-ins: .arg( Arg::new("name") .long("name") - .help("The name that will be displayed in the docs") + .help("The name that will be used in the docs (ie for breadcrumbs)") .action(ArgAction::Set) .require_equals(true).help_heading(DOC_HEADING) ) @@ -2042,14 +2045,14 @@ Show documentation for runtime built-ins: .arg( Arg::new("strip-trailing-html") .long("strip-trailing-html") - .help("Remove trailing .html from various links. Will still generate files with a .html extension.") + .help("Remove trailing .html from various links. Will still generate files with a .html extension") .requires("html") .action(ArgAction::SetTrue).help_heading(DOC_HEADING) ) .arg( Arg::new("default-symbol-map") .long("default-symbol-map") - .help("Uses the provided mapping of default name to wanted name for usage blocks.") + .help("Uses the provided mapping of default name to wanted name for usage blocks") .requires("html") .action(ArgAction::Set) .require_equals(true).help_heading(DOC_HEADING) @@ -2135,7 +2138,7 @@ This command has implicit access to all permissions (--allow-all).", Arg::new("code_arg") .num_args(1..) .action(ArgAction::Append) - .help("Code arg") + .help("Code to evaluate") .value_name("CODE_ARG") .required_unless_present("help"), ) @@ -2167,7 +2170,7 @@ Ignore formatting a file by adding an ignore comment at the top of the file: .arg(config_arg()) .arg(no_config_arg()) .arg( - Arg::new("check") + Arg::new("check") .long("check") .help("Check if the source files are formatted") .num_args(0) @@ -2214,7 +2217,7 @@ Ignore formatting a file by adding an ignore comment at the top of the file: .default_missing_value("true") .require_equals(true) .help( - "Use tabs instead of spaces for indentation. Defaults to false.", + cstr!( "Use tabs instead of spaces for indentation [default: false]"), ) .help_heading(FMT_HEADING), ) @@ -2222,7 +2225,7 @@ Ignore formatting a file by adding an ignore comment at the top of the file: Arg::new("line-width") .long("line-width") .alias("options-line-width") - .help("Define maximum line width. Defaults to 80.") + .help(cstr!("Define maximum line width [default: 80]")) .value_parser(value_parser!(NonZeroU32)) .help_heading(FMT_HEADING), ) @@ -2230,7 +2233,7 @@ Ignore formatting a file by adding an ignore comment at the top of the file: Arg::new("indent-width") .long("indent-width") .alias("options-indent-width") - .help("Define indentation width. Defaults to 2.") + .help(cstr!("Define indentation width [default: 2]")) .value_parser(value_parser!(NonZeroU8)) .help_heading(FMT_HEADING), ) @@ -2242,7 +2245,7 @@ Ignore formatting a file by adding an ignore comment at the top of the file: .value_parser(value_parser!(bool)) .default_missing_value("true") .require_equals(true) - .help("Use single quotes. Defaults to false.") + .help(cstr!("Use single quotes [default: false]")) .help_heading(FMT_HEADING), ) .arg( @@ -2250,7 +2253,7 @@ Ignore formatting a file by adding an ignore comment at the top of the file: .long("prose-wrap") .alias("options-prose-wrap") .value_parser(["always", "never", "preserve"]) - .help("Define how prose should be wrapped. Defaults to always.") + .help(cstr!("Define how prose should be wrapped [default: always]")) .help_heading(FMT_HEADING), ) .arg( @@ -2262,14 +2265,14 @@ Ignore formatting a file by adding an ignore comment at the top of the file: .default_missing_value("true") .require_equals(true) .help( - "Don't use semicolons except where necessary. Defaults to false.", + cstr!("Don't use semicolons except where necessary [default: false]"), ) .help_heading(FMT_HEADING), ) .arg( Arg::new("unstable-css") .long("unstable-css") - .help("Enable formatting CSS, SCSS, Sass and Less files.") + .help("Enable formatting CSS, SCSS, Sass and Less files") .value_parser(FalseyValueParser::new()) .action(ArgAction::SetTrue) .help_heading(FMT_HEADING), @@ -2277,21 +2280,23 @@ Ignore formatting a file by adding an ignore comment at the top of the file: .arg( Arg::new("unstable-html") .long("unstable-html") - .help("Enable formatting HTML files.") + .help("Enable formatting HTML files") .value_parser(FalseyValueParser::new()) - .action(ArgAction::SetTrue), + .action(ArgAction::SetTrue) + .help_heading(FMT_HEADING), ) .arg( Arg::new("unstable-component") .long("unstable-component") - .help("Enable formatting Svelte, Vue, Astro and Angular files.") + .help("Enable formatting Svelte, Vue, Astro and Angular files") .value_parser(FalseyValueParser::new()) - .action(ArgAction::SetTrue), + .action(ArgAction::SetTrue) + .help_heading(FMT_HEADING), ) .arg( Arg::new("unstable-yaml") .long("unstable-yaml") - .help("Enable formatting YAML files.") + .help("Enable formatting YAML files") .value_parser(FalseyValueParser::new()) .action(ArgAction::SetTrue) .help_heading(FMT_HEADING), @@ -2304,7 +2309,12 @@ fn init_subcommand() -> Command { |cmd| { cmd .arg(Arg::new("dir").value_hint(ValueHint::DirPath)) - .arg(Arg::new("lib").long("lib").action(ArgAction::SetTrue)) + .arg( + Arg::new("lib") + .long("lib") + .help("Generate an example library project") + .action(ArgAction::SetTrue), + ) .arg( Arg::new("serve") .long("serve") @@ -2707,18 +2717,18 @@ fn repl_subcommand() -> Command { .num_args(1..) .use_value_delimiter(true) .require_equals(true) - .help("Evaluates the provided file(s) as scripts when the REPL starts. Accepts file paths and URLs.") + .help("Evaluates the provided file(s) as scripts when the REPL starts. Accepts file paths and URLs") .value_hint(ValueHint::AnyPath), ) .arg( Arg::new("eval") .long("eval") - .help("Evaluates the provided code when the REPL starts.") + .help("Evaluates the provided code when the REPL starts") .value_name("code"), ) - .after_help(color_print::cstr!("Environment variables: + .after_help(cstr!("Environment variables: DENO_REPL_HISTORY Set REPL history file path. History file is disabled when the value is empty. - (defaults to $DENO_DIR/deno_history.txt)")) + [default: $DENO_DIR/deno_history.txt]")) ) .arg(env_file_arg()) } @@ -2734,16 +2744,14 @@ fn run_args(command: Command, top_level: bool) -> Command { .arg(if top_level { script_arg().trailing_var_arg(true).hide(true) } else { - script_arg() - .required_unless_present_any(["help", "v8-flags"]) - .trailing_var_arg(true) + script_arg().trailing_var_arg(true) }) .arg(env_file_arg()) .arg(no_code_cache_arg()) } fn run_subcommand() -> Command { - run_args(command("run", color_print::cstr!("Run a JavaScript or TypeScript program, or a task or script. + run_args(command("run", cstr!("Run a JavaScript or TypeScript program, or a task or script. By default all programs are run in sandbox without access to disk, network or ability to spawn subprocesses. deno run https://examples.deno.land/hello-world.ts @@ -2774,13 +2782,13 @@ fn serve_subcommand() -> Command { .arg( Arg::new("port") .long("port") - .help("The TCP port to serve on, defaulting to 8000. Pass 0 to pick a random free port.") + .help("The TCP port to serve on, defaulting to 8000. Pass 0 to pick a random free port") .value_parser(value_parser!(u16)), ) .arg( Arg::new("host") .long("host") - .help("The TCP address to serve on, defaulting to 0.0.0.0 (all interfaces).") + .help("The TCP address to serve on, defaulting to 0.0.0.0 (all interfaces)") .value_parser(serve_host_validator), ) .arg( @@ -2874,7 +2882,7 @@ Directory arguments are expanded to all contained files matching the glob .arg( Arg::new("trace-ops") .long("trace-ops") - .help("Deprecated alias for --trace-leaks.") + .help("Deprecated alias for --trace-leaks") .hide(true) .action(ArgAction::SetTrue) .help_heading(TEST_HEADING), @@ -2882,7 +2890,7 @@ Directory arguments are expanded to all contained files matching the glob .arg( Arg::new("trace-leaks") .long("trace-leaks") - .help("Enable tracing of leaks. Useful when debugging leaking ops in test, but impacts test execution time.") + .help("Enable tracing of leaks. Useful when debugging leaking ops in test, but impacts test execution time") .action(ArgAction::SetTrue) .help_heading(TEST_HEADING), ) @@ -2897,7 +2905,7 @@ Directory arguments are expanded to all contained files matching the glob Arg::new("fail-fast") .long("fail-fast") .alias("failfast") - .help("Stop after N errors. Defaults to stopping after first failure.") + .help("Stop after N errors. Defaults to stopping after first failure") .num_args(0..=1) .require_equals(true) .value_name("N") @@ -2923,7 +2931,7 @@ Directory arguments are expanded to all contained files matching the glob Arg::new("filter") .allow_hyphen_values(true) .long("filter") - .help("Run tests with this string or pattern in the test name") + .help("Run tests with this string or regexp pattern in the test name") .help_heading(TEST_HEADING), ) .arg( @@ -2946,14 +2954,14 @@ Directory arguments are expanded to all contained files matching the glob .conflicts_with("inspect") .conflicts_with("inspect-wait") .conflicts_with("inspect-brk") - .help("Collect coverage profile data into DIR. If DIR is not specified, it uses 'coverage/'.") + .help("Collect coverage profile data into DIR. If DIR is not specified, it uses 'coverage/'") .help_heading(TEST_HEADING), ) .arg( Arg::new("clean") .long("clean") - .help("Empty the temporary coverage profile data directory before running tests. - Note: running multiple `deno test --clean` calls in series or parallel for the same coverage directory may cause race conditions.") + .help(cstr!("Empty the temporary coverage profile data directory before running tests. + Note: running multiple `deno test --clean` calls in series or parallel for the same coverage directory may cause race conditions.")) .action(ArgAction::SetTrue) .help_heading(TEST_HEADING), ) @@ -2990,13 +2998,13 @@ Directory arguments are expanded to all contained files matching the glob .long("junit-path") .value_name("PATH") .value_hint(ValueHint::FilePath) - .help("Write a JUnit XML test report to PATH. Use '-' to write to stdout which is the default when PATH is not provided.") + .help("Write a JUnit XML test report to PATH. Use '-' to write to stdout which is the default when PATH is not provided") .help_heading(TEST_HEADING) ) .arg( Arg::new("reporter") .long("reporter") - .help("Select reporter to use. Default to 'pretty'.") + .help("Select reporter to use. Default to 'pretty'") .value_parser(["pretty", "dot", "junit", "tap"]) .help_heading(TEST_HEADING) ) @@ -3013,7 +3021,7 @@ Directory arguments are expanded to all contained files matching the glob fn parallel_arg(descr: &str, jobs_fallback: bool) -> Arg { let arg = Arg::new("parallel") .long("parallel") - .help(format!("Run {descr} in parallel. Parallelism defaults to the number of available CPUs or the value of the DENO_JOBS environment variable.")) + .help(format!("Run {descr} in parallel. Parallelism defaults to the number of available CPUs or the value of the DENO_JOBS environment variable")) .action(ArgAction::SetTrue); if jobs_fallback { arg.conflicts_with("jobs") @@ -3216,7 +3224,8 @@ fn publish_subcommand() -> Command { ).arg( Arg::new("no-provenance") .long("no-provenance") - .help("Disable provenance attestation. Enabled by default on Github actions, publicly links the package to where it was built and published from.") + .help(cstr!("Disable provenance attestation. + Enabled by default on Github actions, publicly links the package to where it was built and published from.")) .action(ArgAction::SetTrue) .help_heading(PUBLISH_HEADING) ) @@ -3248,33 +3257,51 @@ fn compile_args_without_check_args(app: Command) -> Command { fn permission_args(app: Command) -> Command { app - .after_help(color_print::cstr!(r#"Permission options: -Docs: https://docs.deno.com/go/permissions + .after_help(cstr!(r#"Permission options: +Docs: https://docs.deno.com/go/permissions - -A, --allow-all Allow all permissions. - -R, --{allow,deny}-read[=<...] Allow / deny file system read access. Optionally specify allowed / denied paths. - --allow-read | --allow-read="/etc,/var/log.txt" | --deny-read="/etc/hosts" - -W, --{allow,write}-read[=<...] Allow / deny file system write access. Optionally specify allowed / denied paths. - --allow-write | --allow-write="/etc,/var/log.txt" | --deny-write="/etc/hosts" - -N, --{allow,deny}-net[=<...] Allow / deny network access. Optionally specify allowed IP addresses and host names, with ports as necessary. - --allow-net | --allow-net="localhost:8080,deno.land" | --deny-net="deno.com" - -E --{allow,deny}-env[=<...] Allow / deny access to environment variables. Optionally specify accessible / inacessible environment variables. - --allow-env | --allow-env="PORT,HOME,PATH" | --deny-env="ACCESS_TOKEN" - -S --{allow,deny}-sys[=<...] Allow / deny access to OS information. Optionally allow / deny specific APIs by function name. - --allow-sys | --allow-sys="systemMemoryInfo,osRelease" | --deny-sys="hostname" - --{allow,deny}-run[=<...] Allow / deny running subprocesses. Optionally specify allowed / denied runnable program names. - --allow-run | --allow-run="whoami,ps" | --deny-run="cat" - --{allow,deny}-ffi[=<...] (Unstable) Allow / deny loading dynamic libraries. Optionally specify allowed / denied directories or files. - --allow-ffi | --allow-ffi="./libfoo.so" | --deny-ffi="./libfoo.so" - --{allow,deny}-hrtime Allow / deny high-resolution time measurement. Note: this can enable timing attacks and fingerprinting. - --no-prompt Always throw if required permission wasn't passed. Can also be set via the DENO_NO_PROMPT environment variable. + -A, --allow-all Allow all permissions. + --no-prompt Always throw if required permission wasn't passed. + Can also be set via the DENO_NO_PROMPT environment variable. + -R, --allow-read[=<...] Allow file system read access. Optionally specify allowed paths. + --allow-read | --allow-read="/etc,/var/log.txt" + -W, --allow-write[=<...] Allow file system write access. Optionally specify allowed paths. + --allow-write | --allow-write="/etc,/var/log.txt" + -N, --allow-net[=<...] Allow network access. Optionally specify allowed IP addresses and host names, with ports as necessary. + --allow-net | --allow-net="localhost:8080,deno.land" + -E, --allow-env[=<...] Allow access to environment variables. Optionally specify accessible environment variables. + --allow-env | --allow-env="PORT,HOME,PATH" + -S, --allow-sys[=<...] Allow access to OS information. Optionally allow specific APIs by function name. + --allow-sys | --allow-sys="systemMemoryInfo,osRelease" + --allow-run[=<...] Allow running subprocesses. Optionally specify allowed runnable program names. + --allow-run | --allow-run="whoami,ps" + --allow-ffi[=<...] (Unstable) Allow loading dynamic libraries. Optionally specify allowed directories or files. + --allow-ffi | --allow-ffi="./libfoo.so" + --allow-hrtime Allow high-resolution time measurement. Note: this can enable timing attacks and fingerprinting. + --allow-hrtime + --deny-read[=<...] Deny file system read access. Optionally specify denied paths. + --deny-read | --deny-read="/etc,/var/log.txt" + --deny-write[=<...] Deny file system write access. Optionally specify denied paths. + --deny-write | --deny-write="/etc,/var/log.txt" + --deny-net[=<...] Deny network access. Optionally specify defined IP addresses and host names, with ports as necessary. + --deny-net | --deny-net="localhost:8080,deno.land" + --deny-env[=<...] Deny access to environment variables. Optionally specify inacessible environment variables. + --deny-env | --deny-env="PORT,HOME,PATH" + -S, --deny-sys[=<...] Deny access to OS information. Optionally deny specific APIs by function name. + --deny-sys | --deny-sys="systemMemoryInfo,osRelease" + --deny-run[=<...] Deny running subprocesses. Optionally specify denied runnable program names. + --deny-run | --deny-run="whoami,ps" + --deny-ffi[=<...] (Unstable) Deny loading dynamic libraries. Optionally specify denied directories or files. + --deny-ffi | --deny-ffi="./libfoo.so" + --deny-hrtime Deny high-resolution time measurement. + --deny-hrtime "#)) .arg( Arg::new("allow-all") .short('A') .long("allow-all") .action(ArgAction::SetTrue) - .help("Allow all permissions.") + .help("Allow all permissions") .hide(true), ) .arg( @@ -3285,7 +3312,7 @@ Docs: https://docs.deno.com/go/permissions .use_value_delimiter(true) .require_equals(true) .value_name("PATH") - .help("Allow file system read access. Optionally specify allowed paths.") + .help("Allow file system read access. Optionally specify allowed paths") .value_parser(value_parser!(String)) .value_hint(ValueHint::AnyPath) .hide(true), @@ -3297,7 +3324,7 @@ Docs: https://docs.deno.com/go/permissions .use_value_delimiter(true) .require_equals(true) .value_name("PATH") - .help("Deny file system read access. Optionally specify denied paths.") + .help("Deny file system read access. Optionally specify denied paths") .value_parser(value_parser!(String)) .value_hint(ValueHint::AnyPath) .hide(true), @@ -3310,7 +3337,7 @@ Docs: https://docs.deno.com/go/permissions .use_value_delimiter(true) .require_equals(true) .value_name("PATH") - .help("Allow file system write access. Optionally specify allowed paths.") + .help("Allow file system write access. Optionally specify allowed paths") .value_parser(value_parser!(String)) .value_hint(ValueHint::AnyPath) .hide(true), @@ -3322,7 +3349,7 @@ Docs: https://docs.deno.com/go/permissions .use_value_delimiter(true) .require_equals(true) .value_name("PATH") - .help("Deny file system write access. Optionally specify denied paths.") + .help("Deny file system write access. Optionally specify denied paths") .value_parser(value_parser!(String)) .value_hint(ValueHint::AnyPath) .hide(true), @@ -3335,7 +3362,7 @@ Docs: https://docs.deno.com/go/permissions .use_value_delimiter(true) .require_equals(true) .value_name("IP_OR_HOSTNAME") - .help("Allow network access. Optionally specify allowed IP addresses and host names, with ports as necessary.") + .help("Allow network access. Optionally specify allowed IP addresses and host names, with ports as necessary") .value_parser(flags_net::validator) .hide(true), ) @@ -3346,7 +3373,7 @@ Docs: https://docs.deno.com/go/permissions .use_value_delimiter(true) .require_equals(true) .value_name("IP_OR_HOSTNAME") - .help("Deny network access. Optionally specify denied IP addresses and host names, with ports as necessary.") + .help("Deny network access. Optionally specify denied IP addresses and host names, with ports as necessary") .value_parser(flags_net::validator) .hide(true), ) @@ -3358,7 +3385,7 @@ Docs: https://docs.deno.com/go/permissions .use_value_delimiter(true) .require_equals(true) .value_name("VARIABLE_NAME") - .help("Allow access to system environment information. Optionally specify accessible environment variables.") + .help("Allow access to system environment information. Optionally specify accessible environment variables") .value_parser(|key: &str| { if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { return Err(format!("invalid key \"{key}\"")); @@ -3379,7 +3406,7 @@ Docs: https://docs.deno.com/go/permissions .use_value_delimiter(true) .require_equals(true) .value_name("VARIABLE_NAME") - .help("Deny access to system environment information. Optionally specify accessible environment variables.") + .help("Deny access to system environment information. Optionally specify accessible environment variables") .value_parser(|key: &str| { if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { return Err(format!("invalid key \"{key}\"")); @@ -3401,7 +3428,7 @@ Docs: https://docs.deno.com/go/permissions .use_value_delimiter(true) .require_equals(true) .value_name("API_NAME") - .help("Allow access to OS information. Optionally allow specific APIs by function name.") + .help("Allow access to OS information. Optionally allow specific APIs by function name") .value_parser(|key: &str| parse_sys_kind(key).map(ToString::to_string)) .hide(true), ) @@ -3412,7 +3439,7 @@ Docs: https://docs.deno.com/go/permissions .use_value_delimiter(true) .require_equals(true) .value_name("API_NAME") - .help("Deny access to OS information. Optionally deny specific APIs by function name.") + .help("Deny access to OS information. Optionally deny specific APIs by function name") .value_parser(|key: &str| parse_sys_kind(key).map(ToString::to_string)) .hide(true), ) @@ -3423,7 +3450,7 @@ Docs: https://docs.deno.com/go/permissions .use_value_delimiter(true) .require_equals(true) .value_name("PROGRAM_NAME") - .help("Allow running subprocesses. Optionally specify allowed runnable program names.") + .help("Allow running subprocesses. Optionally specify allowed runnable program names") .hide(true), ) .arg( @@ -3433,7 +3460,7 @@ Docs: https://docs.deno.com/go/permissions .use_value_delimiter(true) .require_equals(true) .value_name("PROGRAM_NAME") - .help("Deny running subprocesses. Optionally specify denied runnable program names.") + .help("Deny running subprocesses. Optionally specify denied runnable program names") .hide(true), ) .arg( @@ -3443,7 +3470,7 @@ Docs: https://docs.deno.com/go/permissions .use_value_delimiter(true) .require_equals(true) .value_name("PATH") - .help("(Unstable) Allow loading dynamic libraries. Optionally specify allowed directories or files.") + .help("(Unstable) Allow loading dynamic libraries. Optionally specify allowed directories or files") .value_parser(value_parser!(String)) .value_hint(ValueHint::AnyPath) .hide(true), @@ -3455,7 +3482,7 @@ Docs: https://docs.deno.com/go/permissions .use_value_delimiter(true) .require_equals(true) .value_name("PATH") - .help("(Unstable) Deny loading dynamic libraries. Optionally specify denied directories or files.") + .help("(Unstable) Deny loading dynamic libraries. Optionally specify denied directories or files") .value_parser(value_parser!(String)) .value_hint(ValueHint::AnyPath) .hide(true), @@ -3464,14 +3491,14 @@ Docs: https://docs.deno.com/go/permissions Arg::new("allow-hrtime") .long("allow-hrtime") .action(ArgAction::SetTrue) - .help("Allow high-resolution time measurement. Note: this can enable timing attacks and fingerprinting.") + .help("Allow high-resolution time measurement. Note: this can enable timing attacks and fingerprinting") .hide(true), ) .arg( Arg::new("deny-hrtime") .long("deny-hrtime") .action(ArgAction::SetTrue) - .help("Deny high-resolution time measurement. Note: this can prevent timing attacks and fingerprinting.") + .help("Deny high-resolution time measurement. Note: this can prevent timing attacks and fingerprinting") .hide(true), ) .arg( @@ -3515,7 +3542,8 @@ fn inspect_args(app: Command) -> Command { Arg::new("inspect") .long("inspect") .value_name("HOST_AND_PORT") - .help("Activate inspector on host:port (default: 127.0.0.1:9229)") + .default_missing_value("127.0.0.1:9229") + .help(cstr!("Activate inspector on host:port [default: 127.0.0.1:9229]")) .num_args(0..=1) .require_equals(true) .value_parser(value_parser!(SocketAddr)) @@ -3525,6 +3553,7 @@ fn inspect_args(app: Command) -> Command { Arg::new("inspect-brk") .long("inspect-brk") .value_name("HOST_AND_PORT") + .default_missing_value("127.0.0.1:9229") .help( "Activate inspector on host:port, wait for debugger to connect and break at the start of user script", ) @@ -3537,6 +3566,7 @@ fn inspect_args(app: Command) -> Command { Arg::new("inspect-wait") .long("inspect-wait") .value_name("HOST_AND_PORT") + .default_missing_value("127.0.0.1:9229") .help( "Activate inspector on host:port and wait for debugger to connect before running user code", ) @@ -3552,10 +3582,10 @@ fn import_map_arg() -> Arg { .long("import-map") .alias("importmap") .value_name("FILE") - .help( - "Load import map file from local file or remote URL. - Docs: https://docs.deno.com/runtime/manual/basics/import_maps", - ) + .help(cstr!( + "Load import map file from local file or remote URL + Docs: https://docs.deno.com/runtime/manual/basics/import_maps", + )) .value_hint(ValueHint::FilePath) .help_heading(DEPENDENCY_MANAGEMENT_HEADING) } @@ -3565,11 +3595,11 @@ fn env_file_arg() -> Arg { .long("env-file") .alias("env") .value_name("FILE") - .help( - "Load environment variables from local file. - Only the first environment variable with a given key is used. - Existing process environment variables are not overwritten.", - ) + .help(cstr!( + "Load environment variables from local file + Only the first environment variable with a given key is used. + Existing process environment variables are not overwritten." + )) .value_hint(ValueHint::FilePath) .default_missing_value(".env") .require_equals(true) @@ -3585,7 +3615,7 @@ fn reload_arg() -> Arg { .long("reload") .value_name("CACHE_BLOCKLIST") .help( - color_print::cstr!("Reload source code cache (recompile TypeScript) + cstr!("Reload source code cache (recompile TypeScript) no value Reload everything jsr:@std/http/file-server,jsr:@std/assert/assert-equals Reloads specific modules npm: Reload all npm modules @@ -3683,9 +3713,9 @@ fn v8_flags_arg() -> Arg { .use_value_delimiter(true) .require_equals(true) .value_name("V8_FLAGS") - .help("To see a list of all available flags use --v8-flags=--help. - Flags can also be set via the DENO_V8_FLAGS environment variable. - Any flags set with this flag are appended after the DENO_V8_FLAGS environment variable") + .help( cstr!("To see a list of all available flags use --v8-flags=--help + Flags can also be set via the DENO_V8_FLAGS environment variable. + Any flags set with this flag are appended after the DENO_V8_FLAGS environment variable")) } fn seed_arg() -> Arg { @@ -3713,14 +3743,14 @@ fn hmr_arg(takes_files: bool) -> Arg { .use_value_delimiter(true) .require_equals(true) .help( - color_print::cstr!( + cstr!( "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."), ) .value_hint(ValueHint::AnyPath) } else { - arg.action(ArgAction::SetTrue).help(color_print::cstr!( + arg.action(ArgAction::SetTrue).help(cstr!( "Watch for file changes and restart process automatically. Only local files from entry point module graph are watched." )) @@ -3740,14 +3770,14 @@ fn watch_arg(takes_files: bool) -> Arg { .use_value_delimiter(true) .require_equals(true) .help( - color_print::cstr!( + cstr!( "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."), ) .value_hint(ValueHint::AnyPath) } else { - arg.action(ArgAction::SetTrue).help(color_print::cstr!( + arg.action(ArgAction::SetTrue).help(cstr!( "Watch for file changes and restart process automatically. Only local files from entry point module graph are watched." )) @@ -3789,10 +3819,7 @@ fn no_check_arg() -> Arg { .require_equals(true) .value_name("NO_CHECK_TYPE") .long("no-check") - .help( - "Skip type-checking. If the value of '--no-check=remote' is supplied, - diagnostic errors from remote modules will be ignored.", - ) + .help("Skip type-checking. If the value of \"remote\" is supplied, diagnostic errors from remote modules will be ignored") .help_heading(TYPE_CHECKING_HEADING) } @@ -3807,17 +3834,16 @@ fn check_arg(checks_local_by_default: bool) -> Arg { if checks_local_by_default { arg.help( - "Set type-checking behavior. This subcommand type-checks local modules by - default, so adding --check is redundant. - If the value of \"all\" is supplied, remote modules will be included. - Alternatively, the 'deno check' subcommand can be used.", - ) + cstr!("Set type-checking behavior. This subcommand type-checks local modules by default, so adding --check is redundant + If the value of \"all\" is supplied, remote modules will be included. + Alternatively, the 'deno check' subcommand can be used", + )) } else { - arg.help( - "Enable type-checking. This subcommand does not type-check by default. - If the value of \"all\" is supplied, remote modules will be included. - Alternatively, the 'deno check' subcommand can be used.", - ) + arg.help(cstr!( + "Enable type-checking. This subcommand does not type-check by default + If the value of \"all\" is supplied, remote modules will be included. + Alternatively, the 'deno check' subcommand can be used" + )) } } @@ -3854,7 +3880,7 @@ fn lock_write_arg() -> Arg { Arg::new("lock-write") .action(ArgAction::SetTrue) .long("lock-write") - .help("Force overwriting the lock file.") + .help("Force overwriting the lock file") .conflicts_with("no-lock") .hide(true) } @@ -3873,10 +3899,10 @@ fn config_arg() -> Arg { .short('c') .long("config") .value_name("FILE") - .help("Configure different aspects of deno including TypeScript, linting, and code formatting. - Typically the configuration file will be called `deno.json` or `deno.jsonc` and + .help(cstr!("Configure different aspects of deno including TypeScript, linting, and code formatting + Typically the configuration file will be called `deno.json` or `deno.jsonc` and automatically detected; in that case this flag is not necessary. - Docs: https://docs.deno.com/go/config") + Docs: https://docs.deno.com/go/config")) .value_hint(ValueHint::FilePath) } @@ -3884,7 +3910,7 @@ fn no_config_arg() -> Arg { Arg::new("no-config") .long("no-config") .action(ArgAction::SetTrue) - .help("Disable automatic loading of the configuration file.") + .help("Disable automatic loading of the configuration file") .conflicts_with("config") } @@ -3923,10 +3949,7 @@ fn vendor_arg() -> Arg { .value_parser(value_parser!(bool)) .default_missing_value("true") .require_equals(true) - .help( - "Enables or disables the use of a local vendor folder - for remote modules and node_modules folder for npm packages", - ) + .help("Toggles local vendor folder usage for remote modules and a node_modules folder for npm packages") .help_heading(DEPENDENCY_MANAGEMENT_HEADING) } @@ -3950,7 +3973,8 @@ fn allow_scripts_arg() -> Arg { .require_equals(true) .value_name("PACKAGE") .value_parser(parse_packages_allowed_scripts) - .help("Allow running npm lifecycle scripts for the given packages. Note: Scripts will only be executed when using a node_modules directory (`--node-modules-dir`)") + .help(cstr!("Allow running npm lifecycle scripts for the given packages + Note: Scripts will only be executed when using a node_modules directory (`--node-modules-dir`)")) } enum UnstableArgsConfig { @@ -3972,7 +3996,8 @@ impl Iterator for UnstableArgsIter { let arg = if self.idx == 0 { Arg::new("unstable") .long("unstable") - .help("Enable all unstable features and APIs. Instead of using this flag, consider enabling individual unstable features.\n To view the list of individual unstable feature flags, run this command again with --help=unstable.") + .help(cstr!("Enable all unstable features and APIs. Instead of using this flag, consider enabling individual unstable features + To view the list of individual unstable feature flags, run this command again with --help=unstable")) .action(ArgAction::SetTrue) .hide(matches!(self.cfg, UnstableArgsConfig::None)) } else if self.idx == 1 { @@ -4006,9 +4031,7 @@ impl Iterator for UnstableArgsIter { } else if self.idx == 3 { Arg::new("unstable-sloppy-imports") .long("unstable-sloppy-imports") - .help( - "Enable unstable resolving of specifiers by extension probing, .js to .ts, and directory probing.", - ) + .help("Enable unstable resolving of specifiers by extension probing, .js to .ts, and directory probing") .env("DENO_UNSTABLE_SLOPPY_IMPORTS") .value_parser(FalseyValueParser::new()) .action(ArgAction::SetTrue) @@ -4628,7 +4651,7 @@ fn repl_parse(flags: &mut Flags, matches: &mut ArgMatches) { fn run_parse( flags: &mut Flags, matches: &mut ArgMatches, - mut app: Command, + app: Command, bare: bool, ) -> clap::error::Result<()> { // todo(dsherret): remove this in Deno 2.0 @@ -4679,35 +4702,31 @@ fn run_parse( } runtime_args_parse(flags, matches, true, true); + ext_arg_parse(flags, matches); flags.code_cache_enabled = !matches.get_flag("no-code-cache"); - let mut script_arg = - matches.remove_many::("script_arg").ok_or_else(|| { - if bare { - app.override_usage("deno [OPTIONS] [COMMAND] [SCRIPT_ARG]...").error( - clap::error::ErrorKind::MissingRequiredArgument, - "[SCRIPT_ARG] may only be omitted with --v8-flags=--help, else to use the repl with arguments, please use the `deno repl` subcommand", - ) - } else { - app.find_subcommand_mut("run").unwrap().error( - clap::error::ErrorKind::MissingRequiredArgument, - "[SCRIPT_ARG] may only be omitted with --v8-flags=--help", - ) - } - })?; - - let script = script_arg.next().unwrap(); - flags.argv.extend(script_arg); - - ext_arg_parse(flags, matches); - temp_netlify_deno_1_hack(flags, &script); - - flags.subcommand = DenoSubcommand::Run(RunFlags { - script, - watch: watch_arg_parse_with_paths(matches), - bare, - }); + if let Some(mut script_arg) = matches.remove_many::("script_arg") { + let script = script_arg.next().unwrap(); + flags.argv.extend(script_arg); + temp_netlify_deno_1_hack(flags, &script); + flags.subcommand = DenoSubcommand::Run(RunFlags { + script, + watch: watch_arg_parse_with_paths(matches), + bare, + }); + } else if bare { + return Err(app.override_usage("deno [OPTIONS] [COMMAND] [SCRIPT_ARG]...").error( + clap::error::ErrorKind::MissingRequiredArgument, + "[SCRIPT_ARG] may only be omitted with --v8-flags=--help, else to use the repl with arguments, please use the `deno repl` subcommand", + )); + } else { + flags.subcommand = DenoSubcommand::Task(TaskFlags { + cwd: None, + task: None, + is_run: true, + }); + } Ok(()) } @@ -4781,6 +4800,7 @@ fn task_parse(flags: &mut Flags, matches: &mut ArgMatches) { let mut task_flags = TaskFlags { cwd: matches.remove_one::("cwd"), task: None, + is_run: false, }; if let Some((task, mut matches)) = matches.remove_subcommand() { @@ -5153,34 +5173,9 @@ fn runtime_args_parse( } fn inspect_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) { - let default = || "127.0.0.1:9229".parse::().unwrap(); - flags.inspect = if matches.contains_id("inspect") { - Some( - matches - .remove_one::("inspect") - .unwrap_or_else(default), - ) - } else { - None - }; - flags.inspect_brk = if matches.contains_id("inspect-brk") { - Some( - matches - .remove_one::("inspect-brk") - .unwrap_or_else(default), - ) - } else { - None - }; - flags.inspect_wait = if matches.contains_id("inspect-wait") { - Some( - matches - .remove_one::("inspect-wait") - .unwrap_or_else(default), - ) - } else { - None - }; + flags.inspect = matches.remove_one::("inspect"); + flags.inspect_brk = matches.remove_one::("inspect-brk"); + flags.inspect_wait = matches.remove_one::("inspect-wait"); } fn import_map_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) { @@ -5886,10 +5881,7 @@ mod tests { ); let r = flags_from_vec(svec!["deno", "run", "--v8-flags=--expose-gc"]); - assert!(r - .unwrap_err() - .to_string() - .contains("[SCRIPT_ARG] may only be omitted with --v8-flags=--help")); + assert!(r.is_ok()); } #[test] @@ -10341,6 +10333,7 @@ mod tests { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, task: Some("build".to_string()), + is_run: false, }), argv: svec!["hello", "world"], ..Flags::default() @@ -10354,6 +10347,7 @@ mod tests { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, task: Some("build".to_string()), + is_run: false, }), ..Flags::default() } @@ -10366,6 +10360,7 @@ mod tests { subcommand: DenoSubcommand::Task(TaskFlags { cwd: Some("foo".to_string()), task: Some("build".to_string()), + is_run: false, }), ..Flags::default() } @@ -10390,6 +10385,7 @@ mod tests { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, task: Some("build".to_string()), + is_run: false, }), argv: svec!["--", "hello", "world"], config_flag: ConfigFlag::Path("deno.json".to_owned()), @@ -10406,6 +10402,7 @@ mod tests { subcommand: DenoSubcommand::Task(TaskFlags { cwd: Some("foo".to_string()), task: Some("build".to_string()), + is_run: false, }), argv: svec!["--", "hello", "world"], ..Flags::default() @@ -10423,6 +10420,7 @@ mod tests { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, task: Some("build".to_string()), + is_run: false, }), argv: svec!["--"], ..Flags::default() @@ -10439,6 +10437,7 @@ mod tests { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, task: Some("build".to_string()), + is_run: false, }), argv: svec!["-1", "--test"], ..Flags::default() @@ -10455,6 +10454,7 @@ mod tests { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, task: Some("build".to_string()), + is_run: false, }), argv: svec!["--test"], ..Flags::default() @@ -10472,6 +10472,7 @@ mod tests { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, task: Some("build".to_string()), + is_run: false, }), log_level: Some(log::Level::Error), ..Flags::default() @@ -10488,6 +10489,7 @@ mod tests { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, task: None, + is_run: false, }), ..Flags::default() } @@ -10503,6 +10505,7 @@ mod tests { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, task: None, + is_run: false, }), config_flag: ConfigFlag::Path("deno.jsonc".to_string()), ..Flags::default() @@ -10519,6 +10522,7 @@ mod tests { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, task: None, + is_run: false, }), config_flag: ConfigFlag::Path("deno.jsonc".to_string()), ..Flags::default() @@ -11052,4 +11056,43 @@ mod tests { .to_string() .contains("Usage: deno [OPTIONS] [COMMAND] [SCRIPT_ARG]...")); } + + #[test] + fn equal_help_output() { + for command in clap_root().get_subcommands() { + if command.get_name() == "help" { + continue; + } + + let long_flag = if let DenoSubcommand::Help(help) = + flags_from_vec(svec!["deno", command.get_name(), "--help"]) + .unwrap() + .subcommand + { + help.help.to_string() + } else { + unreachable!() + }; + let short_flag = if let DenoSubcommand::Help(help) = + flags_from_vec(svec!["deno", command.get_name(), "-h"]) + .unwrap() + .subcommand + { + help.help.to_string() + } else { + unreachable!() + }; + let subcommand = if let DenoSubcommand::Help(help) = + flags_from_vec(svec!["deno", "help", command.get_name()]) + .unwrap() + .subcommand + { + help.help.to_string() + } else { + unreachable!() + }; + assert_eq!(long_flag, short_flag, "{} subcommand", command.get_name()); + assert_eq!(long_flag, subcommand, "{} subcommand", command.get_name()); + } + } } diff --git a/cli/main.rs b/cli/main.rs index 6a7575deec..290eee120d 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -218,9 +218,10 @@ async fn run_subcommand(flags: Arc) -> Result { let task_flags = TaskFlags { cwd: None, task: Some(run_flags.script.clone()), + is_run: true, }; new_flags.subcommand = DenoSubcommand::Task(task_flags.clone()); - let result = tools::task::execute_script(Arc::new(new_flags), task_flags.clone(), true).await; + let result = tools::task::execute_script(Arc::new(new_flags), task_flags.clone()).await; match result { Ok(v) => Ok(v), Err(_) => { @@ -240,7 +241,7 @@ async fn run_subcommand(flags: Arc) -> Result { tools::serve::serve(flags, serve_flags).await }), DenoSubcommand::Task(task_flags) => spawn_subcommand(async { - tools::task::execute_script(flags, task_flags, false).await + tools::task::execute_script(flags, task_flags).await }), DenoSubcommand::Test(test_flags) => { spawn_subcommand(async { @@ -290,7 +291,21 @@ async fn run_subcommand(flags: Arc) -> Result { tools::registry::publish(flags, publish_flags).await }), DenoSubcommand::Help(help_flags) => spawn_subcommand(async move { - display::write_to_stdout_ignore_sigpipe(help_flags.help.ansi().to_string().as_bytes()) + use std::io::Write; + + let mut stream = anstream::AutoStream::new(std::io::stdout(), if colors::use_color() { + anstream::ColorChoice::Auto + } else { + anstream::ColorChoice::Never + }); + + match stream.write_all(help_flags.help.ansi().to_string().as_bytes()) { + Ok(()) => Ok(()), + Err(e) => match e.kind() { + std::io::ErrorKind::BrokenPipe => Ok(()), + _ => Err(e), + }, + } }), }; diff --git a/cli/tools/task.rs b/cli/tools/task.rs index 0110d17414..23da5b4fb9 100644 --- a/cli/tools/task.rs +++ b/cli/tools/task.rs @@ -29,13 +29,24 @@ use std::sync::Arc; pub async fn execute_script( flags: Arc, task_flags: TaskFlags, - using_run: bool, ) -> Result { let factory = CliFactory::from_flags(flags); let cli_options = factory.cli_options()?; let start_dir = &cli_options.start_dir; if !start_dir.has_deno_or_pkg_json() { - bail!("deno task couldn't find deno.json(c). See https://docs.deno.com/go/config") + if task_flags.is_run { + bail!( + r#"deno run couldn't find deno.json(c). +If you meant to run a script, specify it, e.g., `deno run ./script.ts`. +To run a task, ensure the config file exists. +Examples: +- Script: `deno run ./script.ts` +- Task: `deno run dev` +See https://docs.deno.com/go/config"# + ) + } else { + bail!("deno task couldn't find deno.json(c). See https://docs.deno.com/go/config") + } } let force_use_pkg_json = std::env::var_os(crate::task_runner::USE_PKG_JSON_HIDDEN_ENV_VAR_NAME) @@ -142,7 +153,7 @@ pub async fn execute_script( } }, None => { - if using_run { + if task_flags.is_run { return Err(anyhow!("Task not found: {}", task_name)); } log::error!("Task not found: {}", task_name);