diff options
author | Leo Kettmeir <crowlkats@toaxl.com> | 2024-08-21 06:54:59 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-21 06:54:59 -0700 |
commit | 71393370832946a33e8a34d2e9cfaad8bf97c91b (patch) | |
tree | 4fbb1c8ad9e2c734986611c43063b09aa38c1569 /cli | |
parent | e920835417d10f3735645c910e513886cdda6a39 (diff) |
feat(flags): improve help output and make `deno run` list tasks (#25108)
- 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 <biwanczuk@gmail.com>
Diffstat (limited to 'cli')
-rw-r--r-- | cli/Cargo.toml | 1 | ||||
-rw-r--r-- | cli/args/flags.rs | 473 | ||||
-rw-r--r-- | cli/main.rs | 21 | ||||
-rw-r--r-- | cli/tools/task.rs | 17 |
4 files changed, 291 insertions, 221 deletions
diff --git a/cli/Cargo.toml b/cli/Cargo.toml index d0b871f49..087b52477 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 a273d8b37..de6358359 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<String>, pub task: Option<String>, + 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#"<y>Environment variables:</> <g>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!( <g>NPM_CONFIG_REGISTRY</> URL to use for the npm registry."# ); -static DENO_HELP: &str = color_print::cstr!( +static DENO_HELP: &str = cstr!( "Deno: <g>A modern JavaScript and TypeScript runtime</> <p(245)>Usage:</> <g>{usage}</> @@ -1249,6 +1251,19 @@ pub fn flags_from_vec(args: Vec<OsString>) -> clap::error::Result<Flags> { 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); } if let Some((subcommand, mut m)) = matches.remove_subcommand() { @@ -1283,15 +1298,6 @@ pub fn flags_from_vec(args: Vec<OsString>) -> clap::error::Result<Flags> { "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: <p(245)>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. + <p(245)>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/<inferred-name>)") + .help(cstr!("Output file <p(245)>(defaults to $PWD/<<inferred-name>>)</>")) .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. + <p(245)>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 <p(245)>[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 <p(245)>[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 <p(245)>[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 <p(245)>[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 <p(245)>[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 <p(245)>[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!("<y>Environment variables:</> + .after_help(cstr!("<y>Environment variables:</> <g>DENO_REPL_HISTORY</> Set REPL history file path. History file is disabled when the value is empty. - <p(245)>(defaults to $DENO_DIR/deno_history.txt)</>")) + <p(245)>[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. <p(245)>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. + <p(245)>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. + <p(245)>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#"<y>Permission options:</> -Docs: https://docs.deno.com/go/permissions - - <g>-A, --allow-all</> Allow all permissions. - <g>-R, --{allow,deny}-read[=<<PATH>...]</> Allow / deny file system read access. Optionally specify allowed / denied paths. - <p(245)>--allow-read | --allow-read="/etc,/var/log.txt" | --deny-read="/etc/hosts"</> - <g>-W, --{allow,write}-read[=<<PATH>...]</> Allow / deny file system write access. Optionally specify allowed / denied paths. - <p(245)>--allow-write | --allow-write="/etc,/var/log.txt" | --deny-write="/etc/hosts"</> - <g>-N, --{allow,deny}-net[=<<IP_OR_HOSTNAME>...]</> Allow / deny network access. Optionally specify allowed IP addresses and host names, with ports as necessary. - <p(245)>--allow-net | --allow-net="localhost:8080,deno.land" | --deny-net="deno.com"</> - <g>-E --{allow,deny}-env[=<<VARIABLE_NAME>...]</> Allow / deny access to environment variables. Optionally specify accessible / inacessible environment variables. - <p(245)>--allow-env | --allow-env="PORT,HOME,PATH" | --deny-env="ACCESS_TOKEN"</> - <g>-S --{allow,deny}-sys[=<<API_NAME>...]</> Allow / deny access to OS information. Optionally allow / deny specific APIs by function name. - <p(245)>--allow-sys | --allow-sys="systemMemoryInfo,osRelease" | --deny-sys="hostname"</> - <g>--{allow,deny}-run[=<<PROGRAM_NAME>...]</> Allow / deny running subprocesses. Optionally specify allowed / denied runnable program names. - <p(245)>--allow-run | --allow-run="whoami,ps" | --deny-run="cat"</> - <g>--{allow,deny}-ffi[=<<PATH>...]</> (Unstable) Allow / deny loading dynamic libraries. Optionally specify allowed / denied directories or files. - <p(245)>--allow-ffi | --allow-ffi="./libfoo.so" | --deny-ffi="./libfoo.so"</> - <g>--{allow,deny}-hrtime</> Allow / deny high-resolution time measurement. Note: this can enable timing attacks and fingerprinting. - <g>--no-prompt</> Always throw if required permission wasn't passed. Can also be set via the DENO_NO_PROMPT environment variable. + .after_help(cstr!(r#"<y>Permission options:</> +Docs: <c>https://docs.deno.com/go/permissions</> + + <g>-A, --allow-all</> Allow all permissions. + <g>--no-prompt</> Always throw if required permission wasn't passed. + <p(245)>Can also be set via the DENO_NO_PROMPT environment variable.</> + <g>-R, --allow-read[=<<PATH>...]</> Allow file system read access. Optionally specify allowed paths. + <p(245)>--allow-read | --allow-read="/etc,/var/log.txt"</> + <g>-W, --allow-write[=<<PATH>...]</> Allow file system write access. Optionally specify allowed paths. + <p(245)>--allow-write | --allow-write="/etc,/var/log.txt"</> + <g>-N, --allow-net[=<<IP_OR_HOSTNAME>...]</> Allow network access. Optionally specify allowed IP addresses and host names, with ports as necessary. + <p(245)>--allow-net | --allow-net="localhost:8080,deno.land"</> + <g>-E, --allow-env[=<<VARIABLE_NAME>...]</> Allow access to environment variables. Optionally specify accessible environment variables. + <p(245)>--allow-env | --allow-env="PORT,HOME,PATH"</> + <g>-S, --allow-sys[=<<API_NAME>...]</> Allow access to OS information. Optionally allow specific APIs by function name. + <p(245)>--allow-sys | --allow-sys="systemMemoryInfo,osRelease"</> + <g>--allow-run[=<<PROGRAM_NAME>...]</> Allow running subprocesses. Optionally specify allowed runnable program names. + <p(245)>--allow-run | --allow-run="whoami,ps"</> + <g>--allow-ffi[=<<PATH>...]</> (Unstable) Allow loading dynamic libraries. Optionally specify allowed directories or files. + <p(245)>--allow-ffi | --allow-ffi="./libfoo.so"</> + <g>--allow-hrtime</> Allow high-resolution time measurement. Note: this can enable timing attacks and fingerprinting. + <p(245)>--allow-hrtime</> + <g> --deny-read[=<<PATH>...]</> Deny file system read access. Optionally specify denied paths. + <p(245)>--deny-read | --deny-read="/etc,/var/log.txt"</> + <g> --deny-write[=<<PATH>...]</> Deny file system write access. Optionally specify denied paths. + <p(245)>--deny-write | --deny-write="/etc,/var/log.txt"</> + <g> --deny-net[=<<IP_OR_HOSTNAME>...]</> Deny network access. Optionally specify defined IP addresses and host names, with ports as necessary. + <p(245)>--deny-net | --deny-net="localhost:8080,deno.land"</> + <g> --deny-env[=<<VARIABLE_NAME>...]</> Deny access to environment variables. Optionally specify inacessible environment variables. + <p(245)>--deny-env | --deny-env="PORT,HOME,PATH"</> + <g>-S, --deny-sys[=<<API_NAME>...]</> Deny access to OS information. Optionally deny specific APIs by function name. + <p(245)>--deny-sys | --deny-sys="systemMemoryInfo,osRelease"</> + <g>--deny-run[=<<PROGRAM_NAME>...]</> Deny running subprocesses. Optionally specify denied runnable program names. + <p(245)>--deny-run | --deny-run="whoami,ps"</> + <g>--deny-ffi[=<<PATH>...]</> (Unstable) Deny loading dynamic libraries. Optionally specify denied directories or files. + <p(245)>--deny-ffi | --deny-ffi="./libfoo.so"</> + <g>--deny-hrtime</> Deny high-resolution time measurement. + <p(245)>--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 <p(245)>[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 + <p(245)>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 + <p(245)>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) <p(245)>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 + <p(245)>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. <p(245)>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. <p(245)>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. <p(245)>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. <p(245)>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 + <p(245)>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 + <p(245)>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 + <p(245)>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 + <p(245)>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 + <p(245)>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::<String>("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::<String>("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::<String>("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::<SocketAddr>().unwrap(); - flags.inspect = if matches.contains_id("inspect") { - Some( - matches - .remove_one::<SocketAddr>("inspect") - .unwrap_or_else(default), - ) - } else { - None - }; - flags.inspect_brk = if matches.contains_id("inspect-brk") { - Some( - matches - .remove_one::<SocketAddr>("inspect-brk") - .unwrap_or_else(default), - ) - } else { - None - }; - flags.inspect_wait = if matches.contains_id("inspect-wait") { - Some( - matches - .remove_one::<SocketAddr>("inspect-wait") - .unwrap_or_else(default), - ) - } else { - None - }; + flags.inspect = matches.remove_one::<SocketAddr>("inspect"); + flags.inspect_brk = matches.remove_one::<SocketAddr>("inspect-brk"); + flags.inspect_wait = matches.remove_one::<SocketAddr>("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 6a7575dee..290eee120 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -218,9 +218,10 @@ async fn run_subcommand(flags: Arc<Flags>) -> Result<i32, AnyError> { 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<Flags>) -> Result<i32, AnyError> { 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<Flags>) -> Result<i32, AnyError> { 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 0110d1741..23da5b4fb 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<Flags>, task_flags: TaskFlags, - using_run: bool, ) -> Result<i32, AnyError> { 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); |