// Copyright 2018-2026 the Deno authors. MIT license.

use std::borrow::Cow;
use std::collections::HashSet;
use std::env;
use std::ffi::OsString;
use std::net::IpAddr;
use std::net::SocketAddr;
use std::net::ToSocketAddrs;
use std::num::NonZeroU8;
use std::num::NonZeroU32;
use std::num::NonZeroUsize;
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use std::sync::LazyLock;

use clap::Arg;
use clap::ArgAction;
use clap::ArgMatches;
use clap::ColorChoice;
use clap::Command;
use clap::ValueHint;
use clap::builder::FalseyValueParser;
use clap::builder::styling::AnsiColor;
use clap::error::ErrorKind;
use clap::value_parser;
use clap_complete::CompletionCandidate;
use clap_complete::engine::SubcommandCandidates;
use clap_complete::env::EnvCompleter;
use clap_complete::env::Shells;
use color_print::cstr;
use deno_bundle_runtime::BundleFormat;
use deno_bundle_runtime::BundlePlatform;
use deno_bundle_runtime::PackageHandling;
use deno_bundle_runtime::SourceMapType;
pub use deno_cli_parser::flags::*;
use deno_config::deno_json::NewestDependencyDate;
use deno_config::deno_json::NodeModulesDirMode;
use deno_config::deno_json::NodeModulesLinkerMode;
use deno_config::glob::FilePatterns;
use deno_config::glob::PathOrPatternSet;
use deno_core::anyhow::Context;
use deno_core::anyhow::bail;
use deno_core::error::AnyError;
use deno_core::serde_json;
use deno_core::url::Url;
use deno_graph::GraphKind;
use deno_lib::args::CaData;
use deno_lib::version::DENO_VERSION_INFO;
use deno_npm::NpmSystemInfo;
use deno_npm_installer::PackagesAllowedScripts;
use deno_path_util::normalize_path;
use deno_path_util::resolve_url_or_path;
use deno_path_util::url_to_file_path;
use deno_runtime::UnstableFeatureKind;
pub use deno_runtime::deno_inspector_server::InspectPublishUid;
use deno_runtime::deno_permissions::SysDescriptor;
use deno_semver::jsr::JsrDepPackageReq;
use deno_semver::package::PackageKind;
use deno_telemetry::OtelConfig;
use deno_telemetry::OtelConsoleConfig;
use deno_telemetry::OtelPropagators;
use log::Level;
use log::debug;
use node_shim::parse_node_options_env_var;
use serde::Serialize;

use super::flags_net;
use crate::util::env::resolve_cwd;
use crate::util::fs::canonicalize_path;

// ---------------------------------------------------------------------------
// CLI-only value parsers and helpers. These stay in the CLI because they
// depend on clap / deno_graph / deno_telemetry / cli build env, which the
// parser crate intentionally does not.
// ---------------------------------------------------------------------------

fn minutes_duration_or_date_parser(
  s: &str,
) -> Result<NewestDependencyDate, clap::Error> {
  deno_config::parse_minutes_duration_or_date(&sys_traits::impls::RealSys, s)
    .map_err(|e| clap::Error::raw(clap::error::ErrorKind::InvalidValue, e))
}

fn parse_packages_scripts_arg(
  s: &str,
  flag_name: &str,
) -> Result<String, AnyError> {
  if !s.starts_with("npm:") {
    bail!(
      "Invalid package for {}: '{}'. An 'npm:' specifier is required",
      flag_name,
      s,
    );
  } else {
    Ok(s.into())
  }
}

fn parse_packages_allowed_scripts(s: &str) -> Result<String, AnyError> {
  parse_packages_scripts_arg(s, "--allow-scripts")
}

fn parse_packages_ignored_scripts(s: &str) -> Result<String, AnyError> {
  if s.is_empty() {
    bail!("Invalid package for --ignore-scripts: package cannot be empty");
  }
  Ok(s.into())
}

/// Parse --inspect-publish-uid from a comma-separated string like "stderr,http".
pub fn parse_inspect_publish_uid(s: &str) -> Result<InspectPublishUid, String> {
  let mut result = InspectPublishUid {
    console: false,
    http: false,
  };
  for part in s.split(',') {
    let part = part.trim();
    match part {
      "stderr" => result.console = true,
      "http" => result.http = true,
      "" => {}
      _ => {
        return Err(format!(
          "--inspect-publish-uid destination can be stderr or http, got '{}'",
          part
        ));
      }
    }
  }
  Ok(result)
}

fn join_paths(allowlist: &[String], d: &str) -> String {
  allowlist
    .iter()
    .map(|path| path.to_string())
    .collect::<Vec<String>>()
    .join(d)
}

pub trait FileFlagsExt {
  fn as_file_patterns(&self, base: &Path) -> Result<FilePatterns, AnyError>;
}

impl FileFlagsExt for FileFlags {
  fn as_file_patterns(&self, base: &Path) -> Result<FilePatterns, AnyError> {
    Ok(FilePatterns {
      include: if self.include.is_empty() {
        None
      } else {
        Some(PathOrPatternSet::from_include_relative_path_or_patterns(
          base,
          &self.include,
        )?)
      },
      exclude: PathOrPatternSet::from_exclude_relative_path_or_patterns(
        base,
        &self.ignore,
      )?,
      base: base.to_path_buf(),
    })
  }
}

pub trait CompileFlagsExt {
  fn resolve_target(&self) -> String;
}

impl CompileFlagsExt for CompileFlags {
  fn resolve_target(&self) -> String {
    self
      .target
      .clone()
      .unwrap_or_else(|| env!("TARGET").to_string())
  }
}

fn npm_system_info_from_env() -> NpmSystemInfo {
  let arch = std::env::var_os("DENO_INSTALL_ARCH");
  if let Some(var) = arch.as_ref().and_then(|s| s.to_str()) {
    NpmSystemInfo::from_rust(std::env::consts::OS, var)
  } else {
    NpmSystemInfo::default()
  }
}

pub trait DenoSubcommandExt {
  fn npm_system_info(&self) -> NpmSystemInfo;
}

impl DenoSubcommandExt for DenoSubcommand {
  fn npm_system_info(&self) -> NpmSystemInfo {
    match self {
      DenoSubcommand::Compile(CompileFlags {
        target: Some(target),
        ..
      })
      | DenoSubcommand::Desktop(DesktopFlags {
        target: Some(target),
        ..
      }) => {
        // the values of NpmSystemInfo align with the possible values for the
        // `arch` and `platform` fields of Node.js' `process` global:
        // https://nodejs.org/api/process.html
        match target.as_str() {
          "aarch64-apple-darwin" => NpmSystemInfo {
            os: "darwin".into(),
            cpu: "arm64".into(),
          },
          "aarch64-unknown-linux-gnu" => NpmSystemInfo {
            os: "linux".into(),
            cpu: "arm64".into(),
          },
          "x86_64-apple-darwin" => NpmSystemInfo {
            os: "darwin".into(),
            cpu: "x64".into(),
          },
          "x86_64-unknown-linux-gnu" => NpmSystemInfo {
            os: "linux".into(),
            cpu: "x64".into(),
          },
          "x86_64-pc-windows-msvc" => NpmSystemInfo {
            os: "win32".into(),
            cpu: "x64".into(),
          },
          value => {
            log::warn!(
              concat!(
                "Not implemented npm system info for target '{}'. Using current ",
                "system default. This may impact architecture specific dependencies."
              ),
              value,
            );
            NpmSystemInfo::default()
          }
        }
      }
      DenoSubcommand::Install(InstallFlags::Local(
        _,
        NpmInstallTargetFlags { os, arch },
      )) => {
        let default = npm_system_info_from_env();
        NpmSystemInfo {
          os: os.as_deref().unwrap_or(&default.os).into(),
          cpu: arch.as_deref().unwrap_or(&default.cpu).into(),
        }
      }
      _ => npm_system_info_from_env(),
    }
  }
}

pub trait TypeCheckModeExt {
  fn as_graph_kind(&self) -> GraphKind;
}

impl TypeCheckModeExt for TypeCheckMode {
  /// Gets the corresponding module `GraphKind` that should be created
  /// for the current `TypeCheckMode`.
  fn as_graph_kind(&self) -> GraphKind {
    match self.is_true() {
      true => GraphKind::All,
      false => GraphKind::CodeOnly,
    }
  }
}

pub trait FlagsExt {
  fn to_permission_args(&self) -> Vec<String>;
  fn no_legacy_abort(&self) -> bool;
  fn otel_config(&self) -> OtelConfig;
  fn config_path_args(&self, current_dir: &Path) -> Option<Vec<PathBuf>>;
  fn allow_all(&mut self);
  fn resolve_watch_exclude_set(&self) -> Result<PathOrPatternSet, AnyError>;
}

impl FlagsExt for Flags {
  /// Return list of permission arguments that are equivalent
  /// to the ones used to create `self`.
  fn to_permission_args(&self) -> Vec<String> {
    let mut args = vec![];

    if self.permissions.allow_all {
      args.push("--allow-all".to_string());
      return args;
    }

    match &self.permissions.allow_read {
      Some(read_allowlist) if read_allowlist.is_empty() => {
        args.push("--allow-read".to_string());
      }
      Some(read_allowlist) => {
        let s = format!("--allow-read={}", join_paths(read_allowlist, ","));
        args.push(s);
      }
      _ => {}
    }

    match &self.permissions.deny_read {
      Some(read_denylist) if read_denylist.is_empty() => {
        args.push("--deny-read".to_string());
      }
      Some(read_denylist) => {
        let s = format!("--deny-read={}", join_paths(read_denylist, ","));
        args.push(s);
      }
      _ => {}
    }

    match &self.permissions.allow_write {
      Some(write_allowlist) if write_allowlist.is_empty() => {
        args.push("--allow-write".to_string());
      }
      Some(write_allowlist) => {
        let s = format!("--allow-write={}", join_paths(write_allowlist, ","));
        args.push(s);
      }
      _ => {}
    }

    match &self.permissions.deny_write {
      Some(write_denylist) if write_denylist.is_empty() => {
        args.push("--deny-write".to_string());
      }
      Some(write_denylist) => {
        let s = format!("--deny-write={}", join_paths(write_denylist, ","));
        args.push(s);
      }
      _ => {}
    }

    match &self.permissions.allow_net {
      Some(net_allowlist) if net_allowlist.is_empty() => {
        args.push("--allow-net".to_string());
      }
      Some(net_allowlist) => {
        let s = format!("--allow-net={}", net_allowlist.join(","));
        args.push(s);
      }
      _ => {}
    }

    match &self.permissions.deny_net {
      Some(net_denylist) if net_denylist.is_empty() => {
        args.push("--deny-net".to_string());
      }
      Some(net_denylist) => {
        let s = format!("--deny-net={}", net_denylist.join(","));
        args.push(s);
      }
      _ => {}
    }

    match &self.unsafely_ignore_certificate_errors {
      Some(ic_allowlist) if ic_allowlist.is_empty() => {
        args.push("--unsafely-ignore-certificate-errors".to_string());
      }
      Some(ic_allowlist) => {
        let s = format!(
          "--unsafely-ignore-certificate-errors={}",
          ic_allowlist.join(",")
        );
        args.push(s);
      }
      _ => {}
    }

    match &self.permissions.allow_env {
      Some(env_allowlist) if env_allowlist.is_empty() => {
        args.push("--allow-env".to_string());
      }
      Some(env_allowlist) => {
        let s = format!("--allow-env={}", env_allowlist.join(","));
        args.push(s);
      }
      _ => {}
    }

    match &self.permissions.deny_env {
      Some(env_denylist) if env_denylist.is_empty() => {
        args.push("--deny-env".to_string());
      }
      Some(env_denylist) => {
        let s = format!("--deny-env={}", env_denylist.join(","));
        args.push(s);
      }
      _ => {}
    }

    match &self.permissions.ignore_env {
      Some(ignorelist) if ignorelist.is_empty() => {
        args.push("--ignore-env".to_string());
      }
      Some(ignorelist) => {
        let s = format!("--ignore-env={}", ignorelist.join(","));
        args.push(s);
      }
      _ => {}
    }

    match &self.permissions.ignore_read {
      Some(ignorelist) if ignorelist.is_empty() => {
        args.push("--ignore-read".to_string());
      }
      Some(ignorelist) => {
        let s = format!("--ignore-read={}", ignorelist.join(","));
        args.push(s);
      }
      _ => {}
    }

    match &self.permissions.allow_run {
      Some(run_allowlist) if run_allowlist.is_empty() => {
        args.push("--allow-run".to_string());
      }
      Some(run_allowlist) => {
        let s = format!("--allow-run={}", run_allowlist.join(","));
        args.push(s);
      }
      _ => {}
    }

    match &self.permissions.deny_run {
      Some(run_denylist) if run_denylist.is_empty() => {
        args.push("--deny-run".to_string());
      }
      Some(run_denylist) => {
        let s = format!("--deny-run={}", run_denylist.join(","));
        args.push(s);
      }
      _ => {}
    }

    match &self.permissions.allow_sys {
      Some(sys_allowlist) if sys_allowlist.is_empty() => {
        args.push("--allow-sys".to_string());
      }
      Some(sys_allowlist) => {
        let s = format!("--allow-sys={}", sys_allowlist.join(","));
        args.push(s)
      }
      _ => {}
    }

    match &self.permissions.deny_sys {
      Some(sys_denylist) if sys_denylist.is_empty() => {
        args.push("--deny-sys".to_string());
      }
      Some(sys_denylist) => {
        let s = format!("--deny-sys={}", sys_denylist.join(","));
        args.push(s)
      }
      _ => {}
    }

    match &self.permissions.allow_ffi {
      Some(ffi_allowlist) if ffi_allowlist.is_empty() => {
        args.push("--allow-ffi".to_string());
      }
      Some(ffi_allowlist) => {
        let s = format!("--allow-ffi={}", join_paths(ffi_allowlist, ","));
        args.push(s);
      }
      _ => {}
    }

    match &self.permissions.deny_ffi {
      Some(ffi_denylist) if ffi_denylist.is_empty() => {
        args.push("--deny-ffi".to_string());
      }
      Some(ffi_denylist) => {
        let s = format!("--deny-ffi={}", join_paths(ffi_denylist, ","));
        args.push(s);
      }
      _ => {}
    }

    match &self.permissions.allow_import {
      Some(allowlist) if allowlist.is_empty() => {
        args.push("--allow-import".to_string());
      }
      Some(allowlist) => {
        let s = format!("--allow-import={}", allowlist.join(","));
        args.push(s);
      }
      _ => {}
    }

    match &self.permissions.deny_import {
      Some(denylist) if denylist.is_empty() => {
        args.push("--deny-import".to_string());
      }
      Some(denylist) => {
        let s = format!("--deny-import={}", denylist.join(","));
        args.push(s);
      }
      _ => {}
    }

    args
  }

  fn no_legacy_abort(&self) -> bool {
    self
      .unstable_config
      .features
      .contains(&String::from("no-legacy-abort"))
  }

  fn otel_config(&self) -> OtelConfig {
    let otel_var = |name| match std::env::var(name) {
      Ok(s) if s.eq_ignore_ascii_case("true") => Some(true),
      Ok(s) if s.eq_ignore_ascii_case("false") => Some(false),
      Ok(_) => {
        log::warn!(
          "'{name}' env var value not recognized, only 'true' and 'false' are accepted"
        );
        None
      }
      Err(_) => None,
    };

    let disabled = otel_var("OTEL_SDK_DISABLED").unwrap_or(false);
    let default = !disabled && otel_var("OTEL_DENO").unwrap_or(false);

    let propagators = if default {
      if let Ok(propagators) = std::env::var("OTEL_PROPAGATORS") {
        propagators
          .split(',')
          .filter_map(|p| match p.trim() {
            "tracecontext" => Some(OtelPropagators::TraceContext),
            "baggage" => Some(OtelPropagators::Baggage),
            _ => None,
          })
          .collect()
      } else {
        HashSet::from([OtelPropagators::TraceContext, OtelPropagators::Baggage])
      }
    } else {
      HashSet::default()
    };

    OtelConfig {
      tracing_enabled: !disabled
        && otel_var("OTEL_DENO_TRACING").unwrap_or(default),
      metrics_enabled: !disabled
        && otel_var("OTEL_DENO_METRICS").unwrap_or(default),
      propagators,
      console: match std::env::var("OTEL_DENO_CONSOLE").as_deref() {
        Ok(_) if disabled => OtelConsoleConfig::Ignore,
        Ok("ignore") => OtelConsoleConfig::Ignore,
        Ok("capture") => OtelConsoleConfig::Capture,
        Ok("replace") => OtelConsoleConfig::Replace,
        res => {
          if res.is_ok() {
            log::warn!("'OTEL_DENO_CONSOLE' env var value not recognized, only 'ignore', 'capture', or 'replace' are accepted");
          }
          if default {
            OtelConsoleConfig::Capture
          } else {
            OtelConsoleConfig::Ignore
          }
        }
      },
      deterministic_prefix: std::env::var("DENO_UNSTABLE_OTEL_DETERMINISTIC")
        .as_deref()
        .map(u8::from_str)
        .map(|x| match x {
          Ok(x) => Some(x),
          Err(_) => {
            log::warn!("'DENO_UNSTABLE_OTEL_DETERMINISTIC' env var value not recognized, only integers are accepted");
            None
          }
        })
        .ok()
        .flatten(),
    }
  }

  /// Extract the paths the config file should be discovered from.
  ///
  /// Returns `None` if the config file should not be auto-discovered.
  fn config_path_args(&self, current_dir: &Path) -> Option<Vec<PathBuf>> {
    fn resolve_multiple_files(
      files_or_dirs: &[String],
      current_dir: &Path,
    ) -> Vec<PathBuf> {
      let mut seen = HashSet::with_capacity(files_or_dirs.len());
      let result = files_or_dirs
        .iter()
        .filter_map(|p| {
          let path = normalize_path(Cow::Owned(current_dir.join(p)));
          if seen.insert(path.clone()) {
            Some(path.into_owned())
          } else {
            None
          }
        })
        .collect::<Vec<_>>();
      if result.is_empty() {
        vec![current_dir.to_path_buf()]
      } else {
        result
      }
    }

    fn resolve_single_folder_path(
      arg: &str,
      current_dir: &Path,
      maybe_resolve_directory: impl FnOnce(PathBuf) -> Option<PathBuf>,
    ) -> Option<PathBuf> {
      if let Ok(module_specifier) = resolve_url_or_path(arg, current_dir) {
        if module_specifier.scheme() == "file"
          || module_specifier.scheme() == "npm"
          || module_specifier.scheme() == "jsr"
        {
          if let Ok(p) = url_to_file_path(&module_specifier) {
            maybe_resolve_directory(p)
          } else {
            Some(current_dir.to_path_buf())
          }
        } else {
          // When the entrypoint is a remote script (e.g. https:), then we
          // don't auto discover the config file. Package entrypoints (npm:,
          // jsr:) resolve within the project context, so they do.
          None
        }
      } else {
        Some(current_dir.to_path_buf())
      }
    }

    use DenoSubcommand::*;
    match &self.subcommand {
      Fmt(FmtFlags { files, .. }) => {
        Some(resolve_multiple_files(&files.include, current_dir))
      }
      Lint(LintFlags { files, .. }) => {
        Some(resolve_multiple_files(&files.include, current_dir))
      }
      Run(RunFlags { script, .. })
      | Compile(CompileFlags {
        source_file: script,
        ..
      }) => resolve_single_folder_path(script, current_dir, |mut p| {
        if p.pop() { Some(p) } else { None }
      })
      .map(|p| vec![p]),
      Desktop(DesktopFlags {
        source_file: script,
        ..
      }) => resolve_single_folder_path(script, current_dir, |p| {
        // The desktop entrypoint is commonly a directory (e.g. `deno desktop
        // .` for framework detection), in which case the config lives in that
        // directory itself — don't pop up to its parent. Only pop when the
        // entrypoint is a file.
        if p.is_dir() {
          Some(p)
        } else {
          p.parent().map(|parent| parent.to_path_buf())
        }
      })
      .map(|p| vec![p]),
      Task(TaskFlags {
        cwd: Some(path), ..
      }) => {
        // todo(dsherret): Why is this canonicalized? Document why.
        // attempt to resolve the config file from the task subcommand's
        // `--cwd` when specified
        match canonicalize_path(Path::new(path)) {
          Ok(path) => Some(vec![path]),
          Err(_) => Some(vec![current_dir.to_path_buf()]),
        }
      }
      Cache(CacheFlags { files, .. })
      | Install(InstallFlags::Local(
        InstallFlagsLocal::Entrypoints(InstallEntrypointsFlags {
          entrypoints: files,
          ..
        }),
        _,
      )) => Some(vec![
        files
          .iter()
          .filter_map(|file| {
            resolve_single_folder_path(file, current_dir, |mut p| {
              if p.is_dir() {
                return Some(p);
              }
              if p.pop() { Some(p) } else { None }
            })
          })
          .next()
          .unwrap_or_else(|| current_dir.to_path_buf()),
      ]),
      _ => Some(vec![current_dir.to_path_buf()]),
    }
  }

  #[inline(always)]
  fn allow_all(&mut self) {
    self.permissions.allow_all = true;
    self.permissions.allow_read = None;
    self.permissions.allow_env = None;
    self.permissions.allow_net = None;
    self.permissions.allow_run = None;
    self.permissions.allow_write = None;
    self.permissions.allow_sys = None;
    self.permissions.allow_ffi = None;
    self.permissions.allow_import = None;
  }

  fn resolve_watch_exclude_set(&self) -> Result<PathOrPatternSet, AnyError> {
    match &self.watch {
      Some(WatchFlagsWithPaths {
        exclude: excluded_paths,
        ..
      }) => {
        let cwd = resolve_cwd(self.initial_cwd.as_deref())?;
        PathOrPatternSet::from_exclude_relative_path_or_patterns(
          &cwd,
          excluded_paths,
        )
        .context("Failed resolving watch exclude patterns.")
      }
      _ => Ok(PathOrPatternSet::default()),
    }
  }
}

#[derive(Serialize)]
struct EnvVar {
  name: &'static str,
  description: &'static str,
  example: Option<&'static str>,
}

static ENV_VARS: &[EnvVar] = &[
  EnvVar {
    name: "DENO_AUTH_TOKENS",
    description: "A semi-colon separated list of bearer tokens and hostnames\nto use when fetching remote modules from private repositories",
    example: Some(r#"(e.g. "abcde12345@deno.land;54321edcba@github.com")"#),
  },
  EnvVar {
    name: "DENO_CACHE_DB_MODE",
    description: "Controls whether Web cache should use disk based or in-memory database.",
    example: None,
  },
  EnvVar {
    name: "DENO_CERT",
    description: "Load certificate authorities from PEM encoded file.",
    example: None,
  },
  EnvVar {
    name: "DENO_COMPAT",
    description: "Enable Node.js compatibility mode - extensionless imports, built-in\nNode.js modules, CommonJS detection and more.",
    example: None,
  },
  EnvVar {
    name: "DENO_CONDITIONS",
    description: "Comma-separated list of custom conditions to resolve npm package\nexports and imports with. Equivalent to using the --conditions flag.",
    example: None,
  },
  EnvVar {
    name: "DENO_COVERAGE_DIR",
    description: "Set the directory for collecting code coverage profiles.\nEquivalent to using the --coverage flag.",
    example: None,
  },
  EnvVar {
    name: "DENO_DIR",
    description: "Set the cache directory",
    example: None,
  },
  EnvVar {
    name: "DENO_INSTALL_ROOT",
    description: "Set deno install's output directory",
    example: Some("(defaults to $HOME/.deno/bin)"),
  },
  EnvVar {
    name: "DENO_JOBS",
    description: "Number of parallel workers used for the --parallel flag with the test\nsubcommand. Defaults to the number of available CPUs.",
    example: None,
  },
  EnvVar {
    name: "DENO_KV_DB_MODE",
    description: "Controls whether Deno.openKv() API should use disk based or in-memory\ndatabase.",
    example: None,
  },
  EnvVar {
    name: "DENO_KV_DEFAULT_PATH",
    description: "Set the default path for Deno.openKv() when no path is provided.",
    example: None,
  },
  EnvVar {
    name: "DENO_KV_PATH_PREFIX",
    description: "Set a prefix to be added to all Deno.openKv() paths.",
    example: None,
  },
  EnvVar {
    name: "DENO_KV_REQUIRES_DISTRIBUTED_DATABASE",
    description: "Require Deno.openKv() to resolve to a distributed (remote) database.\nWhen set to \"error\", Deno.openKv() is always exposed and rejects with a\nclear message unless the resolved path is remote. When set to \"warn\", a\nlocal/in-memory fallback is allowed but logs a warning. Used by Deno\nDeploy to surface a clear error when no KV database is attached.",
    example: None,
  },
  EnvVar {
    name: "DENO_EMIT_CACHE_MODE",
    description: "Control if the transpiled sources should be cached.",
    example: None,
  },
  EnvVar {
    name: "DENO_NO_PACKAGE_JSON",
    description: "Disables auto-resolution of package.json.",
    example: None,
  },
  EnvVar {
    name: "DENO_NO_PROMPT",
    description: "Set to disable permission prompts on access\n(alternative to passing --no-prompt on invocation).",
    example: None,
  },
  EnvVar {
    name: "DENO_NO_UPDATE_CHECK",
    description: "Set to disable checking if a newer Deno version is available",
    example: None,
  },
  EnvVar {
    name: "DENO_PATCH_REACT_CVE",
    description: "Enable load-time source patches mitigating known React Server\nComponents CVEs (CVE-2025-55182, CVE-2025-55184).",
    example: None,
  },
  EnvVar {
    name: "DENO_SERVE_ADDRESS",
    description: "Override address for Deno.serve",
    example: Some(
      r#"("tcp:0.0.0.0:8080", "unix:/tmp/deno.sock", or "vsock:1234:5678")"#,
    ),
  },
  EnvVar {
    name: "DENO_SERVE_AUTOMATIC_COMPRESSION",
    description: "Set to 1 or true to enable automatic response body compression in Deno.serve by default.",
    example: None,
  },
  EnvVar {
    name: "DENO_AUTO_SERVE",
    description: "If the entrypoint contains export default { fetch }, `deno run`\nbehaves like `deno serve`.",
    example: None,
  },
  EnvVar {
    name: "DENO_TLS_CA_STORE",
    description: cstr!(
      "Comma-separated list of order dependent certificate stores.\nPossible values: \"system\", \"mozilla\" <p(245)>(defaults to \"mozilla\")</>"
    ),
    example: None,
  },
  EnvVar {
    name: "DENO_TRACE_PERMISSIONS",
    description: "Environmental variable to enable stack traces in permission prompts.",
    example: None,
  },
  EnvVar {
    name: "DENO_USE_CGROUPS",
    description: "Use cgroups to determine V8 memory limit.",
    example: None,
  },
  EnvVar {
    name: "DENO_V8_FLAGS",
    description: "Set V8 command line options. Equivalent to using the --v8-flags flag;\nflags passed via --v8-flags are appended after these.",
    example: None,
  },
  EnvVar {
    name: "FORCE_COLOR",
    description: "Set force color output even if stdout isn't a tty.",
    example: None,
  },
  EnvVar {
    name: "HTTP_PROXY",
    description: "Proxy address for HTTP requests.",
    example: Some("(module downloads, fetch)"),
  },
  EnvVar {
    name: "HTTPS_PROXY",
    description: "Proxy address for HTTPS requests.",
    example: Some("(module downloads, fetch)"),
  },
  EnvVar {
    name: "NO_COLOR",
    description: "Set to disable color.",
    example: None,
  },
  EnvVar {
    name: "NO_PROXY",
    description: "Comma-separated list of hosts which do not use a proxy.",
    example: Some("(module downloads, fetch)"),
  },
  EnvVar {
    name: "NODE_USE_ENV_PROXY",
    description: "If set to 1, node:http and node:https honor HTTP_PROXY,\nHTTPS_PROXY, and NO_PROXY from the environment.",
    example: None,
  },
  EnvVar {
    name: "NPM_CONFIG_REGISTRY",
    description: "URL to use for the npm registry.",
    example: None,
  },
  EnvVar {
    name: "SSLKEYLOGFILE",
    description: "Write TLS session keys to the specified file in NSS Key Log format\nfor debugging encrypted traffic with tools like Wireshark.",
    example: None,
  },
  EnvVar {
    name: "DENO_TRUST_PROXY_HEADERS",
    description: "If specified, removes X-deno-client-address header when serving HTTP.",
    example: None,
  },
  EnvVar {
    name: "DENO_USR2_MEMORY_TRIM",
    description: "If specified, listen for SIGUSR2 signal to try and free memory (Linux only).",
    example: None,
  },
];

static ENV_VARIABLES_HELP: LazyLock<String> = LazyLock::new(|| {
  let mut out = cstr!(
    r#"<y>Environment variables:</>
<y>Docs:</> <c>https://docs.deno.com/go/env-vars</>

"#
  )
  .to_string();

  let longest = ENV_VARS.iter().map(|var| var.name.len()).max().unwrap() + 1;

  out.push_str(
    &ENV_VARS
      .iter()
      .map(|var| {
        let mut output = color_print::cformat!(
          "  <g>{}</>{}{}",
          var.name,
          " ".repeat(longest - var.name.len()),
          var
            .description
            .replace("\n", &format!("\n  {}", " ".repeat(longest)))
        );
        if let Some(example) = var.example {
          output.push_str(&color_print::cformat!(
            "\n  {}<p(245)>{}</>",
            " ".repeat(longest + 1),
            example
          ));
        }
        output
      })
      .collect::<Vec<_>>()
      .join("\n"),
  );

  out
});

static DENO_HELP: &str = cstr!(
  "Deno: <g>A modern JavaScript and TypeScript runtime</>

<p(245)>Usage:</> <g>{usage}</>

<y>Commands:</>
  <y>Execution:</>
    <g>run</>          Run a JavaScript or TypeScript program, or a task
                  <p(245)>deno run main.ts  |  deno run --allow-net=google.com main.ts  |  deno main.ts</>
    <g>serve</>        Run a server
                  <p(245)>deno serve main.ts</>
    <g>watch</>        Run a program, watching for changes and hot-replacing modules
                  <p(245)>deno watch main.ts</>
    <g>task</>         Run a task defined in the configuration file
                  <p(245)>deno task dev</>
    <g>repl</>         Start an interactive Read-Eval-Print Loop (REPL) for Deno
    <g>eval</>         Evaluate a script from the command line

  <y>Dependency management:</>
    <g>add</>          Add dependencies
                  <p(245)>deno add jsr:@std/assert  |  deno add npm:express</>
    <g>bump-version</> Update version in the configuration file
    <g>install</>      Installs dependencies either in the local project or globally to a bin directory
    <g>ci</>           Install dependencies in a clean, reproducible way for CI environments
                  <p(245)>deno ci  |  deno ci --prod</>
    <g>uninstall</>    Uninstalls a dependency or an executable script in the installation root's bin directory
    <g>outdated</>     Find and update outdated dependencies
    <g>list</>         List the dependencies declared in deno.json / package.json
    <g>approve-scripts</> Approve npm lifecycle scripts
    <g>remove</>       Remove dependencies from the configuration file
    <g>link</>         Link a local JSR package into the current project
                  <p(245)>deno link ../my-local-pkg</>
    <g>unlink</>       Remove a linked local package from the current project
    <g>publish</>      Publish the current working directory's package or workspace
    <g>why</>          Show why a package is installed

  <y>Tooling:</>
    <g>bench</>        Run benchmarks
                  <p(245)>deno bench bench.ts</>
    <g>check</>        Type-check the dependencies
    <g>clean</>        Remove the cache directory
    <g>compile</>      Compile the script into a self contained executable
                  <p(245)>deno compile main.ts  |  deno compile --target=x86_64-unknown-linux-gnu</>
    <g>coverage</>     Print coverage reports
    <g>deploy</>       Manage and publish applications with Deno Deploy
    <g>doc</>          Generate and show documentation for a module or built-ins
                  <p(245)>deno doc  |  deno doc --json  |  deno doc --html mod.ts</>
    <g>fmt</>          Format source files
                  <p(245)>deno fmt  |  deno fmt main.ts</>
    <g>info</>         Show info about cache or info related to source file
    <g>jupyter</>      Deno kernel for Jupyter notebooks
    <g>lint</>         Lint source files
    <g>init</>         Initialize a new project
    <g>test</>         Run tests
                  <p(245)>deno test  |  deno test test.ts</>
    <g>pack</>         Create an npm-compatible tarball from a Deno project
                  <p(245)>deno pack  |  deno pack --set-version 1.2.3</>
    <g>upgrade</>      Upgrade deno executable to given version
                  <p(245)>deno upgrade  |  deno upgrade 1.45.0  |  deno upgrade canary</>
{after-help}

<y>Docs:</> https://docs.deno.com
<y>Standard Library:</> https://jsr.io/@std
<y>Bugs:</> https://github.com/denoland/deno/issues
<y>Discord:</> https://discord.gg/deno

<p(245)>If you are an AI agent, read</> <c>https://deno.com/agents.md</> <p(245)>to get started.</>
");

pub fn flags_from_vec(args: Vec<OsString>) -> clap::error::Result<Flags> {
  flags_from_vec_with_initial_cwd(args, None)
}

/// Strip a single trailing carriage return from an argument.
///
/// When a script with CRLF line endings starts with a shebang like
/// `#!/usr/bin/env -S deno run --allow-net`, the kernel passes the trailing
/// `\r` as part of the final argument (e.g. `--allow-net\r`), which then
/// fails clap parsing with a confusing "isn't valid in this context" error.
/// Trimming a trailing CR here makes such scripts run as the author intended.
fn strip_trailing_cr(arg: OsString) -> OsString {
  let bytes = arg.as_encoded_bytes();
  if bytes.ends_with(b"\r") {
    let stripped = bytes[..bytes.len() - 1].to_vec();
    // SAFETY: `\r` is the single ASCII byte 0x0D; removing an ASCII byte from
    // the end of a valid OS-string byte sequence (UTF-8 on Unix, WTF-8 on
    // Windows) cannot split a multi-byte code point and keeps the encoding
    // valid.
    unsafe { OsString::from_encoded_bytes_unchecked(stripped) }
  } else {
    arg
  }
}

/// Main entry point for parsing deno's command line flags.
pub fn flags_from_vec_with_initial_cwd(
  args: Vec<OsString>,
  initial_cwd: Option<PathBuf>,
) -> clap::error::Result<Flags> {
  // Strip a trailing `\r` from each argument so that a shebang in a script
  // saved with CRLF line endings (e.g. `#!/usr/bin/env -S deno run -A\r`)
  // doesn't poison the final shebang argument with a stray carriage return.
  let args: Vec<OsString> = args.into_iter().map(strip_trailing_cr).collect();
  let args = if !args.is_empty()
    && (args[0].as_encoded_bytes().ends_with(b"dx")
      || args[0].as_encoded_bytes().ends_with(b"denox")
      || args[0].as_encoded_bytes().ends_with(b"dnx"))
  {
    let mut new_args = Vec::with_capacity(args.len() + 1);
    new_args.push(args[0].clone());
    new_args.push(OsString::from("x"));
    if args.len() >= 2 {
      new_args.extend(args.into_iter().skip(1));
    }
    new_args
  } else {
    args
  };
  // Fast path: `deno run <file> [script args...]` with no deno-level flags.
  // Building the full clap command tree (clap_root) costs ~5.5ms cold /
  // ~0.86ms warm at startup; for the overwhelmingly common bare-run case we
  // skip it entirely. Any deno flag (anything starting with `-` before the
  // script) falls through to the full parser, preserving exact behavior.
  // Config-file discovery still happens downstream in CliOptions, so this only
  // skips argument parsing, not resolution.
  if args.len() >= 3
    && args[1] == "run"
    && args[2] != "-"
    && args[2].as_encoded_bytes().first() != Some(&b'-')
    && let Some(script) = args[2].to_str()
  {
    let argv = args[3..]
      .iter()
      .map(|a| a.to_string_lossy().into_owned())
      .collect::<Vec<_>>();
    let mut flags = Flags {
      subcommand: DenoSubcommand::Run(RunFlags {
        script: script.to_string(),
        bare: false,
        coverage_dir: None,
        print_task_list: false,
      }),
      argv,
      // `flags.code_cache_enabled = !matches.get_flag("no-code-cache")` in
      // the full `run_parse`; without any flags before the script that
      // resolves to `true`. Match it here so downstream code-cache checks
      // behave identically.
      code_cache_enabled: true,
      ..Default::default()
    };
    // NODE_OPTIONS / DENO_COMPAT etc. are handled here too, matching the
    // tail of `flags_from_vec` after clap parsing.
    apply_node_options(&mut flags);
    return Ok(flags);
  }
  let mut app = clap_root();
  let mut matches =
    app
      .try_get_matches_from_mut(&args)
      .map_err(|mut e| match e.kind() {
        ErrorKind::MissingRequiredArgument => {
          if let Some(clap::error::ContextValue::Strings(s)) =
            e.get(clap::error::ContextKind::InvalidArg)
            && s.len() == 1
            && s[0] == "--global"
            && args.iter().any(|arg| arg == "install")
          {
            e.insert(
              clap::error::ContextKind::Usage,
              clap::error::ContextValue::StyledStr(
                "Note: Permission flags can only be used in a global setting"
                  .into(),
              ),
            );
          }

          e
        }
        _ => e,
      })?;

  let mut flags = Flags {
    initial_cwd,
    ..Default::default()
  };

  // to pass all flags, even help
  if matches.subcommand_matches("deploy").is_some() {
    deploy_parse(
      &mut flags,
      &mut matches.remove_subcommand().unwrap().1,
      false,
    )?;
    return Ok(flags);
  } else if matches.subcommand_matches("sandbox").is_some() {
    deploy_parse(
      &mut flags,
      &mut matches.remove_subcommand().unwrap().1,
      true,
    )?;
    return Ok(flags);
  }

  if matches.get_flag("quiet") {
    flags.log_level = Some(Level::Error);
  } else if let Some(log_level) = matches.get_one::<String>("log-level") {
    flags.log_level = match log_level.as_str() {
      "trace" => Some(Level::Trace),
      "debug" => Some(Level::Debug),
      "info" => Some(Level::Info),
      _ => unreachable!(),
    };
  }

  if let Some(help_expansion) = matches.get_one::<String>("help").cloned() {
    let mut subcommand = match matches.remove_subcommand() {
      Some((sub, _)) => app.find_subcommand(sub).unwrap().clone(),
      _ => app,
    };

    if help_expansion == "full" {
      subcommand = enable_full(subcommand);
    } else if help_expansion == "unstable"
      && subcommand
        .get_arguments()
        .any(|arg| arg.get_id().as_str() == "unstable")
    {
      subcommand = enable_unstable(subcommand);
    }

    help_parse(&mut flags, subcommand);
    return Ok(flags);
  } else if matches.contains_id("help") {
    let subcommand = match matches.remove_subcommand() {
      Some((sub, _)) => app.find_subcommand(sub).unwrap().clone(),
      _ => 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
      };

    if subcommand.get_name() == "deploy" {
      flags.argv = vec![String::from("--help")];
      flags.permissions.allow_all = true;
      flags.subcommand = DenoSubcommand::Deploy(DeployFlags::default());
      return Ok(flags);
    } else if subcommand.get_name() == "sandbox" {
      flags.argv = vec![String::from("--help")];
      flags.permissions.allow_all = true;
      flags.subcommand = DenoSubcommand::Deploy(DeployFlags { sandbox: true });
      return Ok(flags);
    }

    help_parse(&mut flags, subcommand);

    return Ok(flags);
  }

  match matches.remove_subcommand() {
    Some((subcommand, mut m)) => {
      let pre_subcommand_arg = app
        .get_arguments()
        .filter(|arg| !arg.is_global_set())
        .find(|arg| {
          matches
            .value_source(arg.get_id().as_str())
            .is_some_and(|value| {
              value == clap::parser::ValueSource::CommandLine
            })
        })
        .map(|arg| {
          format!(
            "--{}",
            arg.get_long().unwrap_or_else(|| arg.get_id().as_str())
          )
        });

      if let Some(arg) = pre_subcommand_arg {
        // Only suggest moving the flag after the subcommand if the subcommand
        // actually accepts it. Otherwise the "'<subcommand> <arg>' exists" tip
        // would lie about a combination that doesn't exist (e.g. `deno -A lint`
        // suggesting `lint --allow-all`, which `deno lint` rejects).
        let arg_long = arg.trim_start_matches('-');
        let (usage, subcommand_has_arg) = {
          let sub = app.find_subcommand_mut(&subcommand).unwrap();
          let has_arg =
            sub.get_arguments().any(|a| a.get_long() == Some(arg_long));
          (sub.render_usage(), has_arg)
        };

        let mut err =
          clap::error::Error::new(ErrorKind::UnknownArgument).with_cmd(&app);
        err.insert(
          clap::error::ContextKind::InvalidArg,
          clap::error::ContextValue::String(arg.clone()),
        );

        if subcommand_has_arg {
          let valid = app.get_styles().get_valid();

          let styled_suggestion = clap::builder::StyledStr::from(format!(
            "'{}{subcommand} {arg}{}' exists",
            valid.render(),
            valid.render_reset()
          ));

          err.insert(
            clap::error::ContextKind::Suggested,
            clap::error::ContextValue::StyledStrs(vec![styled_suggestion]),
          );
        }

        err.insert(
          clap::error::ContextKind::Usage,
          clap::error::ContextValue::StyledStr(usage),
        );

        return Err(err);
      }

      match subcommand.as_str() {
        "add" => add_parse(&mut flags, &mut m)?,
        "audit" => audit_parse(&mut flags, &mut m)?,
        "approve-scripts" => approve_scripts_parse(&mut flags, &mut m)?,
        "remove" => remove_parse(&mut flags, &mut m)?,
        "bench" => bench_parse(&mut flags, &mut m)?,
        "bundle" => bundle_parse(&mut flags, &mut m)?,
        "cache" => cache_parse(&mut flags, &mut m)?,
        "check" => check_parse(&mut flags, &mut m)?,
        "clean" => clean_parse(&mut flags, &mut m),
        "compile" => compile_parse(&mut flags, &mut m)?,
        "create" => create_parse(&mut flags, &mut m)?,
        "desktop" => desktop_parse(&mut flags, &mut m)?,
        "completions" => completions_parse(&mut flags, &mut m, app),
        "coverage" => coverage_parse(&mut flags, &mut m)?,
        "doc" => doc_parse(&mut flags, &mut m)?,
        "eval" => eval_parse(&mut flags, &mut m)?,
        "fmt" => fmt_parse(&mut flags, &mut m)?,
        "init" => init_parse(&mut flags, &mut m)?,
        "info" => info_parse(&mut flags, &mut m)?,
        "list" => list_parse(&mut flags, &mut m)?,
        "install" => install_parse(&mut flags, &mut m, app)?,
        "link" => link_parse(&mut flags, &mut m)?,
        "unlink" => unlink_parse(&mut flags, &mut m)?,
        "ci" => ci_parse(&mut flags, &mut m)?,
        "json_reference" => json_reference_parse(&mut flags, &mut m, app),
        "jupyter" => jupyter_parse(&mut flags, &mut m),
        "lint" => lint_parse(&mut flags, &mut m)?,
        "lsp" => lsp_parse(&mut flags, &mut m),
        "outdated" => outdated_parse(&mut flags, &mut m, false)?,
        "repl" => repl_parse(&mut flags, &mut m)?,
        "run" => run_parse(&mut flags, &mut m, app, false, false)?,
        "watch" => run_parse(&mut flags, &mut m, app, false, true)?,
        "serve" => serve_parse(&mut flags, &mut m, app)?,
        "task" => task_parse(&mut flags, &mut m, app)?,
        "test" => test_parse(&mut flags, &mut m)?,
        "transpile" => transpile_parse(&mut flags, &mut m)?,
        "types" => types_parse(&mut flags, &mut m),
        "uninstall" => uninstall_parse(&mut flags, &mut m),
        "update" => outdated_parse(&mut flags, &mut m, true)?,
        "upgrade" => upgrade_parse(&mut flags, &mut m)?,
        "bump-version" => bump_version_parse(&mut flags, &mut m),
        "vendor" => vendor_parse(&mut flags, &mut m),
        "why" => why_parse(&mut flags, &mut m),
        "publish" => publish_parse(&mut flags, &mut m)?,
        "pack" => pack_parse(&mut flags, &mut m)?,
        "x" => x_parse(&mut flags, &mut m)?,
        _ => unreachable!(),
      }
    }
    _ => {
      let has_non_globals = app
        .get_arguments()
        .filter(|arg| !arg.is_global_set())
        .any(|arg| {
          matches
            .value_source(arg.get_id().as_str())
            .is_some_and(|value| {
              value != clap::parser::ValueSource::DefaultValue
            })
        });

      if has_non_globals || matches.contains_id("script_arg") {
        run_parse(&mut flags, &mut matches, app, true, false)?;
      } else {
        handle_repl_flags(
          &mut flags,
          ReplFlags {
            eval_files: None,
            eval: None,
            is_default_command: true,
            json: false,
          },
        )
      }
    }
  }

  apply_node_options(&mut flags);

  Ok(flags)
}

fn enable_unstable(command: Command) -> Command {
  command
    .mut_arg("unstable", |arg| {
      let new_help = arg
        .get_help()
        .unwrap()
        .to_string()
        .split_once("\n")
        .unwrap()
        .0
        .to_string();
      arg.help_heading(UNSTABLE_HEADING).help(new_help)
    })
    .mut_args(|arg| {
      // long_help here is being used as a metadata, see unstable args definition
      if arg.get_help_heading() == Some(UNSTABLE_HEADING)
        && arg.get_long_help().is_some()
      {
        arg.hide(false)
      } else {
        arg
      }
    })
}

fn enable_full(command: Command) -> Command {
  command.mut_args(|arg| {
    let long_help = arg.get_long_help();
    if long_help.is_none_or(|s| s.to_string() != "false") {
      arg.hide(false)
    } else {
      arg
    }
  })
}

macro_rules! heading {
    ($($name:ident = $title:expr),+; $total:literal) => {
      $(const $name: &str = $title;)+
      const HEADING_ORDER: [&str; $total] = [$($name),+];
    };
}

heading! {
  // subcommand flags headings
  DOC_HEADING = "Documentation options",
  FMT_HEADING = "Formatting options",
  COMPILE_HEADING = "Compile options",
  DESKTOP_HEADING = "Desktop options",
  LINT_HEADING = "Linting options",
  TEST_HEADING = "Testing options",
  UPGRADE_HEADING = "Upgrade options",
  PUBLISH_HEADING = "Publishing options",

  // categorized flags headings
  TYPE_CHECKING_HEADING = "Type checking options",
  FILE_WATCHING_HEADING = "File watching options",
  DEBUGGING_HEADING = "Debugging options",
  DEPENDENCY_MANAGEMENT_HEADING = "Dependency management options",

  UNSTABLE_HEADING = "Unstable options";
  13
}

fn help_parse(flags: &mut Flags, mut subcommand: Command) {
  let mut args = subcommand
    .get_arguments()
    .map(|arg| {
      (
        arg.get_id().as_str().to_string(),
        arg.get_help_heading().map(|h| h.to_string()),
      )
    })
    .collect::<Vec<_>>();
  args.sort_by(|a, b| {
    a.1
      .as_ref()
      .map(|heading| HEADING_ORDER.iter().position(|h| h == heading))
      .cmp(
        &b.1
          .as_ref()
          .map(|heading| HEADING_ORDER.iter().position(|h| h == heading)),
      )
      .then(a.0.cmp(&b.0))
  });

  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 += heading_i * 100;
    }

    subcommand = subcommand.mut_arg(arg, |arg| arg.display_order(i));
  }

  flags.subcommand = DenoSubcommand::Help(HelpFlags {
    help: subcommand.render_help().ansi().to_string(),
  });
}

// copied from clap, https://github.com/clap-rs/clap/blob/4e1a565b8adb4f2ad74a9631565574767fdc37ae/clap_builder/src/parser/features/suggestions.rs#L11-L26
pub fn did_you_mean<T, I>(v: &str, possible_values: I) -> Vec<String>
where
  T: AsRef<str>,
  I: IntoIterator<Item = T>,
{
  let mut candidates: Vec<(f64, String)> = possible_values
    .into_iter()
    // GH #4660: using `jaro` because `jaro_winkler` implementation in `strsim-rs` is wrong
    // causing strings with common prefix >=10 to be considered perfectly similar
    .map(|pv| (strsim::jaro(v, pv.as_ref()), pv.as_ref().to_owned()))
    // Confidence of 0.7 so that bar -> baz is suggested
    .filter(|(confidence, _)| *confidence > 0.8)
    .collect();
  candidates
    .sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
  candidates.into_iter().map(|(_, pv)| pv).collect()
}

fn handle_repl_flags(flags: &mut Flags, repl_flags: ReplFlags) {
  // If user runs just `deno` binary we enter REPL and allow all permissions.
  if repl_flags.is_default_command {
    flags.allow_all();
  }
  flags.subcommand = DenoSubcommand::Repl(repl_flags);
}

pub fn clap_root() -> Command {
  debug_assert_eq!(DENO_VERSION_INFO.typescript, deno_snapshots::TS_VERSION);
  let long_version = format!(
    "{} ({}, {}, {})\nv8 {}\ntypescript {}",
    DENO_VERSION_INFO.deno,
    DENO_VERSION_INFO.release_channel.name(),
    env!("PROFILE"),
    env!("TARGET"),
    deno_core::v8::VERSION_STRING,
    DENO_VERSION_INFO.typescript
  );

  run_args(Command::new("deno"), true)
    .with_unstable_args(UnstableArgsConfig::ResolutionAndRuntime)
    .next_line_help(false)
    .bin_name("deno")
    .styles(
      clap::builder::Styles::styled()
        .header(AnsiColor::Yellow.on_default())
        .usage(AnsiColor::White.on_default())
        .literal(AnsiColor::Green.on_default())
        .placeholder(AnsiColor::Green.on_default()),
    )
    .color(ColorChoice::Auto)
    .term_width(800)
    .version(DENO_VERSION_INFO.deno)
    .long_version(long_version)
    .disable_version_flag(true)
    .disable_help_flag(true)
    .disable_help_subcommand(true)
    .arg(
      Arg::new("help")
        .short('h')
        .long("help")
        .action(ArgAction::Append)
        .num_args(0..=1)
        .require_equals(true)
        .value_name("CONTEXT")
        .value_parser(["unstable", "full"])
        .global(true),
    )
    .arg(
      Arg::new("version")
        .short('V')
        .short_alias('v')
        .long("version")
        .action(ArgAction::Version)
        .help("Print version"),
    )
    .arg(
      Arg::new("log-level")
        .short('L')
        .long("log-level")
        .help("Set log level")
        .hide(true)
        .value_parser(["trace", "debug", "info"])
        .global(true),
    )
    .arg(
      Arg::new("quiet")
        .short('q')
        .long("quiet")
        .help("Suppress diagnostic output")
        .action(ArgAction::SetTrue)
        .global(true),
    )
    .subcommand(run_subcommand())
    .subcommand(watch_subcommand())
    .subcommand(serve_subcommand())
    .defer(|cmd| {
      let cmd = cmd
        .subcommand(add_subcommand())
        .subcommand(audit_subcommand())
        .subcommand(remove_subcommand())
        .subcommand(bench_subcommand())
        .subcommand(bundle_subcommand())
        .subcommand(cache_subcommand())
        .subcommand(check_subcommand())
        .subcommand(clean_subcommand())
        .subcommand(compile_subcommand())
        .subcommand(create_subcommand())
        .subcommand(desktop_subcommand())
        .subcommand(completions_subcommand())
        .subcommand(coverage_subcommand())
        .subcommand(doc_subcommand())
        .subcommand(deploy_subcommand())
        .subcommand(sandbox_subcommand())
        .subcommand(eval_subcommand())
        .subcommand(fmt_subcommand())
        .subcommand(init_subcommand())
        .subcommand(info_subcommand())
        .subcommand(list_subcommand())
        .subcommand(install_subcommand())
        .subcommand(ci_subcommand())
        .subcommand(json_reference_subcommand())
        .subcommand(jupyter_subcommand())
        .subcommand(approve_scripts_subcommand())
        .subcommand(uninstall_subcommand())
        .subcommand(outdated_subcommand())
        .subcommand(link_subcommand())
        .subcommand(unlink_subcommand())
        .subcommand(lsp_subcommand())
        .subcommand(lint_subcommand())
        .subcommand(publish_subcommand())
        .subcommand(pack_subcommand())
        .subcommand(repl_subcommand())
        .subcommand(task_subcommand())
        .subcommand(test_subcommand())
        .subcommand(transpile_subcommand())
        .subcommand(types_subcommand())
        .subcommand(update_subcommand())
        .subcommand(upgrade_subcommand())
        .subcommand(bump_version_subcommand())
        .subcommand(vendor_subcommand())
        .subcommand(why_subcommand())
        .subcommand(x_subcommand());

      let help = help_subcommand(&cmd);
      cmd.subcommand(help)
    })
    .help_template(DENO_HELP)
    .after_help(&*ENV_VARIABLES_HELP)
    .next_line_help(false)
}

#[inline(always)]
fn command(
  name: &'static str,
  about: impl clap::builder::IntoResettable<clap::builder::StyledStr>,
  unstable_args_config: UnstableArgsConfig,
) -> Command {
  Command::new(name)
    .about(about)
    .with_unstable_args(unstable_args_config)
}

fn help_subcommand(app: &Command) -> Command {
  command("help", None, UnstableArgsConfig::None)
    .disable_version_flag(true)
    .disable_help_subcommand(true)
    .subcommands(app.get_subcommands().map(|command| {
      Command::new(command.get_name().to_owned())
        .disable_help_flag(true)
        .disable_version_flag(true)
    }))
}

fn add_dev_arg() -> Arg {
  Arg::new("dev")
    .long("dev")
    .short('D')
    .help("Add the package as a dev dependency (under `devDependencies`). Note: this only applies when adding to a `package.json` file.")
    .action(ArgAction::SetTrue)
}

fn add_optional_arg() -> Arg {
  Arg::new("save-optional")
    .long("save-optional")
    .short('O')
    .help("Add the package as an optional dependency (under `optionalDependencies`). Note: this only applies when adding to a `package.json` file.")
    .action(ArgAction::SetTrue)
    .conflicts_with("dev")
}

fn add_no_save_arg() -> Arg {
  Arg::new("no-save")
    .long("no-save")
    .help(
      "Install the package(s) without adding them to the configuration file.",
    )
    .action(ArgAction::SetTrue)
    .conflicts_with("dev")
    .conflicts_with("save-optional")
}

fn add_unscoped_arg() -> Arg {
  Arg::new("unscoped")
    .long("unscoped")
    .help("Use the package name without its scope as the alias (ex. `jsr:@david/jsonc-morph` is added as `jsonc-morph`). Packages given an explicit alias are unaffected.")
    .action(ArgAction::SetTrue)
}

fn add_subcommand() -> Command {
  command(
    "add",
    cstr!(
      "Add dependencies to your configuration file.
  <p(245)>deno add express</>

Unprefixed packages default to npm. Use jsr: prefix for jsr packages:
  <p(245)>deno add jsr:@std/path</>

Or multiple dependencies at once:
  <p(245)>deno add express jsr:@std/path</>"
    ),
    UnstableArgsConfig::None,
  )
  .defer(|cmd| {
    cmd
      .arg(
        Arg::new("packages")
          .help("List of packages to add")
          .required_unless_present("help")
          .num_args(1..)
          .action(ArgAction::Append),
      )
      .arg(add_dev_arg())
      .arg(add_optional_arg())
      .arg(add_no_save_arg())
      .arg(allow_scripts_arg())
      .arg(allow_import_arg())
      .arg(deny_import_arg())
      .args(lock_args())
      .arg(lockfile_only_arg())
      .args(default_registry_args())
      .arg(
        Arg::new("save-exact")
          .long("save-exact")
          .alias("exact")
          .help("Save exact version without the caret (^)")
          .action(ArgAction::SetTrue),
      )
      .arg(add_unscoped_arg())
      .arg(package_json_arg())
      .arg(env_file_arg())
      .arg(min_dep_age_arg())
  })
}

fn approve_scripts_subcommand() -> Command {
  command(
    "approve-scripts",
    cstr!("Approve npm lifecycle scripts for installed dependencies."),
    UnstableArgsConfig::None,
  )
  .alias("approve-builds")
  .defer(|cmd| {
    cmd.arg(
      Arg::new("packages")
        .help("Packages to approve (npm specifiers). When omitted, you will be prompted to select from installed packages with lifecycle scripts.")
        .num_args(0..)
        .action(ArgAction::Append),
    )
    .arg(lockfile_only_arg())
  })
}

fn approve_scripts_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  flags.subcommand = DenoSubcommand::ApproveScripts(ApproveScriptsFlags {
    packages: matches
      .remove_many::<String>("packages")
      .map(|values| {
        values
          .flat_map(flat_escape_split_commas)
          .collect::<Result<Vec<_>, _>>()
      })
      .transpose()?
      .unwrap_or_default(),
    lockfile_only: matches.get_flag("lockfile-only"),
  });
  Ok(())
}

fn audit_subcommand() -> Command {
  command(
    "audit",
    cstr!(
      "Audit currently installed dependencies.
  <p(245)>deno audit</>

Show only high and critical severity vulnerabilities
  <p(245)>deno audit --level=high</>

Check against socket.dev vulnerability database
  <p(245)>deno audit --socket</>

Don't error if the audit data can't be retrieved from the registry
  <p(245)>deno audit --ignore-registry-errors</>"
    ),
    UnstableArgsConfig::None,
  )
  .defer(|cmd| {
    cmd
      .args(lock_args())
      .arg(env_file_arg())
      .arg(
        Arg::new("level")
          .long("level")
          .alias("audit-level")
          .alias("severity")
          .help("Only show advisories with severity greater or equal to the one specified")
          .value_parser([
            "low",
            "moderate",
            "high",
            "critical",
          ]),
      )
      .arg(
        Arg::new("ignore-unfixable")
          .long("ignore-unfixable")
          .help("Ignore advisories that don't have any actions to resolve them")
          .action(ArgAction::SetTrue)
      )
      .arg(
        Arg::new("socket")
          .long("socket")
          .help("Check against socket.dev vulnerability database")
          .action(ArgAction::SetTrue)
      )
      .arg(
        Arg::new("ignore-registry-errors")
          .long("ignore-registry-errors")
          .help("Return exit code 0 if remote service(s) responds with an error.")
          .action(ArgAction::SetTrue)
      )
      .arg(
        Arg::new("ignore")
          .long("ignore")
          .help("Ignore advisories matching the given CVE IDs")
          .action(ArgAction::Append)
          .value_name("CVE")
      )
      .arg(
        Arg::new("fix")
          .long("fix")
          .help("Automatically fix vulnerabilities by upgrading packages")
          .action(ArgAction::SetTrue)
      )
      .arg(
        Arg::new("action")
          .value_parser(["fix"])
          .hide(true)
      )
  })
}

fn why_subcommand() -> Command {
  command(
    "why",
    cstr!(
      "Show why a package is installed, displaying the dependency chain from your project's direct dependencies to the specified package.
  <p(245)>deno why express</>

Show why a specific version is installed
  <p(245)>deno why express@4.18.2</>"
    ),
    UnstableArgsConfig::None,
  )
  .defer(|cmd| {
    cmd
      .args(lock_args())
      .arg(env_file_arg())
      .arg(
        Arg::new("package")
          .required(true)
          .help("The package name (and optional version) to explain"),
      )
  })
}

fn default_registry_args() -> [Arg; 2] {
  [
    Arg::new("npm")
      .long("npm")
      .help("assume unprefixed package names are npm packages (default)")
      .action(ArgAction::SetTrue)
      .conflicts_with("jsr"),
    Arg::new("jsr")
      .long("jsr")
      .help("assume unprefixed package names are jsr packages")
      .action(ArgAction::SetTrue)
      .conflicts_with("npm"),
  ]
}

fn remove_subcommand() -> Command {
  command(
    "remove",
    cstr!(
      "Remove dependencies from the configuration file.
  <p(245)>deno remove @std/path</>

You can remove multiple dependencies at once:
  <p(245)>deno remove @std/path @std/assert</>

With the <c>--global</> flag, this is an alias for <c>deno uninstall --global</> and
removes a globally installed executable script:
  <p(245)>deno remove --global file_server</>
"
    ),
    UnstableArgsConfig::None,
  )
  .defer(|cmd| {
    cmd
      .arg(
        Arg::new("packages")
          .help("List of packages to remove")
          .required_unless_present("help")
          .num_args(1..)
          .action(ArgAction::Append),
      )
      .arg(
        Arg::new("root")
          .long("root")
          .help("Installation root")
          .requires("global")
          .value_hint(ValueHint::DirPath),
      )
      .arg(
        Arg::new("global")
          .long("global")
          .short('g')
          .help("Remove globally installed package or module")
          .action(ArgAction::SetTrue),
      )
      .args(lock_args())
      .arg(lockfile_only_arg().conflicts_with("global"))
      .arg(package_json_arg().conflicts_with("global"))
      .arg(min_dep_age_arg().conflicts_with("global"))
  })
}

fn link_subcommand() -> Command {
  command(
    "link",
    cstr!(
      "Link a local JSR package into the current project for development.

  <p(245)>deno link ../my-local-pkg</>

Each path must be a directory containing a deno.json with a JSR-style \"name\" field.
The path is appended to the \"links\" array in the nearest deno.json, and modules
imported by that package's name resolve to the local copy instead of the registry.

To stop using the local copy:
  <p(245)>deno unlink ../my-local-pkg</>
  <p(245)>deno unlink @scope/name</>
"
    ),
    UnstableArgsConfig::None,
  )
  .defer(|cmd| {
    cmd
      .arg(
        Arg::new("paths")
          .help("Paths to local package directories to link")
          .required_unless_present("help")
          .num_args(1..)
          .action(ArgAction::Append),
      )
      .args(lock_args())
      .arg(lockfile_only_arg())
  })
}

fn unlink_subcommand() -> Command {
  command(
    "unlink",
    cstr!(
      "Remove a linked local package from the current project.

  <p(245)>deno unlink ../my-local-pkg</>
  <p(245)>deno unlink @scope/name</>

Accepts either a path that matches an existing entry in the \"links\" array,
or the JSR-style name of a linked package.
"
    ),
    UnstableArgsConfig::None,
  )
  .defer(|cmd| {
    cmd
      .arg(
        Arg::new("names_or_paths")
          .help("Linked package names or paths to remove")
          .required_unless_present("help")
          .num_args(1..)
          .action(ArgAction::Append),
      )
      .args(lock_args())
      .arg(lockfile_only_arg())
  })
}

fn bench_subcommand() -> Command {
  command(
    "bench",
    cstr!("Run benchmarks using Deno's built-in bench tool.

Evaluate the given files, run all benches declared with 'Deno.bench()' and report results to standard output:
  <p(245)>deno bench src/fetch_bench.ts src/signal_bench.ts</>

If you specify a directory instead of a file, the path is expanded to all contained files matching the glob <c>{*_,*.,}bench.{js,mjs,ts,mts,jsx,tsx}</>:
  <p(245)>deno bench src/</>

<y>Read more:</> <c>https://docs.deno.com/go/bench</>"),
    UnstableArgsConfig::ResolutionAndRuntime,
  )
  .defer(|cmd| {
    runtime_args(cmd, true, false, true)
      .arg(check_arg(true))
      .arg(
        Arg::new("json")
          .long("json")
          .action(ArgAction::SetTrue)
          .help("UNSTABLE: Output benchmark result in JSON format"),
      )
      .arg(
        Arg::new("ignore")
          .long("ignore")
          .num_args(1..)
          .action(ArgAction::Append)
          .require_equals(true)
          .help("Ignore files"),
      )
      .arg(
        Arg::new("filter")
          .long("filter")
          .allow_hyphen_values(true)
          .help(
          "Run benchmarks with this string or regexp pattern in the bench name",
        ),
      )
      .arg(
        Arg::new("files")
          .help("List of file names to run")
          .num_args(..)
          .action(ArgAction::Append),
      )
      .arg(
        Arg::new("no-run")
          .long("no-run")
          .help("Cache bench modules, but don't run benchmarks")
          .action(ArgAction::SetTrue),
      )
      .arg(permit_no_files_arg())
      .arg(watch_arg(true))
      .arg(watch_exclude_arg())
      .arg(no_clear_screen_arg())
      .arg(script_arg().last(true))
      .arg(env_file_arg())
      .arg(executable_ext_arg())
  })
}

fn bundle_subcommand() -> Command {
  fn format_parser(s: &str) -> Result<BundleFormat, clap::Error> {
    match s {
      "esm" => Ok(BundleFormat::Esm),
      "cjs" => Ok(BundleFormat::Cjs),
      "iife" => Ok(BundleFormat::Iife),
      _ => Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)),
    }
  }
  fn packages_parser(s: &str) -> Result<PackageHandling, clap::Error> {
    match s {
      "bundle" => Ok(PackageHandling::Bundle),
      "external" => Ok(PackageHandling::External),
      _ => Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)),
    }
  }
  fn platform_parser(s: &str) -> Result<BundlePlatform, clap::Error> {
    match s {
      "browser" => Ok(BundlePlatform::Browser),
      "deno" => Ok(BundlePlatform::Deno),
      _ => Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)),
    }
  }
  fn sourcemap_parser(s: &str) -> Result<SourceMapType, clap::Error> {
    match s {
      "linked" => Ok(SourceMapType::Linked),
      "inline" => Ok(SourceMapType::Inline),
      "external" => Ok(SourceMapType::External),
      _ => Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)),
    }
  }
  command(
    "bundle",
    "Output a single JavaScript file with all dependencies.

  deno bundle jsr:@std/http/file-server -o file-server.bundle.js

If no output file is given, the output is written to standard output:

  deno bundle jsr:@std/http/file-server
",
    UnstableArgsConfig::ResolutionOnly,
  )
  .defer(|cmd| {
    compile_args(cmd)
      .arg(check_arg(false))
      .arg(env_file_arg())
      .arg(
        Arg::new("file")
          .num_args(1..)
          .required_unless_present("help")
          .value_hint(ValueHint::FilePath),
      )
      .arg(
        Arg::new("output")
          .long("output")
          .short('o')
          .help("Output path`")
          .num_args(1)
          .value_parser(value_parser!(String))
          .value_hint(ValueHint::FilePath),
      )
      .arg(
        Arg::new("outdir")
          .long("outdir")
          .help("Output directory for bundled files")
          .num_args(1)
          .value_parser(value_parser!(String))
          .value_hint(ValueHint::DirPath),
      )
      .arg(
        Arg::new("external")
          .long("external")
          .action(ArgAction::Append)
          .num_args(1)
          .value_parser(value_parser!(String)),
      )
      .arg(
        Arg::new("format")
          .long("format")
          .num_args(1)
          .value_parser(clap::builder::ValueParser::new(format_parser))
          .default_value("esm"),
      )
      .arg(
        Arg::new("packages")
          .long("packages")
          .help("How to handle packages. Accepted values are 'bundle' or 'external'")
          .num_args(1)
          .value_parser(clap::builder::ValueParser::new(packages_parser))
          .default_value("bundle"),
      )
      .arg(
        Arg::new("minify")
          .long("minify")
          .help("Minify the output")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("keep-names")
          .long("keep-names")
          .help("Keep function and class names")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("code-splitting")
          .long("code-splitting")
          .help("Enable code splitting")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("inline-imports")
          .long("inline-imports")
          .help(cstr!("Whether to inline imported modules into the importing file <p(245)>[default: true]</>"))
          .require_equals(true)
          .default_value("true")
          .default_missing_value("true")
          .value_parser(value_parser!(bool))
          .num_args(0..=1)
          .action(ArgAction::Set),
      )
      .arg(
        Arg::new("sourcemap")
          .long("sourcemap")
          .help("Generate source map. Accepted values are 'linked', 'inline', or 'external'")
          .require_equals(true)
          .default_missing_value("linked")
          .value_parser(clap::builder::ValueParser::new(sourcemap_parser))
          .num_args(0..=1)
          .action(ArgAction::Set),
      )
      .arg(
        Arg::new("watch")
          .long("watch")
          .help("Watch and rebuild on changes")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("platform")
          .long("platform")
          .help("Platform to bundle for. Accepted values are 'browser' or 'deno'")
          .num_args(1)
          .value_parser(clap::builder::ValueParser::new(platform_parser))
          .default_value("deno"),
      )
      .arg(
        Arg::new("declaration")
          .long("declaration")
          .help("Generate .d.ts declaration files alongside the bundle")
          .action(ArgAction::SetTrue),
      )
      .arg(allow_scripts_arg())
      .arg(allow_import_arg())
      .arg(deny_import_arg())
  })
}

fn cache_subcommand() -> Command {
  command(
    "cache",
    cstr!("Cache and compile remote dependencies.

Download and compile a module with all of its static dependencies and save them in the local cache, without running any code:
  <p(245)>deno cache jsr:@std/http/file-server</>

Future runs of this module will trigger no downloads or compilation unless --reload is specified

<y>Read more:</> <c>https://docs.deno.com/go/cache</>"),
    UnstableArgsConfig::ResolutionOnly,
)
  .hide(true)
  .defer(|cmd| {
    compile_args(cmd)
      .arg(check_arg(false))
      .arg(
        Arg::new("file")
          .num_args(1..)
          .required_unless_present("help")
          .value_hint(ValueHint::FilePath),
      )
      .arg(allow_scripts_arg())
      .arg(allow_import_arg())
      .arg(deny_import_arg())
      .arg(env_file_arg())
  })
}

fn clean_subcommand() -> Command {
  command(
    "clean",
    cstr!("Remove the cache directory (<c>$DENO_DIR</>)"),
    UnstableArgsConfig::None,
  )
  .defer(|cmd| {
    cmd
      .arg(
        Arg::new("except-paths")
          .required_if_eq("except", "true")
          .num_args(1..)
          .value_hint(ValueHint::FilePath),
      )
      .arg(
        Arg::new("except")
          .long("except")
          .short('e')
          .help("Retain cache data needed by the given files")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("dry-run")
          .long("dry-run")
          .action(ArgAction::SetTrue)
          .help("Show what would be removed without performing any actions"),
      )
      .arg(node_modules_dir_arg().requires("except"))
      .arg(node_modules_linker_arg().requires("except"))
      .arg(vendor_arg().requires("except"))
  })
}

fn check_subcommand() -> Command {
  command("check",
      cstr!("Download and type-check without execution.

  <p(245)>deno check jsr:@std/http/file-server</>

Unless --reload is specified, this command will not re-download already cached dependencies

<y>Read more:</> <c>https://docs.deno.com/go/check</>"),
          UnstableArgsConfig::ResolutionAndRuntime
    )
    .defer(|cmd| {
      compile_args_without_check_args(cmd)
        .arg(no_code_cache_arg())
        .arg(
          Arg::new("all")
            .long("all")
            .help("Type-check all code, including remote modules and npm packages")
            .action(ArgAction::SetTrue)
            .conflicts_with("no-remote")
        )
        .arg(
          // past alias for --all
          Arg::new("remote")
            .long("remote")
            .help("Type-check all modules, including remote ones")
            .action(ArgAction::SetTrue)
            .conflicts_with("no-remote")
            .hide(true)
        )
        .arg(
          Arg::new("doc")
            .long("doc")
            .help("Type-check code blocks in JSDoc as well as actual code")
            .action(ArgAction::SetTrue)
        )
        .arg(
          Arg::new("doc-only")
          .long("doc-only")
          .help("Type-check code blocks in JSDoc and Markdown only")
            .action(ArgAction::SetTrue)
            .conflicts_with("doc")
        )
        .arg(
          Arg::new("check-js")
            .long("check-js")
            .help(
              "Enable type-checking of JavaScript files (equivalent to `compilerOptions.checkJs: true`)",
            )
            .action(ArgAction::SetTrue)
        )
        .arg(
          Arg::new("desktop")
            .long("desktop")
            .help("Type-check using the type definitions for `deno desktop`")
            .action(ArgAction::SetTrue)
            .help_heading(DESKTOP_HEADING)
        )
        .arg(
          Arg::new("file")
            .num_args(1..)
            .value_hint(ValueHint::FilePath),
        )
        .arg(allow_import_arg())
        .arg(deny_import_arg())
        .arg(v8_flags_arg())
        .arg(env_file_arg())
        .arg(watch_arg(false))
        .arg(watch_exclude_arg())
        .arg(no_clear_screen_arg())
      }
    )
}

const SUPPORTED_OS: [&str; 6] = [
  "x86_64-unknown-linux-gnu",
  "aarch64-unknown-linux-gnu",
  "x86_64-pc-windows-msvc",
  "aarch64-pc-windows-msvc",
  "x86_64-apple-darwin",
  "aarch64-apple-darwin",
];

fn compile_subcommand() -> Command {
  command(
    "compile",
    cstr!("Compiles the given script into a self contained executable.

  <p(245)>deno compile --allow-read --allow-net jsr:@std/http/file-server</>
  <p(245)>deno compile --output file_server jsr:@std/http/file-server</>

Any flags specified which affect runtime behavior will be applied to the resulting binary.

This allows distribution of a Deno application to systems that do not have Deno installed.
Under the hood, it bundles a slimmed down version of the Deno runtime along with your
JavaScript or TypeScript code.

Cross-compiling to different target architectures is supported using the <c>--target</> flag.
On the first invocation of `deno compile`, Deno will download the relevant binary and cache it in <c>$DENO_DIR</>.

<y>Read more:</> <c>https://docs.deno.com/go/compile</>
"),
    UnstableArgsConfig::ResolutionAndRuntime,
  )
  .defer(|cmd| {
    runtime_args(cmd, true, false, true)
      .arg(check_arg(true))
      .arg(
        Arg::new("include")
          .long("include")
          .help(
            cstr!("Includes an additional module or file/directory in the compiled executable.
  <p(245)>Use this flag if a dynamically imported module or a web worker main module
  fails to load in the executable or to embed a file or directory 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),
      )
      .arg(
        Arg::new("exclude")
          .long("exclude")
          .help(
            cstr!("Excludes a file/directory in the compiled executable.
  <p(245)>Use this flag to exclude a specific file or directory within the included files.
  For example, to exclude a certain folder in the bundled node_modules directory.</>",
          ))
          .action(ArgAction::Append)
          .value_hint(ValueHint::FilePath)
          .help_heading(COMPILE_HEADING),
      )
      .arg(
        Arg::new("output")
          .long("output")
          .short('o')
          .value_parser(value_parser!(String))
          .help(cstr!("Output file <p(245)>(defaults to $PWD/<<inferred-name>>)</>"))
          .value_hint(ValueHint::FilePath)
          .help_heading(COMPILE_HEADING),
      )
      .arg(
        Arg::new("target")
          .long("target")
          .help("Target OS architecture")
          .value_parser(SUPPORTED_OS)
          .help_heading(COMPILE_HEADING),
      )
      .arg(
        Arg::new("engine")
          .long("engine")
          .help(cstr!("JS engine the compiled binary runs on <p(245)>(quickjs is smaller and experimental, and does not receive the same security updates as v8)</>"))
          .value_parser(["v8", "quickjs"])
          .help_heading(COMPILE_HEADING),
      )
      .arg(no_code_cache_arg())
      .arg(
        Arg::new("no-terminal")
          .long("no-terminal")
          .help("Hide terminal on Windows")
          .action(ArgAction::SetTrue)
          .help_heading(COMPILE_HEADING),
      )
      .arg(
        Arg::new("icon")
          .long("icon")
          .help("Set the icon of the executable on Windows (.ico)")
          .value_parser(value_parser!(String))
          .help_heading(COMPILE_HEADING),
      )
      .arg(
        Arg::new("self-extracting")
          .long("self-extracting")
          .help("Create a self-extracting binary that extracts the embedded file system to disk on first run and then runs from there")
          .action(ArgAction::SetTrue)
          .help_heading(COMPILE_HEADING),
      )
      .arg(
        Arg::new("bundle")
          .long("bundle")
          .help(cstr!("<y>Experimental.</> Bundle the entrypoint with esbuild before embedding, instead of shipping the whole node_modules tree.
  <p(245)>Produces a smaller binary with faster startup, at the cost of dropping dynamic require/import patterns that can't be statically traced.</>"))
          .action(ArgAction::SetTrue)
          .help_heading(COMPILE_HEADING),
      )
      .arg(
        Arg::new("app-name")
          .long("app-name")
          .help(cstr!("Stable identity for the compiled app.
  <p(245)>Determines where origin-bound storage such as the default `Deno.openKv()`,
  `localStorage` and `caches` is persisted (under the platform's app data directory).
  Defaults to the output file name. Set this to keep storage stable across renames.</>"))
          .value_parser(value_parser!(String))
          .help_heading(COMPILE_HEADING),
      )
      .arg(
        Arg::new("minify")
          .long("minify")
          .help(cstr!("<y>Experimental.</> Minify the bundled output. Only meaningful with <c>--bundle</>.
  <p(245)>Reduces both the embedded bundle size and runtime memory use, at the cost of less readable stack traces.</>"))
          .action(ArgAction::SetTrue)
          .requires("bundle")
          .help_heading(COMPILE_HEADING),
      )
      .arg(exclude_unused_npm_arg().help_heading(COMPILE_HEADING))
      .arg(watch_arg(false))
      .arg(watch_exclude_arg())
      .arg(no_clear_screen_arg())
      .arg(executable_ext_arg())
      .arg(env_file_arg())
      .arg(
        script_arg()
          .required_unless_present("help")
          .trailing_var_arg(true),
      )
  })
}

fn desktop_subcommand() -> Command {
  command(
    "desktop",
    cstr!("Build and run desktop applications.

  <p(245)>deno desktop main.tsx</>
  <p(245)>deno desktop --hmr main.tsx</>
  <p(245)>deno desktop --output MyApp.app main.tsx</>
  <p(245)>deno desktop</>

Compiles the given script into a desktop application using a backend for the UI
layer. The entrypoint can be a file, or omitted (or <c>.</>) to auto-detect a
supported framework (Next.js, Astro, etc.) in the current directory.

<y>Read more:</> <c>https://docs.deno.com/go/desktop</>
"),
    UnstableArgsConfig::ResolutionAndRuntime,
  )
  .defer(|cmd| {
    runtime_args(cmd, true, true, true)
      .arg(check_arg(true))
      .arg(
        Arg::new("inspect-renderer")
          .long("inspect-renderer")
          .value_name("HOST_PORT")
          .default_missing_value("127.0.0.1:0")
          .help(
            "Override the CEF renderer debugger listen address; defaults to an auto-allocated port",
          )
          .num_args(0..=1)
          .require_equals(true)
          .value_parser(inspect_value_parser)
          .help_heading(DEBUGGING_HEADING),
      )
      .arg(
        Arg::new("include")
          .long("include")
          .help(
            cstr!("Includes an additional module or file/directory in the compiled executable.
  <p(245)>Use this flag if a dynamically imported module or a web worker main module
  fails to load in the executable or to embed a file or directory in the executable.
  This flag can be passed multiple times, to include multiple additional modules.</>",
          ))
          .action(ArgAction::Append)
          .value_hint(ValueHint::FilePath)
          .help_heading(DESKTOP_HEADING),
      )
      .arg(
        Arg::new("exclude")
          .long("exclude")
          .help(
            cstr!("Excludes a file/directory in the compiled executable.
  <p(245)>Use this flag to exclude a specific file or directory within the included files.</>",
          ))
          .action(ArgAction::Append)
          .value_hint(ValueHint::FilePath)
          .help_heading(DESKTOP_HEADING),
      )
      .arg(exclude_unused_npm_arg().help_heading(DESKTOP_HEADING))
      .arg(
        Arg::new("output")
          .long("output")
          .short('o')
          .value_parser(value_parser!(String))
          .help(cstr!("Output path <p(245)>(e.g. MyApp.app, MyApp.dmg, MyApp.AppImage, MyApp.deb, MyApp.rpm, MyApp.msi)</>"))
          .value_hint(ValueHint::FilePath)
          .help_heading(DESKTOP_HEADING),
      )
      .arg(
        Arg::new("target")
          .long("target")
          .help("Target OS architecture")
          .value_parser(SUPPORTED_OS)
          .help_heading(DESKTOP_HEADING),
      )
      .arg(no_code_cache_arg())
      .arg(
        Arg::new("icon")
          .long("icon")
          .help("Set the application icon (.ico on Windows, .icns or .png on macOS)")
          .value_parser(value_parser!(String))
          .help_heading(DESKTOP_HEADING),
      )
      .arg(
        Arg::new("hmr")
          .long("hmr")
          .help("Run the desktop app with Hot Module Replacement enabled")
          .action(ArgAction::SetTrue)
          .help_heading(DESKTOP_HEADING),
      )
      .arg(
        Arg::new("backend")
          .long("backend")
          .help(
            "Backend to use for the desktop app",
          )
          .value_parser(["webview", "cef", "raw"])
          .help_heading(DESKTOP_HEADING),
      )
      .arg(
        Arg::new("engine")
          .long("engine")
          .help("JS engine the desktop binary runs on (quickjs is smaller and experimental, and does not receive the same security updates as v8)")
          .value_parser(["v8", "quickjs"])
          .help_heading(DESKTOP_HEADING),
      )
      .arg(
        Arg::new("all-targets")
          .long("all-targets")
          .help("Build for all supported target platforms")
          .action(ArgAction::SetTrue)
          .help_heading(DESKTOP_HEADING),
      )
      .arg(
        Arg::new("compress")
          .long("compress")
          .help(
            "Make the packaged app self-extracting: the payload is compressed \
             inside the app and unpacked on first launch. Off by default. \
             Defaults to xz (decompressed by the system `tar` everywhere); \
             zstd is smaller/faster but needs the `zstd` tool at runtime.",
          )
          .value_parser(["xz", "lzma", "zstd"])
          .num_args(0..=1)
          .default_missing_value("xz")
          .help_heading(DESKTOP_HEADING),
      )
      .arg(executable_ext_arg())
      .arg(env_file_arg())
      .arg(
        // Optional: when omitted, defaults to "." so framework detection
        // runs against the current directory (`deno desktop` ==
        // `deno desktop .`).
        script_arg().trailing_var_arg(true),
      )
  })
}

fn completions_subcommand() -> Command {
  command(
    "completions",
    cstr!(
      "Output shell completion script to standard output.

  <p(245)>deno completions bash > /usr/local/etc/bash_completion.d/deno.bash</>
  <p(245)>source /usr/local/etc/bash_completion.d/deno.bash</>"
    ),
    UnstableArgsConfig::None,
  )
  .defer(|cmd| {
    cmd
      .disable_help_subcommand(true)
      .arg(
        Arg::new("shell")
          .value_parser(["bash", "fish", "powershell", "zsh", "fig"])
          .required_unless_present("help"),
      )
      .arg(
        Arg::new("dynamic")
          .long("dynamic")
          .action(ArgAction::SetTrue)
           .help("Generate dynamic completions for the given shell (unstable), currently this only provides available tasks for `deno task`."),
      )
  })
}

fn coverage_subcommand() -> Command {
  command(
    "coverage",
    cstr!("Print coverage reports from coverage profiles.

Collect a coverage profile with deno test:
  <p(245)>deno test --coverage=cov_profile</>

Print a report to stdout:
  <p(245)>deno coverage cov_profile</>

Include urls that start with the file schema and exclude files ending with <c>test.ts</> and <c>test.js</>,
for an url to match it must match the include pattern and not match the exclude pattern:
  <p(245)>deno coverage --include=\"^file:\" --exclude=\"test\\.(ts|js)\" cov_profile</>

Write a report using the lcov format:
  <p(245)>deno coverage --lcov --output=cov.lcov cov_profile/</>

Generate html reports from lcov:
  <p(245)>genhtml -o html_cov cov.lcov</>

<y>Read more:</> <c>https://docs.deno.com/go/coverage</>"),
    UnstableArgsConfig::None,
  )
  .defer(|cmd| {
    cmd
      .arg(
        Arg::new("ignore")
          .long("ignore")
          .num_args(1..)
          .action(ArgAction::Append)
          .require_equals(true)
          .help("Ignore coverage files")
          .value_hint(ValueHint::AnyPath),
      )
      .arg(
        Arg::new("include")
          .long("include")
          .num_args(1..)
          .action(ArgAction::Append)
          .value_name("regex")
          .require_equals(true)
          .default_value(r"^file:")
          .help("Include source files in the report"),
      )
      .arg(
        Arg::new("exclude")
          .long("exclude")
          .num_args(1..)
          .action(ArgAction::Append)
          .value_name("regex")
          .require_equals(true)
          .default_value(r"test\.(js|mjs|ts|jsx|tsx)$")
          .help("Exclude source files from the report"),
      )
      .arg(
        Arg::new("lcov")
          .long("lcov")
          .help("Output coverage report in lcov format")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("output")
          .requires("lcov")
          .long("output")
          .value_parser(value_parser!(String))
          .help(
            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),
      )
      .arg(
        Arg::new("html")
          .long("html")
          .help("Output coverage report in HTML format in the given directory")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("detailed")
          .long("detailed")
          .help("Output coverage report in detailed format in the terminal")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("threshold")
          .long("threshold")
          .value_name("PERCENT")
          .value_parser(value_parser!(u32).range(0..=100))
          .require_equals(true)
          .help(cstr!("Fail if coverage is below this percentage (0-100), applied to line, branch, and function coverage.
  <p(245)>Per-metric thresholds can be set in deno.json under \"coverage\": { \"thresholds\": { ... } }. The flag takes precedence.</>")),
      )
      .arg(
        Arg::new("files")
          .num_args(0..)
          .action(ArgAction::Append)
          .value_hint(ValueHint::AnyPath),
      )
  })
}

fn deploy_subcommand() -> Command {
  Command::new("deploy").arg(
    Arg::new("args")
      .num_args(0..)
      .action(ArgAction::Append)
      .trailing_var_arg(true)
      .allow_hyphen_values(true),
  )
}

fn sandbox_subcommand() -> Command {
  Command::new("sandbox").arg(
    Arg::new("args")
      .num_args(0..)
      .action(ArgAction::Append)
      .trailing_var_arg(true)
      .allow_hyphen_values(true),
  )
}

fn doc_subcommand() -> Command {
  command("doc",
      cstr!("Show documentation for a module.

Output documentation to standard output:
    <p(245)>deno doc ./path/to/module.ts</>

Output documentation in HTML format:
    <p(245)>deno doc --html --name=\"My library\" ./path/to/module.ts</>

Lint a module for documentation diagnostics:
    <p(245)>deno doc --lint ./path/to/module.ts</>

Target a specific symbol:
    <p(245)>deno doc ./path/to/module.ts MyClass.someField</>

Show documentation for runtime built-ins:
    <p(245)>deno doc</>
    <p(245)>deno doc --filter Deno.Listener</>

<y>Read more:</> <c>https://docs.deno.com/go/doc</>"),
          UnstableArgsConfig::ResolutionOnly
    )
    .defer(|cmd| {
      cmd
        .arg(import_map_arg())
        .arg(reload_arg())
        .args(lock_args())
        .arg(no_npm_arg())
        .arg(no_remote_arg())
        .arg(allow_import_arg())
        .arg(deny_import_arg())
        .arg(env_file_arg())
        .arg(
          Arg::new("json")
            .long("json")
            .help("Output documentation in JSON format")
            .action(ArgAction::SetTrue)
            .help_heading(DOC_HEADING),
        )
        .arg(
          Arg::new("html")
            .long("html")
            .help("Output documentation in HTML format")
            .action(ArgAction::SetTrue)
            .display_order(1000)
            .conflicts_with("json").help_heading(DOC_HEADING)
        )
        .arg(
          Arg::new("name")
            .long("name")
            .help("The name that will be used in the docs (ie for breadcrumbs)")
            .action(ArgAction::Set)
            .require_equals(true).help_heading(DOC_HEADING)
        )
        .arg(
          Arg::new("category-docs")
            .long("category-docs")
            .help("Path to a JSON file keyed by category and an optional value of a markdown doc")
            .requires("html")
            .action(ArgAction::Set)
            .require_equals(true).help_heading(DOC_HEADING)
        )
        .arg(
          Arg::new("symbol-redirect-map")
            .long("symbol-redirect-map")
            .help("Path to a JSON file keyed by file, with an inner map of symbol to an external link")
            .requires("html")
            .action(ArgAction::Set)
            .require_equals(true).help_heading(DOC_HEADING)
        )
        .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")
            .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")
            .requires("html")
            .action(ArgAction::Set)
            .require_equals(true).help_heading(DOC_HEADING)
        )
        .arg(
          Arg::new("output")
            .long("output")
            .help("Directory for HTML documentation output")
            .action(ArgAction::Set)
            .require_equals(true)
            .value_hint(ValueHint::DirPath)
            .value_parser(value_parser!(String)).help_heading(DOC_HEADING)
        )
        .arg(
          Arg::new("private")
            .long("private")
            .help("Output private documentation")
            .action(ArgAction::SetTrue).help_heading(DOC_HEADING),
        )
        .arg(
          Arg::new("filter")
            .long("filter")
            .help("Dot separated path to symbol")
            .conflicts_with("json")
            .conflicts_with("lint")
            .conflicts_with("html").help_heading(DOC_HEADING),
        )
        .arg(
          Arg::new("lint")
            .long("lint")
            .help("Output documentation diagnostics.")
            .action(ArgAction::SetTrue).help_heading(DOC_HEADING),
        )
        // TODO(nayeemrmn): Make `--builtin` a proper option. Blocked by
        // https://github.com/clap-rs/clap/issues/1794. Currently `--builtin` is
        // just a possible value of `source_file` so leading hyphens must be
        // enabled.
        .allow_hyphen_values(true)
        .arg(
          Arg::new("source_file")
            .num_args(1..)
            .action(ArgAction::Append)
            .value_hint(ValueHint::FilePath)
            .required_if_eq_any([("html", "true"), ("lint", "true")]),
        )
    })
}

fn eval_subcommand() -> Command {
  command(
    "eval",
    cstr!(
      "Evaluate JavaScript from the command line.
  <p(245)>deno eval \"console.log('hello world')\"</>

To evaluate as TypeScript:
  <p(245)>deno eval --ext=ts \"const v: string = 'hello'; console.log(v)\"</>

This command has implicit access to all permissions.

<y>Read more:</> <c>https://docs.deno.com/go/eval</>"
    ),
    UnstableArgsConfig::ResolutionAndRuntime,
  )
  .defer(|cmd| {
    cpu_prof_args(
      runtime_args(cmd, false, true, true)
        .arg(check_arg(false))
        .arg(executable_ext_arg())
        .arg(
          Arg::new("print")
            .long("print")
            .short('p')
            .help("print result to stdout")
            .action(ArgAction::SetTrue),
        )
        .arg(
          Arg::new("code_arg")
            .num_args(1..)
            .action(ArgAction::Append)
            .help("Code to evaluate")
            .value_name("CODE_ARG")
            .required_unless_present("help"),
        )
        .arg(env_file_arg()),
    )
  })
}

fn fmt_subcommand() -> Command {
  command(
    "fmt",
    cstr!("Auto-format various file types.
  <p(245)>deno fmt myfile1.ts myfile2.ts</>

Supported file types are:
  <p(245)>JavaScript, TypeScript, Markdown, JSON(C) and Jupyter Notebooks</>

Supported file types which are behind corresponding unstable flags (see formatting options):
  <p(245)>HTML, CSS, SCSS, LESS, YAML, Svelte, Vue, Astro and Angular</>

Format stdin and write to stdout:
  <p(245)>cat file.ts | deno fmt -</>

Check if the files are formatted:
  <p(245)>deno fmt --check</>

Ignore formatting code by preceding it with an ignore comment:
  <p(245)>// deno-fmt-ignore</>

Ignore formatting a file by adding an ignore comment at the top of the file:
  <p(245)>// deno-fmt-ignore-file</>

<y>Read more:</> <c>https://docs.deno.com/go/fmt</>"),
    UnstableArgsConfig::None,
  )
  .defer(|cmd| {
    cmd
      .arg(config_arg())
      .arg(no_config_arg())
      .arg(
         Arg::new("check")
          .long("check")
          .help("Check if the source files are formatted")
          .num_args(0)
          .help_heading(FMT_HEADING),
      )
      .arg(
        Arg::new("fail-fast")
          .long("fail-fast")
          .alias("failfast")
          .help("Stop checking files on first format error")
          .num_args(0)
          .requires("check")
          .help_heading(FMT_HEADING),
      )
      .arg(
        Arg::new("ext")
          .long("ext")
          .help("Set content type of the supplied file")
          .value_parser([
            "ts", "tsx", "js", "jsx", "mts", "mjs", "cts", "cjs", "md", "json", "jsonc", "css", "scss",
            "less", "html", "xml", "svg", "svelte", "vue", "astro", "yml", "yaml",
            "ipynb", "sql", "vto", "njk"
          ])
          .help_heading(FMT_HEADING).requires("files"),
      )
      .arg(
        Arg::new("ignore")
          .long("ignore")
          .num_args(1..)
          .action(ArgAction::Append)
          .require_equals(true)
          .help("Ignore formatting particular source files")
          .value_hint(ValueHint::AnyPath)
          .help_heading(FMT_HEADING),
      )
      .arg(
        Arg::new("files")
          .num_args(1..)
          .action(ArgAction::Append)
          .value_hint(ValueHint::AnyPath),
      )
      .arg(permit_no_files_arg())
      .arg(watch_arg(true))
      .arg(watch_exclude_arg())
      .arg(no_clear_screen_arg())
      .arg(
        Arg::new("use-tabs")
          .long("use-tabs")
          .alias("options-use-tabs")
          .num_args(0..=1)
          .value_parser(value_parser!(bool))
          .default_missing_value("true")
          .require_equals(true)
          .help(
          cstr!(  "Use tabs instead of spaces for indentation <p(245)>[default: false]</>"),
          )
          .help_heading(FMT_HEADING),
      )
      .arg(
        Arg::new("line-width")
          .long("line-width")
          .alias("options-line-width")
          .help(cstr!("Define maximum line width <p(245)>[default: 80]</>"))
          .value_parser(value_parser!(NonZeroU32))
          .help_heading(FMT_HEADING),
      )
      .arg(
        Arg::new("indent-width")
          .long("indent-width")
          .alias("options-indent-width")
          .help(cstr!("Define indentation width <p(245)>[default: 2]</>"))
          .value_parser(value_parser!(NonZeroU8))
          .help_heading(FMT_HEADING),
      )
      .arg(
        Arg::new("single-quote")
          .long("single-quote")
          .alias("options-single-quote")
          .num_args(0..=1)
          .value_parser(value_parser!(bool))
          .default_missing_value("true")
          .require_equals(true)
          .help(cstr!("Use single quotes <p(245)>[default: false]</>"))
          .help_heading(FMT_HEADING),
      )
      .arg(
        Arg::new("prose-wrap")
          .long("prose-wrap")
          .alias("options-prose-wrap")
          .value_parser(["always", "never", "preserve"])
          .help(cstr!("Define how prose should be wrapped <p(245)>[default: always]</>"))
          .help_heading(FMT_HEADING),
      )
      .arg(
        Arg::new("no-semicolons")
          .long("no-semicolons")
          .alias("options-no-semicolons")
          .num_args(0..=1)
          .value_parser(value_parser!(bool))
          .default_missing_value("true")
          .require_equals(true)
          .help(
           cstr!("Don't use semicolons except where necessary <p(245)>[default: false]</>"),
          )
          .help_heading(FMT_HEADING),
      )
      .arg(
        Arg::new("no-editorconfig")
          .long("no-editorconfig")
          .help(
           cstr!("Don't read .editorconfig files to infer formatting options <p(245)>[default: false]</>"),
          )
          .action(ArgAction::SetTrue)
          .help_heading(FMT_HEADING),
      )
      .arg(
        Arg::new("unstable-css")
          .long("unstable-css")
          .help("Enable formatting CSS, SCSS and Less files")
          .value_parser(FalseyValueParser::new())
          .action(ArgAction::SetTrue)
          .help_heading(FMT_HEADING)
          .hide(true),
      )
      .arg(
        Arg::new("unstable-html")
          .long("unstable-html")
          .help("Enable formatting HTML files")
          .value_parser(FalseyValueParser::new())
          .action(ArgAction::SetTrue)
          .help_heading(FMT_HEADING)
          .hide(true),
      )
      .arg(
        Arg::new("unstable-component")
          .long("unstable-component")
          .help("Enable formatting Svelte, Vue, Astro and Angular files")
          .value_parser(FalseyValueParser::new())
          .action(ArgAction::SetTrue)
          .help_heading(FMT_HEADING),
      )
      .arg(
        Arg::new("unstable-yaml")
          .long("unstable-yaml")
          .help("Enable formatting YAML files")
          .value_parser(FalseyValueParser::new())
          .action(ArgAction::SetTrue)
          .help_heading(FMT_HEADING)
          .hide(true),
      )
      .arg(
        Arg::new("unstable-sql")
        .long("unstable-sql")
        .help("Enable formatting SQL files.")
        .value_parser(FalseyValueParser::new())
        .action(ArgAction::SetTrue)
        .help_heading(FMT_HEADING),
      )
  })
}

fn init_subcommand() -> Command {
  command("init", "scaffolds a basic Deno project with a script, test, and configuration file", UnstableArgsConfig::None).defer(
    |cmd| {
      cmd
        .arg(Arg::new("args")
          .num_args(0..)
          .action(ArgAction::Append)
          .value_name("DIRECTORY OR PACKAGE")
          .trailing_var_arg(true)
        )
        .arg(
          Arg::new("npm")
            .long("npm")
            .help("Generate a npm create-* project")
            .conflicts_with_all(["lib", "serve", "empty", "jsr"])
            .action(ArgAction::SetTrue),
        )
        .arg(
          Arg::new("jsr")
            .long("jsr")
            .help("Generate a project from a JSR package")
            .conflicts_with_all(["lib", "serve", "empty", "npm"])
            .action(ArgAction::SetTrue),
        )
        .arg(
          Arg::new("lib")
            .long("lib")
            .help("Generate an example library project")
            .action(ArgAction::SetTrue),
        )
        .arg(
          Arg::new("serve")
            .long("serve")
            .help("Generate an example project for `deno serve`")
            .conflicts_with("lib")
            .action(ArgAction::SetTrue),
        )
        .arg(
          Arg::new("empty")
            .long("empty")
            .help("Generate a minimal project with just main.ts and deno.json")
            .conflicts_with_all(["lib", "serve", "npm", "jsr"])
            .action(ArgAction::SetTrue),
        )
        .arg(
          Arg::new("yes")
            .short('y')
            .long("yes")
            .help("Bypass the prompt and run with full permissions")
            .action(ArgAction::SetTrue),
        )
    },
  )
}

fn create_subcommand() -> Command {
  command(
    "create",
    "scaffolds a project from a package",
    UnstableArgsConfig::None,
  )
  .defer(|cmd| {
    cmd
      .arg(
        Arg::new("package")
          .required_unless_present("help")
          .value_name("PACKAGE"),
      )
      .arg(
        Arg::new("package_args")
          .num_args(0..)
          .action(ArgAction::Append)
          .value_name("ARGS")
          .last(true),
      )
      .arg(
        Arg::new("npm")
          .long("npm")
          .help("Treat unprefixed package names as npm packages")
          .conflicts_with("jsr")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("jsr")
          .long("jsr")
          .help("Treat unprefixed package names as JSR packages")
          .conflicts_with("npm")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("yes")
          .short('y')
          .long("yes")
          .help("Bypass the prompt and run with full permissions")
          .action(ArgAction::SetTrue),
      )
  })
}

fn info_subcommand() -> Command {
  command("info",
      cstr!("Show information about a module or the cache directories.

Get information about a module:
  <p(245)>deno info jsr:@std/http/file-server</>

The following information is shown:
  local: Local path of the file
  type: JavaScript, TypeScript, or JSON
  emit: Local path of compiled source code (TypeScript only)
  dependencies: Dependency tree of the source file

<y>Read more:</> <c>https://docs.deno.com/go/info</>"),
          UnstableArgsConfig::ResolutionOnly
    )
    .defer(|cmd| cmd
      .arg(Arg::new("file").value_hint(ValueHint::FilePath))
      .arg(reload_arg().requires("file"))
      .arg(ca_file_arg())
      .arg(unsafely_ignore_certificate_errors_arg())
      .arg(
        location_arg()
          .conflicts_with("file")
          .help(cstr!("Show files used for origin bound APIs like the Web Storage API when running a script with <c>--location=<<HREF>></>"))
      )
      .arg(no_check_arg().hide(true)) // TODO(lucacasonato): remove for 2.0
      .arg(no_config_arg())
      .arg(no_remote_arg())
      .arg(no_npm_arg())
      .args(lock_args())
      .arg(config_arg())
      .arg(import_map_arg())
      .arg(node_modules_dir_arg())
      .arg(node_modules_linker_arg())
      .arg(vendor_arg())
      .arg(
        Arg::new("json")
          .long("json")
          .help("UNSTABLE: Outputs the information in JSON format")
          .action(ArgAction::SetTrue),
      )
      .arg(min_dep_age_arg()))
      .arg(allow_import_arg())
      .arg(deny_import_arg())
      .arg(env_file_arg())
}

fn install_subcommand() -> Command {
  command("install", cstr!("Installs dependencies either in the local project or globally to a bin directory.

<g>Local installation</>

Add dependencies to the local project's configuration (<p(245)>deno.json / package.json</>) and installs them
in the package cache. If no dependency is specified, installs all dependencies listed in the config file.
If the <p(245)>--entrypoint</> flag is passed, installs the dependencies of the specified entrypoint(s).

  <p(245)>deno install</>
  <p(245)>deno install express</>
  <p(245)>deno install jsr:@std/bytes</>
  <p(245)>deno install --entrypoint entry1.ts entry2.ts</>

<g>Global installation</>

If the <bold>--global</> flag is set, installs a script as an executable in the installation root's bin directory.

  <p(245)>deno install --global --allow-net --allow-read jsr:@std/http/file-server</>
  <p(245)>deno install -g https://examples.deno.land/color-logging.ts</>

To change the executable name, use <c>-n</>/<c>--name</>:
  <p(245)>deno install -g --allow-net --allow-read -n serve jsr:@std/http/file-server</>

The executable name is inferred by default:
  - Attempt to take the file stem of the URL path. The above example would
    become <p(245)>file_server</>.
  - If the file stem is something generic like <p(245)>main</>, <p(245)>mod</>, <p(245)>index</> or <p(245)>cli</>,
    and the path has no parent, take the file name of the parent path. Otherwise
    settle with the generic name.
  - If the resulting name has an <p(245)>@...</> suffix, strip it.

To change the installation root, use <c>--root</>:
  <p(245)>deno install -g --allow-net --allow-read --root /usr/local jsr:@std/http/file-server</>

The installation root is determined, in order of precedence:
  - <p(245)>--root</> option
  - <p(245)>DENO_INSTALL_ROOT</> environment variable
  - <p(245)>$HOME/.deno</>

These must be added to the path manually if required."), UnstableArgsConfig::ResolutionAndRuntime)
    .visible_alias("i")
    .defer(|cmd| {
      permission_args(runtime_args(cmd, false, true, false), Some("global"))
        .arg(check_arg(true))
        .arg(allow_scripts_arg())
        .arg(
          Arg::new("cmd")
            .required_if_eq("global", "true")
            .required_if_eq("entrypoint", "true")
            .num_args(1..)
            .value_hint(ValueHint::FilePath),
        )
        .arg(script_arg().last(true))
        .arg(
          Arg::new("name")
            .long("name")
            .short('n')
            .requires("global")
            .help("Executable file name"),
        )
        .arg(
          Arg::new("root")
            .long("root")
            .requires("global")
            .help("Installation root")
            .value_hint(ValueHint::DirPath),
        )
        .arg(
          Arg::new("force")
            .long("force")
            .requires("global")
            .short('f')
            .help("Forcefully overwrite existing installation")
            .action(ArgAction::SetTrue),
        )
        .arg(
          Arg::new("compile")
            .long("compile")
            .requires("global")
            .help("Install the script as a compiled executable")
            .action(ArgAction::SetTrue),
        )
        .arg(
          Arg::new("global")
            .long("global")
            .short('g')
            .help("Install a package or script as a globally available executable")
            .action(ArgAction::SetTrue),
        )
        .arg(
          Arg::new("entrypoint")
            .long("entrypoint")
            .short('e')
            .conflicts_with("global")
            .action(ArgAction::SetTrue)
            .help("Install dependents of the specified entrypoint(s)"),
        )
        .arg(env_file_arg())
        .arg(add_dev_arg().conflicts_with("entrypoint").conflicts_with("global"))
        .arg(add_optional_arg().conflicts_with("entrypoint").conflicts_with("global"))
        .arg(add_no_save_arg().conflicts_with("entrypoint").conflicts_with("global"))
        .args(default_registry_args().into_iter().map(|arg| arg.conflicts_with("entrypoint").conflicts_with("global")))
        .arg(
          Arg::new("save-exact")
            .long("save-exact")
            .alias("exact")
            .help("Save exact version without the caret (^)")
            .action(ArgAction::SetTrue)
            .conflicts_with("entrypoint")
            .conflicts_with("global"),
        )
        .arg(add_unscoped_arg().conflicts_with("entrypoint").conflicts_with("global"))
        .arg(lockfile_only_arg().conflicts_with("global"))
        .arg(package_json_arg().conflicts_with("entrypoint").conflicts_with("global"))
        .arg(
          Arg::new("os")
            .long("os")
            .conflicts_with("global")
            .help("Target OS for npm package installation (e.g., linux, darwin, win32)")
            .value_parser(["aix", "android", "darwin", "freebsd", "linux", "openbsd", "sunos", "win32"]),
        )
        .arg(
          Arg::new("arch")
            .long("arch")
            .conflicts_with("global")
            .help("Target architecture for npm package installation (e.g., x64, arm64)")
            .value_parser(["arm", "arm64", "ia32", "mips", "mipsel", "ppc", "ppc64", "s390", "s390x", "x64"]),
        )
        .arg(
          Arg::new("prod")
            .long("prod")
            .alias("production")
            .help("Only install production dependencies (excludes devDependencies)")
            .action(ArgAction::SetTrue)
            .conflicts_with("global")
            .conflicts_with("dev"),
        )
        .arg(
          Arg::new("skip-types")
            .long("skip-types")
            .help(cstr!("Exclude @types/* packages from installation.
<y>Be careful, as it uses a name-based heuristic and may skip packages that ship runtime code.</>"))
            .action(ArgAction::SetTrue)
            .requires("prod"),
        )
    })
}

fn ci_subcommand() -> Command {
  command(
    "ci",
    cstr!("Install dependencies in a clean, reproducible way for CI environments.

Similar to <p(245)>npm ci</>: requires a <p(245)>deno.lock</> file, removes any existing <p(245)>node_modules</>
directory, and then installs strictly from the lockfile. Errors if <p(245)>deno.lock</>
is missing or out of date with the config file.

  <p(245)>deno ci</>
  <p(245)>deno ci --prod</>"),
    UnstableArgsConfig::ResolutionAndRuntime,
  )
  .defer(|cmd| {
    cmd
      .arg(
        Arg::new("prod")
          .long("prod")
          .alias("production")
          .help("Only install production dependencies (excludes devDependencies)")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("skip-types")
          .long("skip-types")
          .help(cstr!("Exclude @types/* packages from installation.
<y>Be careful, as it uses a name-based heuristic and may skip packages that ship runtime code.</>"))
          .action(ArgAction::SetTrue)
          .requires("prod"),
      )
      .arg(env_file_arg())
  })
}

fn lockfile_only_arg() -> Arg {
  Arg::new("lockfile-only")
    .long("lockfile-only")
    .action(ArgAction::SetTrue)
    .help("Install only updating the lockfile")
}

fn package_json_arg() -> Arg {
  Arg::new("package-json")
    .long("package-json")
    .action(ArgAction::SetTrue)
    .help(
      "Force using package.json for dependency management instead of deno.json",
    )
}

fn json_reference_subcommand() -> Command {
  Command::new("json_reference").hide(true)
}

fn jupyter_subcommand() -> Command {
  command("jupyter", "Deno kernel for Jupyter notebooks", UnstableArgsConfig::None)
    .arg(
      Arg::new("install")
        .long("install")
        .help("Install a kernelspec")
        .conflicts_with("kernel")
        .action(ArgAction::SetTrue)
    )
    .arg(
      Arg::new("name")
        .long("name")
        .short('n')
        .help(cstr!("Set a name for the kernel (defaults to 'deno'). <p(245)>Useful when maintaing multiple Deno kernels.</>"))
        .value_parser(value_parser!(String))
        .conflicts_with("kernel")
    )
    .arg(
      Arg::new("display")
        .long("display")
        .short('d')
        .help(cstr!("Set a display name for the kernel (defaults to 'Deno'). <p(245)>Useful when maintaing multiple Deno kernels.</>"))
        .value_parser(value_parser!(String))
        .requires("install")
    )
    .arg(
      Arg::new("force")
        .long("force")
        .help("Force installation of a kernel, overwriting previously existing kernelspec")
        .requires("install")
        .action(ArgAction::SetTrue)
    )
    .arg(
      Arg::new("kernel")
        .long("kernel")
        .help("Start the kernel")
        .conflicts_with("install")
        .requires("conn")
        .action(ArgAction::SetTrue)
    )
    .arg(
      Arg::new("conn")
        .long("conn")
        .help("Path to JSON file describing connection parameters, provided by Jupyter")
        .value_parser(value_parser!(String))
        .value_hint(ValueHint::FilePath)
        .conflicts_with("install"))
}

fn update_and_outdated_args() -> [Arg; 6] {
  [
    Arg::new("filters")
      .num_args(0..)
      .action(ArgAction::Append)
      .help(concat!("Filters selecting which packages to act on. Can include wildcards (*) to match multiple packages. ",
                  "If a version requirement is specified, the matching packages will be updated to the given requirement."),
      ),
    Arg::new("latest")
      .long("latest")
      .action(ArgAction::SetTrue)
      .help(
        "Consider the latest version, regardless of semver constraints",
      )
      .conflicts_with("compatible"),
    Arg::new("compatible")
      .long("compatible")
      .action(ArgAction::SetTrue)
      .help("Only consider versions that satisfy semver requirements"),
    Arg::new("recursive")
      .long("recursive")
      .short('r')
      .action(ArgAction::SetTrue)
      .help("Include all workspace members"),
    min_dep_age_arg(),
    lockfile_only_arg(),
  ]
}

fn update_subcommand() -> Command {
  command(
    "update",
    cstr!("Update outdated dependencies.

Update dependencies to the latest semver compatible versions:
  <p(245)>deno update</>
Update dependencies to the latest versions, ignoring semver requirements:
  <p(245)>deno update --latest</>
Update dependencies within their existing version ranges, without editing deno.json / package.json (like <p(245)>npm update</>):
  <p(245)>deno update --lockfile-only</>

<i>This command is an alias of <p(245)>deno outdated --update</></>

Filters can be used to select which packages to act on. Filters can include wildcards (*) to match multiple packages.
  <p(245)>deno update --latest \"@std/*\"</>
  <p(245)>deno update --latest \"react*\"</>
Note that filters act on their aliases configured in deno.json / package.json, not the actual package names:
  Given \"foobar\": \"npm:react@17.0.0\" in deno.json or package.json, the filter \"foobar\" would update npm:react to
  the latest version.
  <p(245)>deno update --latest foobar</>
Filters can be combined, and negative filters can be used to exclude results:
  <p(245)>deno update --latest \"@std/*\" \"!@std/fmt*\"</>

Specific version requirements to update to can be specified:
  <p(245)>deno update @std/fmt@^1.0.2</>
"),
    UnstableArgsConfig::None,
  )
  .defer(|cmd| {
    cmd
      .args(update_and_outdated_args())
      .arg(
        Arg::new("interactive")
          .long("interactive")
          .short('i')
          .action(ArgAction::SetTrue)
          .help("Interactively select which dependencies to update")
      )
      .args(lock_args())
      .arg(env_file_arg())
  })
}

fn outdated_subcommand() -> Command {
  command(
    "outdated",
    cstr!("Find and update outdated dependencies.
By default, outdated dependencies are only displayed.

Display outdated dependencies:
  <p(245)>deno outdated</>
  <p(245)>deno outdated --compatible</>

Update dependencies to the latest semver compatible versions:
  <p(245)>deno outdated --update</>
Update dependencies to the latest versions, ignoring semver requirements:
  <p(245)>deno outdated --update --latest</>
Update dependencies within their existing version ranges, without editing deno.json / package.json (like <p(245)>npm update</>):
  <p(245)>deno outdated --update --lockfile-only</>

Filters can be used to select which packages to act on. Filters can include wildcards (*) to match multiple packages.
  <p(245)>deno outdated --update --latest \"@std/*\"</>
  <p(245)>deno outdated --update --latest \"react*\"</>
Note that filters act on their aliases configured in deno.json / package.json, not the actual package names:
  Given \"foobar\": \"npm:react@17.0.0\" in deno.json or package.json, the filter \"foobar\" would update npm:react to
  the latest version.
  <p(245)>deno outdated --update --latest foobar</>
Filters can be combined, and negative filters can be used to exclude results:
  <p(245)>deno outdated --update --latest \"@std/*\" \"!@std/fmt*\"</>

Specific version requirements to update to can be specified:
  <p(245)>deno outdated --update @std/fmt@^1.0.2</>
"),
    UnstableArgsConfig::None,
  )
  .defer(|cmd| {
    cmd
      .args(update_and_outdated_args())
      .arg(
        Arg::new("interactive")
          .long("interactive")
          .short('i')
          .requires("update")
          .action(ArgAction::SetTrue)
          .help("Interactively select which dependencies to update"),
      )
      .args(lock_args())
      .arg(env_file_arg())
      .arg(
        Arg::new("update")
          .long("update")
          .short('u')
          .action(ArgAction::SetTrue)
          .help("Update dependency versions"),
      )
  })
}

fn list_subcommand() -> Command {
  command(
    "list",
    cstr!("List the dependencies declared in deno.json / package.json.

Show declared dependencies and their resolved versions:
  <p(245)>deno list</>

Show the dependency tree two levels deep:
  <p(245)>deno list --depth 2</>

Show only production or only development dependencies:
  <p(245)>deno list --prod</>
  <p(245)>deno list --dev</>

Filter by name (wildcards allowed, negate with a leading '!'):
  <p(245)>deno list \"@std/*\"</>
  <p(245)>deno list \"react*\" \"!react-dom\"</>

Include all workspace members:
  <p(245)>deno list --recursive</>

Unlike <p(245)>deno info</>, which walks the module graph from an entrypoint, this lists the
packages a project declares as dependencies, similar to <p(245)>npm ls</> / <p(245)>pnpm list</>."),
    UnstableArgsConfig::None,
  )
  .defer(|cmd| {
    cmd
      .arg(
        Arg::new("filters")
          .num_args(0..)
          .action(ArgAction::Append)
          .help(
            "Filters selecting which packages to list. Can include wildcards \
             (*) to match multiple packages, and a leading '!' to exclude.",
          ),
      )
      .arg(
        Arg::new("depth")
          .long("depth")
          .value_parser(value_parser!(u16))
          .help(
            "Maximum depth of the dependency tree to display (0 = direct \
             dependencies only)",
          ),
      )
      .arg(
        Arg::new("prod")
          .long("prod")
          .action(ArgAction::SetTrue)
          .conflicts_with("dev")
          .help("Only list production dependencies"),
      )
      .arg(
        Arg::new("dev")
          .long("dev")
          .action(ArgAction::SetTrue)
          .help("Only list development dependencies"),
      )
      .arg(
        Arg::new("recursive")
          .long("recursive")
          .short('r')
          .action(ArgAction::SetTrue)
          .help("Include all workspace members"),
      )
  })
}

fn uninstall_subcommand() -> Command {
  command(
    "uninstall",
    cstr!("Uninstalls dependencies or an executable script in the installation root's bin directory.
  <p(245)>deno uninstall @std/dotenv chalk</>
  <p(245)>deno uninstall --global file_server</>

To change the installation root, use <c>--root</> flag:
  <p(245)>deno uninstall --global --root /usr/local cowsay serve</>

The installation root is determined, in order of precedence:
  - <p(245)>--root</> option
  - <p(245)>DENO_INSTALL_ROOT</> environment variable
  - <p(245)>$HOME/.deno</>"),
    UnstableArgsConfig::None,
  )
  .defer(|cmd| {
    cmd
      .arg(Arg::new("name-or-package").required_unless_present("help"))
      .arg(
        Arg::new("root")
          .long("root")
          .help("Installation root")
          .requires("global")
          .value_hint(ValueHint::DirPath),
      )
      .arg(
        Arg::new("global")
          .long("global")
          .short('g')
          .help("Remove globally installed packages or modules")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("additional-packages")
          .help("List of additional packages to remove")
          .num_args(1..)
          .action(ArgAction::Append)
      )
      .args(lock_args())
      .arg(lockfile_only_arg())
      .arg(package_json_arg().conflicts_with("global"))
  })
}

fn deno_x_shim_name_parser(value: &str) -> Result<DenoXShimName, String> {
  match value {
    "dx" => Ok(DenoXShimName::Dx),
    "denox" => Ok(DenoXShimName::Denox),
    "dnx" => Ok(DenoXShimName::Dnx),
    _ => Ok(DenoXShimName::Other(value.to_string())),
  }
}

fn x_subcommand() -> Command {
  command(
    "x",
    cstr!("Execute a binary from npm or jsr, like npx"),
    UnstableArgsConfig::ResolutionAndRuntime,
  )
  .defer(|cmd| {
    runtime_args(cmd, true, true, true)
      .arg(script_arg().trailing_var_arg(true))
      .arg(
        Arg::new("yes")
          .long("yes")
          .short('y')
          .help("Assume confirmation for all prompts")
          .action(ArgAction::SetTrue)
          .conflicts_with("install-alias"),
      )
      .arg(
        ignore_scripts_arg().conflicts_with("install-alias"),
      )
      .arg(
        Arg::new("package")
          .long("package")
          .short('p')
          .help("Package to install (use when the binary name differs from the package name)")
          .num_args(1)
          .conflicts_with("install-alias"),
      )
      .arg(check_arg(false))
      .arg(env_file_arg())
      .arg(
        Arg::new("install-alias")
          .long("install-alias")
          .help("Creates a dx alias so you can run dx <command> instead of deno x <command>")
          .num_args(0..=1)
          .default_missing_value("dx")
          .value_parser(deno_x_shim_name_parser)
          .action(ArgAction::Set)
          .conflicts_with("script_arg"),
      )
  })
}

fn lsp_subcommand() -> Command {
  Command::new("lsp").about(
    "The 'deno lsp' subcommand provides a way for code editors and IDEs to interact with Deno
using the Language Server Protocol. Usually humans do not use this subcommand directly.
For example, 'deno lsp' can provide IDEs with go-to-definition support and automatic code formatting.

How to connect various editors and IDEs to 'deno lsp': https://docs.deno.com/go/lsp",
  )
}

fn lint_subcommand() -> Command {
  command(
    "lint",
    cstr!("Lint JavaScript/TypeScript source code.

  <p(245)>deno lint</>
  <p(245)>deno lint myfile1.ts myfile2.js</>

Print result as JSON:
  <p(245)>deno lint --json</>

Read from stdin:
  <p(245)>cat file.ts | deno lint -</>
  <p(245)>cat file.ts | deno lint --json -</>

List available rules:
  <p(245)>deno lint --rules</>

To ignore specific diagnostics, you can write an ignore comment on the preceding line with a rule name (or multiple):
  <p(245)>// deno-lint-ignore no-explicit-any</>
  <p(245)>// deno-lint-ignore require-await no-empty</>

To ignore linting on an entire file, you can add an ignore comment at the top of the file:
  <p(245)>// deno-lint-ignore-file</>

<y>Read more:</> <c>https://docs.deno.com/go/lint</>
"),
    UnstableArgsConfig::ResolutionOnly,
  )
  .defer(|cmd| {
    cmd
      .arg(
        Arg::new("fix")
          .long("fix")
          .help("Fix any linting errors for rules that support it")
          .action(ArgAction::SetTrue)
          .help_heading(LINT_HEADING),
      )
      .arg(
            Arg::new("ext")
                .long("ext")
                .require_equals(true)
                .value_name("EXT")
                .help("Specify the file extension to lint when reading from stdin.\
  For example, use `jsx` to lint JSX files or `tsx` for TSX files.\
  This argument is necessary because stdin input does not automatically infer the file type.\
  Example usage: `cat file.jsx | deno lint - --ext=jsx`."),
        )
        .arg(
        Arg::new("rules")
          .long("rules")
          .help("List available rules")
          .action(ArgAction::SetTrue)
          .help_heading(LINT_HEADING),
      )
      .arg(
        Arg::new("rules-tags")
          .long("rules-tags")
          .require_equals(true)
          .num_args(1..)
          .action(ArgAction::Append)
          .use_value_delimiter(true)
          .help("Use set of rules with a tag")
          .help_heading(LINT_HEADING),
      )
      .arg(
        Arg::new("rules-include")
          .long("rules-include")
          .require_equals(true)
          .num_args(1..)
          .use_value_delimiter(true)
          .conflicts_with("rules")
          .help("Include lint rules")
          .help_heading(LINT_HEADING),
      )
      .arg(
        Arg::new("rules-exclude")
          .long("rules-exclude")
          .require_equals(true)
          .num_args(1..)
          .use_value_delimiter(true)
          .conflicts_with("rules")
          .help("Exclude lint rules")
          .help_heading(LINT_HEADING),
      )
      .arg(no_config_arg())
      .arg(config_arg())
      .arg(
        Arg::new("ignore")
          .long("ignore")
          .num_args(1..)
          .action(ArgAction::Append)
          .require_equals(true)
          .help("Ignore linting particular source files")
          .value_hint(ValueHint::AnyPath)
          .help_heading(LINT_HEADING),
      )
      .arg(
        Arg::new("json")
          .long("json")
          .help("Output lint result in JSON format")
          .action(ArgAction::SetTrue)
          .help_heading(LINT_HEADING),
      )
      .arg(
        Arg::new("compact")
          .long("compact")
          .help("Output lint result in compact format")
          .action(ArgAction::SetTrue)
          .conflicts_with("json")
          .help_heading(LINT_HEADING),
      )
      .arg(
        Arg::new("files")
          .num_args(1..)
          .action(ArgAction::Append)
          .value_hint(ValueHint::AnyPath),
      )
      .arg(permit_no_files_arg())
      .arg(watch_arg(true))
      .arg(watch_exclude_arg())
      .arg(no_clear_screen_arg())
      .arg(allow_import_arg())
      .arg(deny_import_arg())
  })
}

fn repl_subcommand() -> Command {
  command("repl", cstr!(
    "Starts a read-eval-print-loop, which lets you interactively build up program state in the global context.
It is especially useful for quick prototyping and checking snippets of code.

TypeScript is supported, however it is not type-checked, only transpiled."
  ), UnstableArgsConfig::ResolutionAndRuntime)
    .defer(|cmd| {
      let cmd = compile_args_without_check_args(cmd);
      let cmd = inspect_args(cmd);
      let cmd = permission_args(cmd, None);
      let cmd = runtime_misc_args(cmd);

      cmd
      .arg(
        Arg::new("eval-file")
          .long("eval-file")
          .num_args(1..)
          .action(ArgAction::Append)
          .use_value_delimiter(true)
          .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")
          .value_name("code"),
      )
      .arg(Arg::new("json").long("json").action(ArgAction::SetTrue).hide(true))
      .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)>[default: $DENO_DIR/deno_history.txt]</>"))
    })
    .arg(env_file_arg())
    .arg(
      Arg::new("args")
        .num_args(0..)
        .action(ArgAction::Append)
        .value_name("ARGS")
        .last(true)
    )
}

fn run_args(command: Command, top_level: bool) -> Command {
  cpu_prof_args(
    runtime_args(command, true, true, true)
      .arg(check_arg(false))
      .arg(watch_arg(true))
      .arg(hmr_arg(true))
      .arg(watch_exclude_arg())
      .arg(no_clear_screen_arg())
      .arg(executable_ext_arg())
      .arg(if top_level {
        script_arg().trailing_var_arg(true).hide(true)
      } else {
        script_arg().trailing_var_arg(true)
      })
      .arg(env_file_arg())
      .arg(no_code_cache_arg())
      .arg(coverage_arg()),
  )
  .arg(tunnel_arg())
  .arg(use_env_proxy_arg())
  .arg(no_use_env_proxy_arg())
}

#[cfg(test)]
thread_local! {
  static TEST_NODE_OPTIONS: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) };
}

/// Whether `word` is a `--inspect`, `--inspect-brk`, or `--inspect-wait` flag
/// (with or without a `=value` suffix), as it might appear in `NODE_OPTIONS`.
/// Note `--inspect-publish-uid` is intentionally excluded here; it's handled
/// separately.
fn is_inspect_node_option(word: &str) -> bool {
  ["--inspect", "--inspect-brk", "--inspect-wait"]
    .iter()
    .any(|name| {
      word == *name
        || word
          .strip_prefix(name)
          .is_some_and(|rest| rest.starts_with('='))
    })
}

/// Reads some flags from NODE_OPTIONS:
/// https://nodejs.org/api/cli.html#node_optionsoptions
/// Currently supports:
/// - `--require` / `-r`
/// - `--inspect` / `--inspect-brk` / `--inspect-wait`
/// - `--inspect-publish-uid`
fn apply_node_options(flags: &mut Flags) {
  let node_options = match std::env::var("NODE_OPTIONS") {
    Ok(val) if !val.is_empty() => val,
    _ => {
      #[cfg(test)]
      {
        match TEST_NODE_OPTIONS.with(|opt| opt.borrow().clone()) {
          Some(val) if !val.is_empty() => val,
          _ => return,
        }
      }
      #[cfg(not(test))]
      return;
    }
  };

  // Filter out unsupported flags, since they can consume the supported flags as
  // values.
  let args = parse_node_options_env_var(&node_options).unwrap_or_default();
  let args = args
    .iter()
    .map(String::as_str)
    .scan(false, |prev_was_require, word| {
      if word == "--require" || word == "-r" {
        *prev_was_require = true;
        return Some((word, true));
      }
      let keep = *prev_was_require
        || word.starts_with("--inspect-publish-uid=")
        || is_inspect_node_option(word);
      *prev_was_require = false;
      Some((word, keep))
    })
    .filter_map(|(word, should_keep)| should_keep.then_some(word));

  let cmd = inspect_args(
    Command::new("node-options-parser").arg(require_arg().short('r')),
  )
  .ignore_errors(true);
  let mut matches =
    cmd.get_matches_from(std::iter::once("node-options-parser").chain(args));

  if let Some(require_values) = matches.remove_many::<String>("require") {
    let mut merged_require: Vec<String> = require_values.collect();
    merged_require.append(&mut flags.require);
    flags.require = merged_require;
  }

  // `--inspect`, `--inspect-brk`, and `--inspect-wait` are mutually exclusive,
  // so only apply them from NODE_OPTIONS if the user didn't pass any inspector
  // flag on the command line. An explicit CLI flag takes precedence over the
  // whole NODE_OPTIONS inspector family.
  if flags.inspect.is_none()
    && flags.inspect_brk.is_none()
    && flags.inspect_wait.is_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");
  }
  if flags.inspect_publish_uid.is_none() {
    flags.inspect_publish_uid =
      matches.remove_one::<InspectPublishUid>("inspect-publish-uid");
  }
}

fn run_subcommand() -> Command {
  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://docs.deno.com/hello_world.ts</>

Grant permission to read from disk and listen to network:
  <p(245)>deno run --allow-read --allow-net jsr:@std/http/file-server</>

Grant permission to read allow-listed files from disk:
  <p(245)>deno run --allow-read=/etc jsr:@std/http/file-server</>

Grant all permissions:
  <p(245)>deno run -A jsr:@std/http/file-server</>

Specifying the filename '-' to read the file from stdin.
  <p(245)>curl https://docs.deno.com/hello_world.ts | deno run -</>

<y>Read more:</> <c>https://docs.deno.com/go/run</>"), UnstableArgsConfig::ResolutionAndRuntime), false)
}

fn watch_subcommand() -> Command {
  run_args(command("watch", cstr!("Run a JavaScript or TypeScript program, watching for file changes and hot-replacing modules.

This is an alias for <c>deno run --watch-hmr</>. The process restarts if hot replacement fails.
  <p(245)>deno watch main.ts</>

Local files from the entry point module graph are watched by default. Additional paths can be passed with <c>--watch-hmr</>:
  <p(245)>deno watch --watch-hmr=./templates main.ts</>

<y>Read more:</> <c>https://docs.deno.com/go/run</>"), UnstableArgsConfig::ResolutionAndRuntime), false)
}

fn serve_host_validator(host: &str) -> Result<String, String> {
  if Url::parse(&format!("internal://{host}:9999")).is_ok() {
    Ok(host.to_owned())
  } else {
    Err(format!("Bad serve host: {host}"))
  }
}

fn serve_subcommand() -> Command {
  cpu_prof_args(
    runtime_args(command("serve", cstr!("Run a server defined in a main module

The serve command uses the default exports of the main module to determine which servers to start.

Start a server defined in server.ts:
  <p(245)>deno serve server.ts</>

Start a server defined in server.ts, watching for changes and running on port 5050:
  <p(245)>deno serve --watch --port 5050 server.ts</>

<y>Read more:</> <c>https://docs.deno.com/go/serve</>"), UnstableArgsConfig::ResolutionAndRuntime), true, true, true)
    .arg(
      Arg::new("port")
        .long("port")
        .help(cstr!("The TCP port to serve on. Pass 0 to pick a random free port <p(245)>[default: 8000]</>"))
        .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)")
        .value_parser(serve_host_validator),
    ).arg(
      Arg::new("open")
      .long("open")
      .help("Open the browser on the address that the server is running on.")
      .action(ArgAction::SetTrue)
    )
    .arg(
      parallel_arg("multiple server workers")
    )
    .arg(check_arg(false))
    .arg(watch_arg(true))
    .arg(hmr_arg(true))
    .arg(watch_exclude_arg())
    .arg(no_clear_screen_arg())
    .arg(executable_ext_arg())
    .arg(
      script_arg()
        .required_unless_present_any(["help", "v8-flags"])
        .trailing_var_arg(true),
    )
    .arg(env_file_arg())
    .arg(no_code_cache_arg()),
  )
  .arg(tunnel_arg())
}

fn task_subcommand() -> Command {
  command(
    "task",
    cstr!(
      "Run a task defined in the configuration file:
  <p(245)>deno task build</>

List all available tasks (from config files in the current and ancestor directories):
  <p(245)>deno task</>

Evaluate a task from string:
  <p(245)>deno task --eval \"echo $(pwd)\"</>"
    ),
    UnstableArgsConfig::ResolutionAndRuntime,
  )
  .defer(|cmd| {
    cmd
      .add(SubcommandCandidates::new(complete_available_tasks))
      .allow_external_subcommands(true)
      .subcommand_value_name("TASK")
      .arg(config_arg())
      .args(lock_args())
      .arg(
        Arg::new("cwd")
          .long("cwd")
          .value_name("DIR")
          .help("Specify the directory to run the task in")
          .value_hint(ValueHint::DirPath),
      )
      .arg(
        Arg::new("recursive")
          .long("recursive")
          .short('r')
          .help("Run the task in all projects in the workspace")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("members")
          .long("members")
          .help("Run the task in all workspace members, but not in the workspace root")
          .action(ArgAction::SetTrue)
          .conflicts_with_all(["recursive", "filter"]),
      )
      .arg(
        Arg::new("filter")
        .long("filter")
        .short('f')
        .help("Filter members of the workspace by name, implies --recursive flag")
        .value_parser(value_parser!(String)),
      )
      .arg(
        Arg::new("eval")
          .long("eval")
          .help(
            "Evaluate the passed value as if it was a task in a configuration file",
          ).action(ArgAction::SetTrue)
      )
      .arg(
        Arg::new("no-prefix")
          .long("no-prefix")
          .help(
            "Disable prefixing the output of concurrently-executing tasks with the task name",
          )
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("jobs")
          .long("jobs")
          .short('j')
          .visible_alias("concurrency")
          .help(
            "Maximum number of tasks to run concurrently.
Overrides the DENO_JOBS environment variable; defaults to the number of
available CPUs. Use 1 to force sequential execution. Only affects runs
where multiple tasks can run concurrently (workspace runs, or a task with
parallelizable dependencies)",
          )
          .value_name("NUMBER")
          .value_parser(value_parser!(NonZeroUsize)),
      )
      .arg(
        Arg::new("if-present")
          .long("if-present")
          .help(
            "Exit with code 0 instead of an error when the task is not found",
          )
          .action(ArgAction::SetTrue),
      )
      .arg(env_file_arg())
      .arg(node_modules_dir_arg())
      .arg(node_modules_linker_arg())
      .arg(tunnel_arg())
  })
}

// This is used to pass the parsed flags to the completion function. This is so
// we can take into account things like the `--config` flag. We could also take into account
// `--recursive` or `--filter` in the future.
// The completion function can't take any args, so we use a static instead.
// This code will only run if we are actually running the completion code.
static TASK_FLAGS_FOR_COMPLETION: LazyLock<Option<Flags>> =
  LazyLock::new(|| {
    let mut flags = Flags::default();
    let args = std::env::args_os().skip(2);
    let app = clap_root().ignore_errors(true);
    let Ok(mut matches) = app.clone().try_get_matches_from(args) else {
      return None;
    };
    match matches.remove_subcommand() {
      Some((sub, mut matches)) => {
        if sub == "task" {
          let _ = task_parse(&mut flags, &mut matches, app);
          Some(flags)
        } else {
          None
        }
      }
      None => None,
    }
  });

fn complete_available_tasks_inner() -> Result<Vec<CompletionCandidate>, AnyError>
{
  let parsed_flags = TASK_FLAGS_FOR_COMPLETION.clone();

  let flags = parsed_flags.unwrap_or_default();

  let completions = crate::tools::task::get_available_tasks_for_completion(
    std::sync::Arc::new(flags),
  )?;

  Ok(
    completions
      .into_iter()
      .map(|c| {
        let mut candidate = CompletionCandidate::new(c.name);
        if let Some(description) = c.task.description {
          candidate = candidate.help(Some(description.into()));
        }
        candidate
      })
      .collect(),
  )
}

fn complete_available_tasks() -> Vec<CompletionCandidate> {
  match complete_available_tasks_inner() {
    Ok(candidates) => candidates,
    Err(e) => {
      log::debug!("Error during available tasks completion: {e}");
      vec![]
    }
  }
}

/// Parses a `--shard` value of the form `<index>/<count>` (1-based index).
fn parse_test_shard(value: &str) -> Result<(usize, usize), String> {
  let (index, count) = value.split_once('/').ok_or_else(|| {
    format!("expected format <index>/<count>, but got '{value}'")
  })?;
  let index: usize = index
    .parse()
    .map_err(|_| format!("invalid shard index '{index}'"))?;
  let count: usize = count
    .parse()
    .map_err(|_| format!("invalid shard count '{count}'"))?;
  if count == 0 {
    return Err("shard count must be greater than 0".to_string());
  }
  if index == 0 || index > count {
    return Err(format!("shard index must be between 1 and {count}"));
  }
  Ok((index, count))
}

fn test_subcommand() -> Command {
  command("test",
      cstr!("Run tests using Deno's built-in test runner.

Evaluate the given modules, run all tests declared with <bold>Deno.</><y>test()</> and report results to standard output:
  <p(245)>deno test src/fetch_test.ts src/signal_test.ts</>

Directory arguments are expanded to all contained files matching the glob <c>{*_,*.,}test.{js,mjs,ts,mts,jsx,tsx}</>
or <c>**/__tests__/**</>:
 <p(245)>deno test src/</>

<y>Read more:</> <c>https://docs.deno.com/go/test</>"),
          UnstableArgsConfig::ResolutionAndRuntime
    )
    .defer(|cmd|
      runtime_args(cmd, true, true, true)
      .arg(check_arg(true))
      .arg(
        Arg::new("ignore")
          .long("ignore")
          .num_args(1..)
          .action(ArgAction::Append)
          .require_equals(true)
          .help("Ignore files")
          .value_hint(ValueHint::AnyPath),
      )
      .arg(
        Arg::new("no-run")
          .long("no-run")
          .help("Cache test modules, but don't run tests")
          .action(ArgAction::SetTrue)
          .help_heading(TEST_HEADING),
      )
      .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")
          .action(ArgAction::SetTrue)
          .help_heading(TEST_HEADING),
      )
      .arg(
        Arg::new("sanitize-ops")
          .long("sanitize-ops")
          .help("Enable the ops sanitizer, which ensures that all async ops started in a test are completed before the test ends")
          .action(ArgAction::SetTrue)
          .help_heading(TEST_HEADING),
      )
      .arg(
        Arg::new("sanitize-resources")
          .long("sanitize-resources")
          .help("Enable the resources sanitizer, which ensures that all resources opened in a test are closed before the test ends")
          .action(ArgAction::SetTrue)
          .help_heading(TEST_HEADING),
      )
      .arg(
        Arg::new("doc")
          .long("doc")
          .help("Evaluate code blocks in JSDoc and Markdown")
          .action(ArgAction::SetTrue)
          .help_heading(TEST_HEADING),
      )
      .arg(
        Arg::new("fail-fast")
          .long("fail-fast")
          .alias("failfast")
          .help("Stop after N errors. Defaults to stopping after first failure")
          .num_args(0..=1)
          .require_equals(true)
          .value_name("N")
          .value_parser(value_parser!(NonZeroUsize))
          .help_heading(TEST_HEADING))
      .arg(permit_no_files_arg().help_heading(TEST_HEADING))
      .arg(
        Arg::new("filter")
          .allow_hyphen_values(true)
          .long("filter")
          .help("Run tests with this string or regexp pattern in the test name")
          .help_heading(TEST_HEADING),
      )
      .arg(
        Arg::new("shuffle")
          .long("shuffle")
          .value_name("NUMBER")
          .help("Shuffle the order in which the tests are run")
          .num_args(0..=1)
          .require_equals(true)
          .value_parser(value_parser!(u64))
          .help_heading(TEST_HEADING),
      )
      .arg(
        Arg::new("retry")
          .long("retry")
          .value_name("NUMBER")
          .help("Re-run failing tests up to NUMBER times. A test passes if any attempt passes. Tests that set their own `retry` option take precedence")
          .value_parser(value_parser!(u32))
          .help_heading(TEST_HEADING),
      )
      .arg(
        Arg::new("repeats")
          .long("repeats")
          .value_name("NUMBER")
          .help("Run each test NUMBER additional times. Every repetition must pass. Tests that set their own `repeats` option take precedence")
          .value_parser(value_parser!(u32))
          .help_heading(TEST_HEADING),
      )
      .arg(
        Arg::new("shard")
          .long("shard")
          .value_name("INDEX/COUNT")
          .help(cstr!("Run only the test files for shard INDEX of COUNT, e.g. --shard=2/3.
  <p(245)>The discovered test files are sorted and split into COUNT consecutive groups; INDEX is 1-based. Useful for splitting a run across machines.</>"))
          .require_equals(true)
          .value_parser(parse_test_shard)
          .help_heading(TEST_HEADING),
      )
      .arg(
        Arg::new("coverage")
          .long("coverage")
          .value_name("DIR")
          .num_args(0..=1)
          .require_equals(true)
          .default_missing_value("coverage")
          .conflicts_with("inspect")
          .conflicts_with("inspect-wait")
          .conflicts_with("inspect-brk")
          .help(cstr!("Collect coverage profile data into DIR. If DIR is not specified, it uses 'coverage/'.
  <p(245)>This option can also be set via the DENO_COVERAGE_DIR environment variable."))
          .help_heading(TEST_HEADING),
      )
      .arg(
        Arg::new("coverage-raw-data-only")
          .long("coverage-raw-data-only")
          .help("Only collect raw coverage data, without generating a report")
          .action(ArgAction::SetTrue)
          .help_heading(TEST_HEADING),
      )
      .arg(
        Arg::new("coverage-threshold")
          .long("coverage-threshold")
          .value_name("PERCENT")
          .value_parser(value_parser!(u32).range(0..=100))
          .require_equals(true)
          .requires("coverage")
          .help("Fail if coverage is below this percentage (0-100). Requires --coverage")
          .help_heading(TEST_HEADING),
      )
      .arg(
        Arg::new("clean")
          .long("clean")
          .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),
      )
      .arg(
        parallel_arg("test modules")
      )
      .arg(
        Arg::new("files")
          .help("List of file names to run")
          .num_args(0..)
          .action(ArgAction::Append)
          .value_hint(ValueHint::AnyPath),
      )
      .arg(
        Arg::new("changed")
          .long("changed")
          .help(cstr!("Run only test modules affected by files changed in git.
  <p(245)>With no value, uses uncommitted changes (staged, unstaged and untracked).
  Pass a git ref to compare against, e.g. --changed=main or --changed=HEAD~1.</>"))
          .value_name("REF")
          .num_args(0..=1)
          .require_equals(true)
          .conflicts_with("watch")
          .help_heading(TEST_HEADING),
      )
      .arg(
        Arg::new("related")
          .long("related")
          .help("Run only test modules that depend on the given source files")
          .num_args(1)
          .require_equals(true)
          .action(ArgAction::Append)
          .value_hint(ValueHint::AnyPath)
          .conflicts_with("watch")
          .help_heading(TEST_HEADING),
      )
      .arg(
        watch_arg(true)
          .conflicts_with("no-run")
          .conflicts_with("coverage"),
      )
      .arg(watch_exclude_arg())
      .arg(no_clear_screen_arg())
      .arg(script_arg().last(true))
      .arg(
        Arg::new("junit-path")
          .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_heading(TEST_HEADING)
      )
      .arg(
        Arg::new("reporter")
          .long("reporter")
          .help("Select reporter to use. Default to 'pretty'")
          .value_parser(["pretty", "dot", "junit", "tap"])
          .help_heading(TEST_HEADING)
      )
      .arg(
        Arg::new("hide-stacktraces")
          .long("hide-stacktraces")
          .help("Hide stack traces for errors in failure test results.")
          .action(ArgAction::SetTrue)
      )
      .arg(
        Arg::new("update-snapshots")
          .long("update-snapshots")
          .short('u')
          .help("Update snapshots created with `t.assertSnapshot()` instead of failing when they do not match")
          .action(ArgAction::SetTrue)
          .help_heading(TEST_HEADING),
      )
      .arg(env_file_arg())
      .arg(executable_ext_arg())
    )
}

fn parallel_arg(descr: &str) -> 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"))
    .action(ArgAction::SetTrue)
}

fn transpile_subcommand() -> Command {
  command(
    "transpile",
    cstr!(
      "Transpile TypeScript/JSX/TSX files to JavaScript.

  <p(245)>deno transpile main.ts</>

Output to a specific file:
  <p(245)>deno transpile main.ts -o main.js</>

Output to a directory:
  <p(245)>deno transpile src/*.ts --outdir dist</>

With source maps:
  <p(245)>deno transpile main.ts --source-map separate</>

Generate declaration files:
  <p(245)>deno transpile main.ts -o out.js --declaration</>

Note: --declaration always writes .d.ts files to disk (next to the source or in --outdir)."
    ),
    UnstableArgsConfig::ResolutionOnly,
  )
  .defer(|cmd| {
    compile_args_without_check_args(cmd)
      .arg(
        Arg::new("file")
          .num_args(1..)
          .required_unless_present("help")
          .value_hint(ValueHint::FilePath),
      )
      .arg(
        Arg::new("output")
          .long("output")
          .short('o')
          .help("Output file path (for single file transpilation)")
          .num_args(1)
          .value_parser(value_parser!(String))
          .value_hint(ValueHint::FilePath)
          .conflicts_with("outdir"),
      )
      .arg(
        Arg::new("outdir")
          .long("outdir")
          .help("Output directory for transpiled files")
          .num_args(1)
          .value_parser(value_parser!(String))
          .value_hint(ValueHint::DirPath),
      )
      .arg(
        Arg::new("source-map")
          .long("source-map")
          .help("Source map mode: none, inline, or separate")
          .num_args(1)
          .value_parser(["none", "inline", "separate"])
          .default_value("none"),
      )
      .arg(
        Arg::new("declaration")
          .long("declaration")
          .help(
            "Generate .d.ts declaration files (requires type-checking via tsc)",
          )
          .action(ArgAction::SetTrue),
      )
  })
}

fn types_subcommand() -> Command {
  command(
    "types",
    cstr!(
      "Print runtime TypeScript declarations.

  <p(245)>deno types > lib.deno.d.ts</>

The declaration file could be saved and used for typing information."
    ),
    UnstableArgsConfig::None,
  )
}

pub static UPGRADE_USAGE: &str = cstr!(
  "<g>Latest</>
  <bold>deno upgrade</>

<g>Specific version</>
  <bold>deno upgrade</> <p(245)>1.45.0</>
  <bold>deno upgrade</> <p(245)>1.46.0-rc.1</>
  <bold>deno upgrade</> <p(245)>9bc2dd29ad6ba334fd57a20114e367d3c04763d4</>

<g>Channel</>
  <bold>deno upgrade</> <p(245)>stable</>
  <bold>deno upgrade</> <p(245)>alpha</>
  <bold>deno upgrade</> <p(245)>beta</>
  <bold>deno upgrade</> <p(245)>rc</>
  <bold>deno upgrade</> <p(245)>canary</>

<g>From a pull request</> <p(245)>(requires gh CLI)</>
  <bold>deno upgrade</> <p(245)>pr 12345</>"
);

fn upgrade_subcommand() -> Command {
  command(
    "upgrade",
    color_print::cformat!("Upgrade deno executable to the given version.

{}

The version is resolved via <p(245)>https://dl.deno.land</> and then downloaded
from either there or GitHub releases, replacing the current executable.

If you want to not replace the current Deno executable but instead download an
update to a different location, use the <c>--output</> flag:
  <p(245)>deno upgrade --output $HOME/my_deno</>

<y>Read more:</> <c>https://docs.deno.com/go/upgrade</>", UPGRADE_USAGE),
    UnstableArgsConfig::None,
  )
  .hide(cfg!(not(feature = "upgrade")))
  .defer(|cmd| {
    cmd
      .arg(
        Arg::new("version")
          .long("version")
          .help("The version to upgrade to")
          .help_heading(UPGRADE_HEADING)// NOTE(bartlomieju): pre-v1.46 compat
          .hide(true),
      )
      .arg(
        Arg::new("output")
          .long("output")
          .help("The path to output the updated version to")
          .value_parser(value_parser!(String))
          .value_hint(ValueHint::FilePath)
          .help_heading(UPGRADE_HEADING),
      )
      .arg(
        Arg::new("dry-run")
          .long("dry-run")
          .help("Perform all checks without replacing old exe")
          .action(ArgAction::SetTrue)
          .help_heading(UPGRADE_HEADING),
      )
      .arg(
        Arg::new("force")
          .long("force")
          .short('f')
          .help("Replace current exe even if not out-of-date")
          .action(ArgAction::SetTrue)
          .help_heading(UPGRADE_HEADING),
      )
      .arg(
        Arg::new("canary")
          .long("canary")
          .help("Upgrade to canary builds")
          .action(ArgAction::SetTrue)
          .help_heading(UPGRADE_HEADING)// NOTE(bartlomieju): pre-v1.46 compat
          .hide(true),
      )
      .arg(
        Arg::new("release-candidate")
          .long("rc")
          .help("Upgrade to a release candidate")
          .conflicts_with_all(["canary", "version"])
          .action(ArgAction::SetTrue)
          .help_heading(UPGRADE_HEADING)
          // NOTE(bartlomieju): pre-v1.46 compat
          .hide(true),
      )
      .arg(
        Arg::new("version-or-hash-or-channel")
          .help(cstr!("Version <p(245)>(v1.46.0)</>, channel <p(245)>(alpha, beta, rc, canary)</>, commit hash <p(245)>(9bc2dd29ad6ba334fd57a20114e367d3c04763d4)</>, or <p(245)>pr 12345</> to install from a PR"))
          .value_name("VERSION")
          .action(ArgAction::Append)
          .trailing_var_arg(true),
      )
      .arg(
        Arg::new("checksum")
          .long("checksum")
          .help("Verify the downloaded archive against the provided SHA256 checksum")
          .value_parser(value_parser!(String))
          .help_heading(UPGRADE_HEADING),
      )
      .arg(
        Arg::new("no-delta")
          .long("no-delta")
          .help("Disable delta updates and always download the full archive")
          .action(ArgAction::SetTrue)
          .help_heading(UPGRADE_HEADING),
      )
      .arg(ca_file_arg())
      .arg(unsafely_ignore_certificate_errors_arg())
  })
}

fn bump_version_subcommand() -> Command {
  command(
    "bump-version",
    cstr!(
      "Update version in the configuration file.
  <p(245)>deno bump-version patch</>       <p(245)># 1.4.6 -> 1.4.7</>
  <p(245)>deno bump-version minor</>       <p(245)># 1.4.6 -> 1.5.0</>
  <p(245)>deno bump-version major</>       <p(245)># 1.4.6 -> 2.0.0</>
  <p(245)>deno bump-version prepatch</>    <p(245)># 1.4.6 -> 1.4.7-0</>
  <p(245)>deno bump-version preminor</>    <p(245)># 1.4.6 -> 1.5.0-0</>
  <p(245)>deno bump-version premajor</>    <p(245)># 1.4.6 -> 2.0.0-0</>
  <p(245)>deno bump-version prerelease</>  <p(245)># 1.4.7-0 -> 1.4.7-1</>

When invoked at a workspace root, the same increment is applied to every
member package and jsr: references in the root import map are updated.
Without an increment, per-package bumps are derived from conventional
commit messages between the latest tag and the current branch and a
release note is prepended to <p(245)>Releases.md</>."
    ),
    UnstableArgsConfig::None,
  )
  .defer(|cmd| {
    cmd
      .arg(
        Arg::new("increment")
          .help("Version increment type")
          .value_parser([
            "major",
            "minor",
            "patch",
            "premajor",
            "preminor",
            "prepatch",
            "prerelease",
          ])
          .index(1),
      )
      .arg(
        Arg::new("workspace")
          .long("workspace")
          .short('w')
          .help(
            "Bump every package in the workspace (auto-detected at the workspace root)",
          )
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("no-workspace")
          .long("no-workspace")
          .help(
            "Disable workspace mode and only bump the deno.json/package.json in the current directory",
          )
          .conflicts_with("workspace")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("dry-run")
          .long("dry-run")
          .help("Print the planned changes without writing any files")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("start")
          .long("start")
          .value_name("REF")
          .help(
            "[conventional-commits mode] Git ref to start from. Default: latest tag (git describe --tags --abbrev=0)",
          ),
      )
      .arg(
        Arg::new("base")
          .long("base")
          .value_name("REF")
          .help(
            "[conventional-commits mode] Git ref to compare against. Default: current branch",
          ),
      )
      .arg(
        Arg::new("import-map")
          .long("import-map")
          .value_name("PATH")
          .help(
            "Path to the import map to rewrite jsr: version constraints in. Defaults to the root deno.json (or its importMap target)",
          ),
      )
      .arg(
        Arg::new("release-notes")
          .long("release-notes")
          .value_name("PATH")
          .help(
            "[conventional-commits mode] Path to the release notes file to prepend. Default: Releases.md",
          ),
      )
      .arg(
        Arg::new("config")
          .long("config")
          .short('c')
          .value_name("FILE")
          .value_hint(ValueHint::FilePath)
          .conflicts_with("workspace")
          .help(cstr!(
            "Explicit path to the manifest file to bump.
  <p(245)>May point to a `deno.json`/`deno.jsonc` or a `package.json`. When
  set, single-file mode is forced (workspace auto-detection is bypassed).
  Useful when both `deno.json` and `package.json` exist in the same
  directory.</>"
          )),
      )
  })
}

fn vendor_subcommand() -> Command {
  command("vendor",
      "`deno vendor` was removed in Deno 2.

See the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations",
      UnstableArgsConfig::ResolutionOnly
    )
    .hide(true)
}

fn publish_subcommand() -> Command {
  command("publish", "Publish the current working directory's package or workspace to JSR", UnstableArgsConfig::ResolutionOnly)
    .defer(|cmd| {
      cmd
        .arg(
          Arg::new("token")
            .long("token")
            .help("The API token to use when publishing. If unset, interactive authentication is be used")
            .help_heading(PUBLISH_HEADING)
        )
        .arg(config_arg())
        .arg(no_config_arg())
        .arg(
          Arg::new("dry-run")
            .long("dry-run")
            .help("Prepare the package for publishing performing all checks and validations without uploading")
            .action(ArgAction::SetTrue)
            .help_heading(PUBLISH_HEADING),
        )
        .arg(
          Arg::new("allow-slow-types")
            .long("allow-slow-types")
            .help("Allow publishing with slow types")
            .action(ArgAction::SetTrue)
            .help_heading(PUBLISH_HEADING),
        )
        .arg(
          Arg::new("allow-dirty")
            .long("allow-dirty")
            .help("Allow publishing if the repository has uncommitted changed")
            .action(ArgAction::SetTrue)
            .help_heading(PUBLISH_HEADING),
        )
        .arg(
          Arg::new("no-provenance")
            .long("no-provenance")
            .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)
        )
        .arg(
          Arg::new("set-version")
            .long("set-version")
            .help(cstr!("Set version for a package to be published.
  <p(245)>This flag can be used while publishing individual packages and cannot be used in a workspace.</>"))
            .value_name("VERSION")
            .help_heading(PUBLISH_HEADING)
        )
        .arg(check_arg(/* type checks by default */ true))
        .arg(no_check_arg())
        .arg(env_file_arg())
    })
}

fn pack_subcommand() -> Command {
  command(
    "pack",
    "Create an npm-compatible tarball from a Deno project",
    UnstableArgsConfig::ResolutionOnly,
  )
  .defer(|cmd| {
    cmd
      .arg(
        Arg::new("output")
          .short('o')
          .long("output")
          .help("Output file path (defaults to <name>-<version>.tgz)")
          .value_name("FILE"),
      )
      .arg(config_arg())
      .arg(no_config_arg())
      .arg(
        Arg::new("dry-run")
          .long("dry-run")
          .help("Show what would be packed without creating the tarball")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("allow-slow-types")
          .long("allow-slow-types")
          .help("Skip fast-check type extraction; .d.ts files are omitted from the output")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("allow-dirty")
          .long("allow-dirty")
          .help("Allow packing if the repository has uncommitted changes")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("set-version")
          .long("set-version")
          .help("Override the version in the tarball")
          .value_name("VERSION"),
      )
      .arg(
        Arg::new("no-source-maps")
          .long("no-source-maps")
          .help("Don't include source maps in the output")
          .action(ArgAction::SetTrue),
      )
      .arg(
        Arg::new("files")
          .help("List of file patterns to include")
          .num_args(..)
          .action(ArgAction::Append),
      )
      .arg(
        Arg::new("ignore")
          .long("ignore")
          .num_args(1..)
          .action(ArgAction::Append)
          .require_equals(true)
          .help("Ignore files matching these patterns")
          .value_hint(ValueHint::AnyPath),
      )
      .arg(env_file_arg())
  })
}

fn compile_args(app: Command) -> Command {
  compile_args_without_check_args(app.arg(no_check_arg()))
}

fn compile_args_without_check_args(app: Command) -> Command {
  app
    .arg(import_map_arg())
    .arg(no_remote_arg())
    .arg(no_npm_arg())
    .arg(node_modules_dir_arg())
    .arg(node_modules_linker_arg())
    .arg(vendor_arg())
    .arg(node_conditions_arg())
    .arg(config_arg())
    .arg(no_config_arg())
    .arg(reload_arg())
    .args(lock_args())
    .arg(ca_file_arg())
    .arg(unsafely_ignore_certificate_errors_arg())
    .arg(min_dep_age_arg())
}

fn permission_args(app: Command, requires: Option<&'static str>) -> Command {
  let make_deny_ignore_env_arg = |arg: Arg| {
    let mut arg = arg
      .num_args(0..)
      .use_value_delimiter(true)
      .require_equals(true)
      .value_name("VARIABLE_NAME")
      .long_help("false")
      .value_parser(|key: &str| {
        if key.is_empty() || key.contains(&['=', '\0'] as &[char]) {
          return Err(format!("invalid key \"{key}\""));
        }

        Ok(if cfg!(windows) {
          key.to_uppercase()
        } else {
          key.to_string()
        })
      })
      .hide(true);
    if let Some(requires) = requires {
      arg = arg.requires(requires)
    }
    arg
  };
  let make_deny_ignore_read_arg = |arg: Arg| {
    let mut arg = arg
      .num_args(0..)
      .action(ArgAction::Append)
      .require_equals(true)
      .value_name("PATH")
      .long_help("false")
      .value_hint(ValueHint::AnyPath)
      .hide(true);
    if let Some(requires) = requires {
      arg = arg.requires(requires)
    }
    arg
  };
  app
    .after_help(cstr!(r#"<y>Permission options:</>
<y>Docs</>: <c>https://docs.deno.com/go/permissions</>

  <g>-A, --allow-all</>                           Allow all permissions.
  <g>-P, --permission-set[=<<NAME>]</>            Loads the permission set from the config file.
  <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>-I, --allow-import[=<<IP_OR_HOSTNAME>...]</> Allow importing from remote hosts. Optionally specify allowed IP addresses and host names, with ports as necessary.
                                            Default value: <p(245)>deno.land:443,jsr.io:443,esm.sh:443,raw.esm.sh:443,cdn.jsdelivr.net:443,raw.githubusercontent.com:443,gist.githubusercontent.com:443</>
                                             <p(245)>--allow-import  |  --allow-import="example.com,github.com"</>
  <g>-N, --allow-net[=<<IP_OR_HOSTNAME>...]</>    Allow network access. Optionally specify allowed IP addresses and host names, with ports as necessary. A Unix domain socket can be scoped with <p(245)>unix:<<absolute-path></>.
                                             <p(245)>--allow-net  |  --allow-net="localhost:8080,deno.land"  |  --allow-net="unix:/var/run/docker.sock"</>
  <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>--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>--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-import[=<<IP_OR_HOSTNAME>...]</>  Deny importing from remote hosts. Optionally specify denied IP addresses and host names, with ports as necessary.
                                             <p(245)>--deny-import  |  --deny-import="example.com:443,github.com:443"</>
      <g>--ignore-env[=<<VARIABLE_NAME>...]</>    Ignore access to environment variables returning `undefined`. Optionally specify ignored environment variables.
                                             <p(245)>--ignore-env  |  --ignore-env="PORT,HOME,PATH"</>
      <g>--ignore-read[=<<PATH>...]</>            Ignore file system read access with a `NotFound` error. Optionally specify ignored paths.
                                             <p(245)>--ignore-read  |  --ignore-read="/etc,/var/log.txt"</>
      <g>DENO_TRACE_PERMISSIONS</>                Environmental variable to enable stack traces in permission prompts.
                                             <p(245)>DENO_TRACE_PERMISSIONS=1 deno run main.ts</>
      <g>DENO_AUDIT_PERMISSIONS</>               Environmental variable to audit all permissions accesses. Set to a file path for JSONL output, or "otel" to emit as OpenTelemetry log events via the configured OTel exporter.
                                             <p(245)>DENO_AUDIT_PERMISSIONS=./audit.jsonl deno run main.ts</>
                                             <p(245)>DENO_AUDIT_PERMISSIONS=otel deno run main.ts</>
"#))
    .arg(
      {
        let mut arg = allow_all_arg().hide(true);
        if let Some(requires) = requires {
          arg = arg.requires(requires)
        }
        arg
      }
    )
    .arg(
      {
        let mut arg = Arg::new("permission-set")
          .long("permission-set")
          .action(ArgAction::Set)
          .num_args(0..=1)
          .require_equals(true)
          .default_missing_value("")
          .short('P')
          .hide(true);
        if let Some(requires) = requires {
          arg = arg.requires(requires);
        }
        arg
      }
    )
    .arg(
      {
        let mut arg = Arg::new("allow-read")
          .long("allow-read")
          .short('R')
          .num_args(0..)
          .action(ArgAction::Append)
          .require_equals(true)
          .value_name("PATH")
          .long_help("false")
          .value_hint(ValueHint::AnyPath)
          .hide(true);
        if let Some(requires) = requires {
          arg = arg.requires(requires)
        }
        arg
      }
    )
    .arg(make_deny_ignore_read_arg(Arg::new("deny-read").long("deny-read")))
    .arg(make_deny_ignore_read_arg(Arg::new("ignore-read").long("ignore-read")))
    .arg(
      {
        let mut arg = Arg::new("allow-write")
          .long("allow-write")
          .short('W')
          .num_args(0..)
          .action(ArgAction::Append)
          .require_equals(true)
          .value_name("PATH")
          .long_help("false")
          .value_hint(ValueHint::AnyPath)
          .hide(true);
        if let Some(requires) = requires {
          arg = arg.requires(requires)
        }
        arg
      }
    )
    .arg(
      {
        let mut arg = Arg::new("deny-write")
          .long("deny-write")
          .num_args(0..)
          .action(ArgAction::Append)
          .require_equals(true)
          .value_name("PATH")
          .long_help("false")
          .value_hint(ValueHint::AnyPath)
          .hide(true);
        if let Some(requires) = requires {
          arg = arg.requires(requires)
        }
        arg
      }
    )
    .arg(
      {
        let mut arg = Arg::new("allow-net")
          .long("allow-net")
          .short('N')
          .num_args(0..)
          .use_value_delimiter(true)
          .require_equals(true)
          .value_name("IP_OR_HOSTNAME")
          .long_help("false")
          .value_parser(flags_net::validator)
          .hide(true);
        if let Some(requires) = requires {
          arg = arg.requires(requires)
        }
        arg
      }
    )
    .arg(
      {
        let mut arg = Arg::new("deny-net")
          .long("deny-net")
          .num_args(0..)
          .use_value_delimiter(true)
          .require_equals(true)
          .value_name("IP_OR_HOSTNAME")
          .long_help("false")
          .value_parser(flags_net::validator)
          .hide(true);
        if let Some(requires) = requires {
          arg = arg.requires(requires)
        }
        arg
      }
    )
    .arg(
      {
        let mut arg = Arg::new("allow-env")
          .long("allow-env")
          .short('E')
          .num_args(0..)
          .use_value_delimiter(true)
          .require_equals(true)
          .value_name("VARIABLE_NAME")
          .long_help("false")
          .value_parser(|key: &str| {
            if key.is_empty() || key.contains(&['=', '\0'] as &[char]) {
              return Err(format!("invalid key \"{key}\""));
            }

            Ok(if cfg!(windows) {
              key.to_uppercase()
            } else {
              key.to_string()
            })
          })
          .hide(true);
        if let Some(requires) = requires {
          arg = arg.requires(requires)
        }
        arg
      }
    )
    .arg(make_deny_ignore_env_arg(Arg::new("deny-env").long("deny-env")))
    .arg(make_deny_ignore_env_arg(Arg::new("ignore-env").long("ignore-env")))
    .arg(
      {
        let mut arg = Arg::new("allow-sys")
          .long("allow-sys")
          .short('S')
          .num_args(0..)
          .use_value_delimiter(true)
          .require_equals(true)
          .value_name("API_NAME")
          .long_help("false")
          .value_parser(|key: &str| SysDescriptor::parse(key.to_string()).map(|s| s.into_string()))
          .hide(true);
        if let Some(requires) = requires {
          arg = arg.requires(requires)
        }
        arg
      }
    )
    .arg(
      {
        let mut arg = Arg::new("deny-sys")
          .long("deny-sys")
          .num_args(0..)
          .use_value_delimiter(true)
          .require_equals(true)
          .value_name("API_NAME")
          .long_help("false")
          .value_parser(|key: &str| SysDescriptor::parse(key.to_string()).map(|s| s.into_string()))
          .hide(true);
        if let Some(requires) = requires {
          arg = arg.requires(requires)
        }
        arg
      }
    )
    .arg(
      {
        let mut arg = Arg::new("allow-run")
          .long("allow-run")
          .num_args(0..)
          .use_value_delimiter(true)
          .require_equals(true)
          .value_name("PROGRAM_NAME")
          .long_help("false")
          .hide(true);
        if let Some(requires) = requires {
          arg = arg.requires(requires)
        }
        arg
      }
    )
    .arg(
      {
        let mut arg = Arg::new("deny-run")
          .long("deny-run")
          .num_args(0..)
          .use_value_delimiter(true)
          .require_equals(true)
          .value_name("PROGRAM_NAME")
          .long_help("false")
          .hide(true);
        if let Some(requires) = requires {
          arg = arg.requires(requires)
        }
        arg

      }
    )
    .arg(
      {
        let mut arg = Arg::new("allow-ffi")
          .long("allow-ffi")
          .num_args(0..)
          .action(ArgAction::Append)
          .require_equals(true)
          .value_name("PATH")
          .long_help("false")
          .value_hint(ValueHint::AnyPath)
          .hide(true);
        if let Some(requires) = requires {
          arg = arg.requires(requires)
        }
        arg
      }
    )
    .arg(
      {
        let mut arg = Arg::new("deny-ffi")
          .long("deny-ffi")
          .num_args(0..)
          .action(ArgAction::Append)
          .require_equals(true)
          .value_name("PATH")
          .long_help("false")
          .value_hint(ValueHint::AnyPath)
          .hide(true);
        if let Some(requires) = requires {
          arg = arg.requires(requires)
        }
        arg
      }
    )
    .arg(
      {
        let mut arg = Arg::new("allow-hrtime")
          .long("allow-hrtime")
          .action(ArgAction::SetTrue)
          .long_help("false")
          .hide(true);
        if let Some(requires) = requires {
          arg = arg.requires(requires)
        }
        arg
      }
    )
    .arg(
      {
        let mut arg = Arg::new("deny-hrtime")
          .long("deny-hrtime")
          .action(ArgAction::SetTrue)
          .long_help("false")
          .hide(true);
        if let Some(requires) = requires {
          arg = arg.requires(requires)
        }
        arg
      }
    )
    .arg(
      {
        let mut arg = Arg::new("no-prompt")
          .long("no-prompt")
          .action(ArgAction::SetTrue)
          .hide(true)
          .long_help("false");
        if let Some(requires) = requires {
          arg = arg.requires(requires)
        }
        arg
      }
    )
    .arg(
      {
        let mut arg = allow_import_arg().hide(true);
        if let Some(requires) = requires {
          // allow this for install --global
          if requires != "global" {
            arg = arg.requires(requires)
          }
        }
        arg
      }
    )
    .arg(
      {
        let mut arg = deny_import_arg().hide(true);
        if let Some(requires) = requires {
          // allow this for install --global
          if requires != "global" {
            arg = arg.requires(requires)
          }
        }
        arg
      }
    )
}

fn allow_all_arg() -> Arg {
  Arg::new("allow-all")
    .short('A')
    .long("allow-all")
    .conflicts_with("allow-read")
    .conflicts_with("allow-write")
    .conflicts_with("allow-net")
    .conflicts_with("allow-env")
    .conflicts_with("allow-run")
    .conflicts_with("allow-sys")
    .conflicts_with("allow-ffi")
    .conflicts_with("allow-import")
    .conflicts_with("permission-set")
    .help("Allow all permissions")
    // So situations like `--allow-all -A` don't crash - this is for historic reason,
    // because VSCode automatically added `--allow-all` in some scenarios, causing crashes
    // when users used `-A` themselves.
    .action(ArgAction::Count)
}

fn runtime_args(
  app: Command,
  include_perms: bool,
  include_inspector: bool,
  include_allow_scripts: bool,
) -> Command {
  let app = compile_args(app);
  let app = if include_perms {
    permission_args(app, None)
  } else {
    app
  };
  let app = if include_inspector {
    inspect_args(app)
  } else {
    app
  };
  let app = if include_allow_scripts {
    app.arg(allow_scripts_arg())
  } else {
    app
  };
  runtime_misc_args(app)
}

fn runtime_misc_args(app: Command) -> Command {
  app
    .arg(cached_only_arg())
    .arg(location_arg())
    .arg(v8_flags_arg())
    .arg(seed_arg())
    .arg(enable_testing_features_arg())
    .arg(trace_ops_arg())
    .arg(eszip_arg())
    .arg(preload_arg())
    .arg(require_arg())
}

fn eszip_arg() -> Arg {
  Arg::new("eszip-internal-do-not-use")
    .hide(true)
    .long("eszip-internal-do-not-use")
    .action(ArgAction::SetTrue)
}

fn allow_import_arg() -> Arg {
  Arg::new("allow-import")
    .long("allow-import")
    .short('I')
    .num_args(0..)
    .use_value_delimiter(true)
    .require_equals(true)
    .value_name("IP_OR_HOSTNAME")
    .help(cstr!(
      "Allow importing from remote hosts. Optionally specify allowed IP addresses and host names, with ports as necessary. Default value: <p(245)>deno.land:443,jsr.io:443,esm.sh:443,raw.esm.sh:443,cdn.jsdelivr.net:443,raw.githubusercontent.com:443,gist.githubusercontent.com:443</>"
    ))
    .value_parser(flags_net::validator)
}

fn deny_import_arg() -> Arg {
  Arg::new("deny-import")
  .long("deny-import")
  .num_args(0..)
  .use_value_delimiter(true)
  .require_equals(true)
  .value_name("IP_OR_HOSTNAME")
  .help(cstr!(
    "Deny importing from remote hosts. Optionally specify denied IP addresses and host names, with ports as necessary."
  ))
  .value_parser(flags_net::validator)
}

pub fn inspect_value_parser(host_and_port: &str) -> Result<SocketAddr, String> {
  const DEFAULT_HOST: &str = "127.0.0.1";
  const DEFAULT_PORT: u16 = 9229;

  fn parse_port(port: &str) -> Result<u16, String> {
    port
      .parse::<u16>()
      .map_err(|_| format!("Invalid inspector port '{port}'"))
  }

  // Resolve a host string to an IP address. IPs are returned as-is.
  // Hostnames (e.g. "localhost") are resolved via DNS so Node's
  // `--inspect=localhost:0` syntax works. To match Node's resolver,
  // IPv4 results are preferred when both families are returned.
  fn resolve_host(host: &str) -> Result<IpAddr, String> {
    if let Ok(ip) = host.parse::<IpAddr>() {
      return Ok(ip);
    }
    let addrs = (host, 0u16)
      .to_socket_addrs()
      .map_err(|e| format!("Invalid inspector host '{host}': {}", e))?
      .collect::<Vec<_>>();
    addrs
      .iter()
      .find(|a| a.is_ipv4())
      .or_else(|| addrs.first())
      .map(|a| a.ip())
      .ok_or_else(|| format!("Could not resolve inspector host '{host}'"))
  }

  let default_host: IpAddr = DEFAULT_HOST.parse().unwrap();

  if host_and_port.is_empty() {
    return Err("Inspector address cannot be empty".to_string());
  }

  if let Some(port_part) = host_and_port.strip_prefix(':') {
    let port = if port_part.is_empty() {
      DEFAULT_PORT
    } else {
      parse_port(port_part)?
    };
    return Ok(SocketAddr::new(default_host, port));
  }

  if host_and_port.contains(':') {
    if let Ok(addr) = host_and_port.parse::<SocketAddr>() {
      return Ok(addr);
    }

    if let Ok(host_ip) = host_and_port.parse::<IpAddr>() {
      return Ok(SocketAddr::new(host_ip, DEFAULT_PORT));
    }

    let (host_part, port_part) = host_and_port
      .rsplit_once(':')
      .ok_or_else(|| format!("Invalid inspector address '{host_and_port}'"))?;

    let port = if port_part.is_empty() {
      DEFAULT_PORT
    } else {
      parse_port(port_part)?
    };

    let host_ip = resolve_host(host_part)?;

    return Ok(SocketAddr::new(host_ip, port));
  }

  if host_and_port.chars().all(|c| c.is_ascii_digit()) {
    let port = parse_port(host_and_port)?;
    return Ok(SocketAddr::new(default_host, port));
  }

  let host_ip = resolve_host(host_and_port)?;

  Ok(SocketAddr::new(host_ip, DEFAULT_PORT))
}

fn inspect_args(app: Command) -> Command {
  app
    .arg(
      Arg::new("inspect")
        .long("inspect")
        .value_name("HOST_PORT")
        .default_missing_value("127.0.0.1:9229")
        .help(cstr!("Activate inspector on host:port <p(245)>[default: 127.0.0.1:9229]</>. Host and port are optional. Using port 0 will assign a random free port."))
        .num_args(0..=1)
        .require_equals(true)
        .value_parser(inspect_value_parser)
        .help_heading(DEBUGGING_HEADING),
    )
    .arg(
      Arg::new("inspect-brk")
        .long("inspect-brk")
        .value_name("HOST_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",
        )
        .num_args(0..=1)
        .require_equals(true)
        .value_parser(inspect_value_parser)
        .help_heading(DEBUGGING_HEADING),
    )
    .arg(
      Arg::new("inspect-wait")
        .long("inspect-wait")
        .value_name("HOST_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",
        )
        .num_args(0..=1)
        .require_equals(true)
        .value_parser(inspect_value_parser)
        .help_heading(DEBUGGING_HEADING),
    )
    .arg(inspect_publish_uid_arg())
}

fn inspect_publish_uid_value_parser(
  value: &str,
) -> Result<InspectPublishUid, String> {
  parse_inspect_publish_uid(value)
}

fn inspect_publish_uid_arg() -> Arg {
  Arg::new("inspect-publish-uid")
    .long("inspect-publish-uid")
    .value_name("VALUE")
    .require_equals(true)
    .value_parser(inspect_publish_uid_value_parser)
    .hide(true)
}

fn import_map_arg() -> Arg {
  Arg::new("import-map")
    .long("import-map")
    .alias("importmap")
    .value_name("FILE")
    .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)
}

fn env_file_arg() -> Arg {
  Arg::new("env-file")
    .long("env-file")
    .alias("env")
    .value_name("FILE")
    .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, so if variables with the same names already exist in the environment, their values will be preserved.
  Where multiple declarations for the same environment variable exist in your .env file, the first one encountered is applied. This is determined by the order of the files you pass as arguments.</>"
    ))
    .value_hint(ValueHint::FilePath)
    .default_missing_value(".env")
    .require_equals(true)
    .num_args(0..=1)
    .action(ArgAction::Append)
}

fn use_env_proxy_arg() -> Arg {
  Arg::new("use-env-proxy")
    .long("use-env-proxy")
    .action(ArgAction::SetTrue)
    .conflicts_with("no-use-env-proxy")
    .help("Use HTTP_PROXY, HTTPS_PROXY, and NO_PROXY for node:http/node:https")
}

fn no_use_env_proxy_arg() -> Arg {
  Arg::new("no-use-env-proxy")
    .long("no-use-env-proxy")
    .action(ArgAction::SetTrue)
    .conflicts_with("use-env-proxy")
    .hide(true)
}

fn reload_arg() -> Arg {
  Arg::new("reload")
    .short('r')
    .num_args(0..)
    .action(ArgAction::Append)
    .require_equals(true)
    .long("reload")
    .value_name("CACHE_BLOCKLIST")
    .help(
      cstr!("Reload source code cache (recompile TypeScript). With no value, reloads everything. Pass a comma-separated list of specifiers to reload only those modules; <p(245)>npm:</> reloads all npm modules; <p(245)>npm:chalk</> reloads a single npm module; <p(245)>jsr:@std/http/file-server,jsr:@std/assert/assert-equals</> reloads specific modules.",
    ))
    .value_hint(ValueHint::FilePath)
    .help_heading(DEPENDENCY_MANAGEMENT_HEADING)
}

fn preload_arg() -> Arg {
  Arg::new("preload")
    .long("preload")
    .alias("import")
    .value_name("FILE")
    .action(ArgAction::Append)
    .help("A list of files that will be executed before the main module")
    .value_hint(ValueHint::FilePath)
}

fn require_arg() -> Arg {
  Arg::new("require")
    .long("require")
    .value_name("FILE")
    .action(ArgAction::Append)
    .help(
      "A list of CommonJS modules that will be executed before the main module",
    )
    .value_hint(ValueHint::FilePath)
}

fn min_dep_age_arg() -> Arg {
  Arg::new("minimum-dependency-age")
    .long("minimum-dependency-age")
    .alias("min-dep-age")
    .value_parser(minutes_duration_or_date_parser)
    .help("(Unstable) The age in minutes, ISO-8601 duration or RFC3339 absolute timestamp (e.g. '120' for two hours, 'P2D' for two days, '2025-09-16' for cutoff date, '2025-09-16T12:00:00+00:00' for cutoff time, '0' to disable)")
}

fn ca_file_arg() -> Arg {
  Arg::new("cert")
    .long("cert")
    .value_name("FILE")
    .help("Load certificate authority from PEM encoded file")
    .value_hint(ValueHint::FilePath)
}

fn cached_only_arg() -> Arg {
  Arg::new("cached-only")
    .long("cached-only")
    .action(ArgAction::SetTrue)
    .help("Require that remote dependencies are already cached")
    .help_heading(DEPENDENCY_MANAGEMENT_HEADING)
}

/// Used for subcommands that operate on executable scripts only.
/// `deno fmt` has its own `--ext` arg because its possible values differ.
/// If --ext is not provided and the script doesn't have a file extension,
/// deno_graph::parse_module() defaults to js.
fn executable_ext_arg() -> Arg {
  Arg::new("ext")
    .long("ext")
    .help("Set content type of the supplied file")
    .value_parser(["ts", "tsx", "js", "jsx", "mts", "mjs", "cts", "cjs"])
}

fn exclude_unused_npm_arg() -> Arg {
  Arg::new("exclude-unused-npm")
    .long("exclude-unused-npm")
    .help(cstr!("Embed only the npm packages reachable from the module graph (managed npm; no <c>node_modules</> directory).
  <p(245)>Without this flag the full managed npm snapshot from the lockfile / package.json is embedded.
  Reduces binary size when the lockfile contains packages the entrypoint does not import.
  Skips packages that are only reached through non-statically-analyzable dynamic imports;
  pass those with <c>--include npm:<<pkg></> if needed.</>"))
    .action(ArgAction::SetTrue)
}

fn location_arg() -> Arg {
  Arg::new("location")
    .long("location")
    .value_name("HREF")
    .value_parser(|href: &str| -> Result<Url, String> {
      let url = Url::parse(href);
      if url.is_err() {
        return Err("Failed to parse URL".to_string());
      }
      let mut url = url.unwrap();
      if !["http", "https"].contains(&url.scheme()) {
        return Err("Expected protocol \"http\" or \"https\"".to_string());
      }
      url.set_username("").unwrap();
      url.set_password(None).unwrap();
      Ok(url)
    })
    .help(cstr!(
      "Value of <p(245)>globalThis.location</> used by some web APIs"
    ))
    .value_hint(ValueHint::Url)
}

fn enable_testing_features_arg() -> Arg {
  Arg::new("enable-testing-features-do-not-use")
    .long("enable-testing-features-do-not-use")
    .help("INTERNAL: Enable internal features used during integration testing")
    .action(ArgAction::SetTrue)
    .hide(true)
}

fn trace_ops_arg() -> Arg {
  Arg::new("trace-ops")
    .long("trace-ops")
    .num_args(0..)
    .use_value_delimiter(true)
    .require_equals(true)
    .value_name("OPS")
    .help("Trace low-level op calls")
    .hide(true)
}

fn v8_flags_arg() -> Arg {
  Arg::new("v8-flags")
    .long("v8-flags")
    .num_args(..)
    .use_value_delimiter(true)
    .require_equals(true)
    .value_name("V8_FLAGS")
    .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 {
  Arg::new("seed")
    .long("seed")
    .value_name("NUMBER")
    .help("Set the random number generator seed")
    .value_parser(value_parser!(u64))
}

fn hmr_arg(takes_files: bool) -> Arg {
  let arg = Arg::new("hmr")
    .long("watch-hmr")
    // NOTE(bartlomieju): compatibility with Deno pre-1.46
    .alias("unstable-hmr")
    .help("Watch for file changes and hot replace modules")
    .conflicts_with("watch")
    .help_heading(FILE_WATCHING_HEADING);

  if takes_files {
    arg
      .value_name("FILES")
      .num_args(0..)
      .action(ArgAction::Append)
      .require_equals(true)
      .help(
        cstr!(
        "Watch for file changes and hot-replace modules. The process restarts if hot replacement fails.
  <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(cstr!(
      "Watch for file changes and hot-replace modules. The process restarts if hot replacement fails.
  <p(245)>Only local files from entry point module graph are watched.</>"
    ))
  }
}

fn watch_arg(takes_files: bool) -> Arg {
  let arg = Arg::new("watch")
    .long("watch")
    .help_heading(FILE_WATCHING_HEADING);

  if takes_files {
    arg
      .value_name("FILES")
      .num_args(0..)
      .action(ArgAction::Append)
      .require_equals(true)
      .help(
        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(cstr!(
      "Watch for file changes and restart process automatically.
  <p(245)>Only local files from entry point module graph are watched.</>"
    ))
  }
}

fn no_clear_screen_arg() -> Arg {
  Arg::new("no-clear-screen")
    .requires("watch")
    .long("no-clear-screen")
    .action(ArgAction::SetTrue)
    .help("Do not clear terminal screen when under watch mode")
    .help_heading(FILE_WATCHING_HEADING)
}

fn no_code_cache_arg() -> Arg {
  Arg::new("no-code-cache")
    .long("no-code-cache")
    .help("Disable V8 code cache feature")
    .action(ArgAction::SetTrue)
}

fn coverage_arg() -> Arg {
  Arg::new("coverage")
    .long("coverage")
    .value_name("DIR")
    .num_args(0..=1)
    .require_equals(true)
    .default_missing_value("coverage")
    .conflicts_with("inspect")
    .conflicts_with("inspect-wait")
    .conflicts_with("inspect-brk")
    .help(cstr!("Collect coverage profile data into DIR. If DIR is not specified, it uses 'coverage/'.
  <p(245)>This option can also be set via the DENO_COVERAGE_DIR environment variable."))
    .value_hint(ValueHint::AnyPath)
}

fn cpu_prof_args(cmd: Command) -> Command {
  cmd
    .arg(cpu_prof_arg())
    .arg(cpu_prof_dir_arg())
    .arg(cpu_prof_name_arg())
    .arg(cpu_prof_interval_arg())
    .arg(cpu_prof_md_arg())
    .arg(cpu_prof_flamegraph_arg())
}

fn cpu_prof_parse(matches: &mut ArgMatches) -> Option<CpuProfFlags> {
  let enabled = matches.get_flag("cpu-prof");
  let dir = matches.remove_one::<String>("cpu-prof-dir");
  let name = matches.remove_one::<String>("cpu-prof-name");
  let interval = matches.remove_one::<u32>("cpu-prof-interval");
  let md = matches.get_flag("cpu-prof-md");
  let flamegraph = matches.get_flag("cpu-prof-flamegraph");
  if enabled
    || dir.is_some()
    || name.is_some()
    || interval.is_some()
    || md
    || flamegraph
  {
    Some(CpuProfFlags {
      dir,
      name,
      interval,
      md,
      flamegraph,
    })
  } else {
    None
  }
}

fn cpu_prof_arg() -> Arg {
  Arg::new("cpu-prof")
    .long("cpu-prof")
    .help("Start the V8 CPU profiler on startup and write the profile to disk on exit. Profiles are written to the current directory by default")
    .action(ArgAction::SetTrue)
}

fn cpu_prof_dir_arg() -> Arg {
  Arg::new("cpu-prof-dir")
    .long("cpu-prof-dir")
    .value_name("DIR")
    .help("Directory where the V8 CPU profiles will be written. Implicitly enables --cpu-prof")
    .value_hint(ValueHint::DirPath)
    .value_parser(value_parser!(String))
}

fn cpu_prof_name_arg() -> Arg {
  Arg::new("cpu-prof-name")
    .long("cpu-prof-name")
    .value_name("NAME")
    .help("Filename for the CPU profile (defaults to CPU.<timestamp>.<pid>.cpuprofile)")
    .value_parser(value_parser!(String))
}

fn cpu_prof_interval_arg() -> Arg {
  Arg::new("cpu-prof-interval")
    .long("cpu-prof-interval")
    .value_name("MICROSECONDS")
    .help("Sampling interval in microseconds for CPU profiling (default: 1000)")
    .value_parser(value_parser!(u32))
}

fn cpu_prof_md_arg() -> Arg {
  Arg::new("cpu-prof-md")
    .long("cpu-prof-md")
    .help("Generate a human-readable markdown report alongside the CPU profile")
    .action(ArgAction::SetTrue)
}

fn cpu_prof_flamegraph_arg() -> Arg {
  Arg::new("cpu-prof-flamegraph")
    .long("cpu-prof-flamegraph")
    .help("Generate an SVG flamegraph alongside the CPU profile")
    .action(ArgAction::SetTrue)
}

fn permit_no_files_arg() -> Arg {
  Arg::new("permit-no-files")
    .long("permit-no-files")
    .help("Don't return an error code if no files were found")
    .action(ArgAction::SetTrue)
}

fn watch_exclude_arg() -> Arg {
  Arg::new("watch-exclude")
    .long("watch-exclude")
    .help("Exclude provided files/patterns from watch mode")
    .value_name("FILES")
    .num_args(0..)
    .action(ArgAction::Append)
    .require_equals(true)
    .value_hint(ValueHint::AnyPath)
    .help_heading(FILE_WATCHING_HEADING)
}

fn no_check_arg() -> Arg {
  Arg::new("no-check")
    .num_args(0..=1)
    .require_equals(true)
    .value_name("NO_CHECK_TYPE")
    .long("no-check")
    .help("Skip type-checking. If the value of \"remote\" is supplied, diagnostic errors from remote modules will be ignored")
    .help_heading(TYPE_CHECKING_HEADING)
}

fn tunnel_arg() -> Arg {
  Arg::new("tunnel")
    .long("tunnel")
    .alias("connected")
    .short('t')
    .num_args(0..=1)
    .help(cstr!(
      "Execute tasks with a tunnel to Deno Deploy.

    Create a secure connection between your local machine and Deno Deploy,
    providing access to centralised environment variables, logging,
    and serving from your local environment to the public internet"
    ))
    .require_equals(true)
    .action(ArgAction::SetTrue)
}

fn check_arg(checks_local_by_default: bool) -> Arg {
  let arg = Arg::new("check")
    .conflicts_with("no-check")
    .long("check")
    .num_args(0..=1)
    .require_equals(true)
    .value_name("CHECK_TYPE")
    .help_heading(TYPE_CHECKING_HEADING);

  if checks_local_by_default {
    arg.help(
      cstr!("Set type-checking behavior. This subcommand type-checks local modules by default, so passing <p(245)>--check</> is redundant; pass <p(245)>--check=all</> to also type-check remote modules. Alternatively, use the <p(245)>'deno check'</> subcommand.",
    ))
  } else {
    arg.help(cstr!(
      "Enable type-checking. This subcommand does not type-check by default; pass <p(245)>--check=all</> to also type-check remote modules. Alternatively, use the <p(245)>'deno check'</> subcommand."
    ))
  }
}

fn script_arg() -> Arg {
  Arg::new("script_arg")
    .num_args(0..)
    .action(ArgAction::Append)
    // NOTE: these defaults are provided
    // so `deno run --v8-flags=--help` works
    // without specifying file to run.
    .default_value_ifs([
      ("v8-flags", "--help", Some("_")),
      ("v8-flags", "-help", Some("_")),
    ])
    .help("Script arg")
    .value_name("SCRIPT_ARG")
    .value_hint(ValueHint::FilePath)
}

fn lock_args() -> [Arg; 3] {
  [
    Arg::new("lock")
      .long("lock")
      .value_name("FILE")
      .default_missing_value("./deno.lock")
      .help("Check the specified lock file. (If value is not provided, defaults to \"./deno.lock\")")
      .num_args(0..=1)
      .value_parser(value_parser!(String))
      .value_hint(ValueHint::FilePath)
      .help_heading(DEPENDENCY_MANAGEMENT_HEADING),
    Arg::new("no-lock")
      .long("no-lock")
      .action(ArgAction::SetTrue)
      .help("Disable auto discovery of the lock file")
      .conflicts_with("lock")
      .help_heading(DEPENDENCY_MANAGEMENT_HEADING),
    Arg::new("frozen")
      .long("frozen")
      .alias("frozen-lockfile")
      .value_parser(value_parser!(bool))
      .value_name("BOOLEAN")
      .num_args(0..=1)
      .require_equals(true)
      .default_missing_value("true")
      .help("Error out if lockfile is out of date")
      .help_heading(DEPENDENCY_MANAGEMENT_HEADING)
  ]
}

fn node_conditions_arg() -> Arg {
  Arg::new("conditions")
    .long("conditions")
    .help("Use this argument to specify custom conditions for npm package exports. You can also use DENO_CONDITIONS env var.

Docs: https://docs.deno.com/go/conditional-exports")
    .use_value_delimiter(true)
    .action(ArgAction::Append)
}

fn config_arg() -> Arg {
  Arg::new("config")
    .short('c')
    .long("config")
    .value_name("FILE")
    .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</>"))
    .value_hint(ValueHint::FilePath)
}

fn no_config_arg() -> Arg {
  Arg::new("no-config")
    .long("no-config")
    .action(ArgAction::SetTrue)
    .help("Disable automatic loading of the configuration file")
    .conflicts_with("config")
}

fn no_remote_arg() -> Arg {
  Arg::new("no-remote")
    .long("no-remote")
    .action(ArgAction::SetTrue)
    .help("Do not resolve remote modules")
    .help_heading(DEPENDENCY_MANAGEMENT_HEADING)
}

fn no_npm_arg() -> Arg {
  Arg::new("no-npm")
    .long("no-npm")
    .action(ArgAction::SetTrue)
    .help("Do not resolve npm modules")
    .help_heading(DEPENDENCY_MANAGEMENT_HEADING)
}

fn node_modules_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  let value = matches.remove_one::<NodeModulesDirMode>("node-modules-dir");
  if let Some(mode) = value {
    flags.node_modules_dir = Some(mode);
  }
}

fn node_modules_dir_arg() -> Arg {
  fn parse_node_modules_dir_mode(
    s: &str,
  ) -> Result<NodeModulesDirMode, String> {
    match s {
      "auto" | "true" => Ok(NodeModulesDirMode::Auto),
      "manual" => Ok(NodeModulesDirMode::Manual),
      "none" | "false" => Ok(NodeModulesDirMode::None),
      _ => Err(format!(
        "Invalid value '{}': expected \"auto\", \"manual\" or \"none\"",
        s
      )),
    }
  }

  Arg::new("node-modules-dir")
    .long("node-modules-dir")
    .num_args(0..=1)
    .default_missing_value("auto")
    .value_parser(clap::builder::ValueParser::new(parse_node_modules_dir_mode))
    .value_name("MODE")
    .require_equals(true)
    .help(cstr!("Selects the node_modules directory mode for npm packages (not a path). One of: <p(245)>auto</> (create a local node_modules directory and install npm packages into it), <p(245)>manual</> (use the existing local node_modules directory, do not modify it), <p(245)>none</> (do not use a local node_modules directory; resolve npm packages from the global cache). Defaults to <p(245)>auto</> when the flag is passed without a value."))
    .help_heading(DEPENDENCY_MANAGEMENT_HEADING)
}

fn node_modules_linker_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  let value =
    matches.remove_one::<NodeModulesLinkerMode>("node-modules-linker");
  if let Some(mode) = value {
    flags.node_modules_linker = Some(mode);
  }
}

fn node_modules_linker_arg() -> Arg {
  fn parse_node_modules_linker_mode(
    s: &str,
  ) -> Result<NodeModulesLinkerMode, String> {
    match s {
      "isolated" => Ok(NodeModulesLinkerMode::Isolated),
      "hoisted" => Ok(NodeModulesLinkerMode::Hoisted),
      _ => Err(format!(
        "Invalid value '{}': expected \"isolated\" or \"hoisted\"",
        s
      )),
    }
  }

  Arg::new("node-modules-linker")
    .long("node-modules-linker")
    .alias("linker")
    .num_args(1)
    .value_parser(clap::builder::ValueParser::new(
      parse_node_modules_linker_mode,
    ))
    .value_name("MODE")
    .require_equals(true)
    .help("Sets the linker mode for npm packages (isolated or hoisted)")
    .help_heading(DEPENDENCY_MANAGEMENT_HEADING)
}

fn vendor_arg() -> Arg {
  Arg::new("vendor")
    .long("vendor")
    .num_args(0..=1)
    .value_parser(value_parser!(bool))
    .default_missing_value("true")
    .require_equals(true)
    .help("Toggles local vendor folder usage for remote modules and a node_modules folder for npm packages")
    .help_heading(DEPENDENCY_MANAGEMENT_HEADING)
}

fn unsafely_ignore_certificate_errors_arg() -> Arg {
  Arg::new("unsafely-ignore-certificate-errors")
    .hide(true)
    .long("unsafely-ignore-certificate-errors")
    .num_args(0..)
    .use_value_delimiter(true)
    .require_equals(true)
    .value_name("HOSTNAMES")
    .help("DANGER: Disables verification of TLS certificates")
    .value_parser(flags_net::validator)
}

fn allow_scripts_arg() -> Arg {
  Arg::new("allow-scripts")
    .long("allow-scripts")
    .num_args(0..)
    .action(ArgAction::Append)
    .require_equals(true)
    .value_name("PACKAGE")
    .value_parser(parse_packages_allowed_scripts)
    .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`)</>"))
}

fn ignore_scripts_arg() -> Arg {
  Arg::new("ignore-scripts")
    .long("ignore-scripts")
    .num_args(0..)
    .action(ArgAction::Append)
    .require_equals(true)
    .value_name("PACKAGE")
    .value_parser(parse_packages_ignored_scripts)
    .help("Do not run npm lifecycle scripts for the given packages")
}

enum UnstableArgsConfig {
  // for backwards-compatability
  None,
  ResolutionOnly,
  ResolutionAndRuntime,
}

trait CommandExt {
  fn with_unstable_args(self, cfg: UnstableArgsConfig) -> Self;
}

impl CommandExt for Command {
  fn with_unstable_args(self, cfg: UnstableArgsConfig) -> Self {
    let mut next_display_order = {
      let mut value = 1000;
      move || {
        value += 1;
        value
      }
    };

    let mut cmd = self.arg(
      Arg::new("unstable")
      .long("unstable")
      .help(cstr!("The `--unstable` flag has been deprecated. Use granular `--unstable-*` flags instead
  <p(245)>To view the list of individual unstable feature flags, run this command again with --help=unstable</>"))
      .action(ArgAction::SetTrue)
      .hide(matches!(cfg, UnstableArgsConfig::None))
      .display_order(next_display_order())
    );

    for feature in deno_runtime::UNSTABLE_FEATURES.iter() {
      let mut arg = Arg::new(feature.flag_name)
        .long(feature.flag_name)
        .help(feature.help_text)
        .action(ArgAction::SetTrue)
        .value_parser(FalseyValueParser::new())
        .hide(true)
        .help_heading(UNSTABLE_HEADING)
        .display_order(next_display_order());

      // TODO(bartlomieju):
      // Value of `.long_help()` is actuall a metadata. It should be rewritten to use
      // Clap's `ArgExt` instead
      let mut long_help_val = None;

      if feature.show_in_help {
        if matches!(cfg, UnstableArgsConfig::ResolutionOnly)
          && matches!(feature.kind, UnstableFeatureKind::Cli)
        {
          long_help_val = Some("true");
        }

        if matches!(cfg, UnstableArgsConfig::ResolutionAndRuntime) {
          long_help_val = Some("true");
        }
      }

      if feature.flag_name == "unstable-sloppy-imports" {
        arg = arg.alias("sloppy-imports");
      }

      // `--unsafe-proto` is a stable shorthand for `--unstable-unsafe-proto`;
      // it enables the same behavior without being spelled as an unstable flag.
      if feature.flag_name == "unstable-unsafe-proto" {
        arg = arg.alias("unsafe-proto");
      }

      arg = arg.long_help(long_help_val);
      cmd = cmd.arg(arg);
    }

    cmd
  }
}

fn allow_scripts_arg_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  let Some(parts) = matches.remove_many::<String>("allow-scripts") else {
    return Ok(());
  };
  if parts.len() == 0 {
    flags.allow_scripts = PackagesAllowedScripts::All;
  } else {
    flags.allow_scripts = PackagesAllowedScripts::Some(
      parts
        .flat_map(flat_escape_split_commas)
        .map(|result| {
          let value = result?;
          let dep = JsrDepPackageReq::from_str_loose(&value).map_err(|e| {
            clap::Error::raw(clap::error::ErrorKind::InvalidValue, e)
          })?;
          if dep.kind != PackageKind::Npm {
            return Err(clap::Error::raw(
              clap::error::ErrorKind::InvalidValue,
              format!("Only npm package constraints are supported: {}", value),
            ));
          }
          if dep.req.version_req.tag().is_some() {
            return Err(clap::Error::raw(
              clap::error::ErrorKind::InvalidValue,
              format!("Tags are not supported in --allow-scripts: {}", value),
            ));
          }
          Ok(dep.req)
        })
        .collect::<Result<_, _>>()?,
    );
  }
  Ok(())
}

fn ignore_scripts_arg_parse(
  matches: &mut ArgMatches,
) -> clap::error::Result<PackagesAllowedScripts> {
  let Some(parts) = matches.remove_many::<String>("ignore-scripts") else {
    return Ok(PackagesAllowedScripts::None);
  };
  if parts.len() == 0 {
    Ok(PackagesAllowedScripts::All)
  } else {
    Ok(PackagesAllowedScripts::Some(
      parts
        .flat_map(flat_escape_split_commas)
        .map(|result| {
          let value = result?;
          let value = if value.starts_with("npm:") || value.starts_with("jsr:")
          {
            value
          } else {
            format!("npm:{}", value)
          };
          let dep = JsrDepPackageReq::from_str_loose(&value).map_err(|e| {
            clap::Error::raw(clap::error::ErrorKind::InvalidValue, e)
          })?;
          if dep.kind != PackageKind::Npm {
            return Err(clap::Error::raw(
              clap::error::ErrorKind::InvalidValue,
              format!("Only npm package constraints are supported: {}", value),
            ));
          }
          if dep.req.version_req.tag().is_some() {
            return Err(clap::Error::raw(
              clap::error::ErrorKind::InvalidValue,
              format!("Tags are not supported in --ignore-scripts: {}", value),
            ));
          }
          Ok(dep.req)
        })
        .collect::<Result<_, _>>()?,
    ))
  }
}

fn audit_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  lock_args_parse(flags, matches);
  env_file_arg_parse(flags, matches);
  let severity = matches
    .remove_one::<String>("level")
    .unwrap_or_else(|| "low".to_string());
  let ignore_unfixable = matches.get_flag("ignore-unfixable");
  let ignore_registry_errors = matches.get_flag("ignore-registry-errors");
  let socket = matches.get_flag("socket");
  let fix = matches.get_flag("fix")
    || matches
      .get_one::<String>("action")
      .map(|s| s == "fix")
      .unwrap_or(false);
  let dev = true;
  let prod = true;
  let optional = true;
  let ignore = matches
    .remove_many::<String>("ignore")
    .map(|v| v.collect())
    .unwrap_or_default();

  flags.subcommand = DenoSubcommand::Audit(AuditFlags {
    severity,
    dev,
    prod,
    optional,
    ignore_registry_errors,
    ignore_unfixable,
    ignore,
    socket,
    fix,
  });
  Ok(())
}

fn why_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  lock_args_parse(flags, matches);
  env_file_arg_parse(flags, matches);
  let package = matches.remove_one::<String>("package").unwrap();
  flags.subcommand = DenoSubcommand::Why(WhyFlags { package });
}

fn add_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  allow_scripts_arg_parse(flags, matches)?;
  allow_and_deny_import_parse(flags, matches)?;
  lock_args_parse(flags, matches);
  env_file_arg_parse(flags, matches);
  min_dep_age_arg_parse(flags, matches);
  flags.subcommand = DenoSubcommand::Add(add_parse_inner(matches, None));
  Ok(())
}

fn add_parse_inner(
  matches: &mut ArgMatches,
  packages: Option<clap::parser::Values<String>>,
) -> AddFlags {
  let packages = packages
    .unwrap_or_else(|| matches.remove_many::<String>("packages").unwrap())
    .collect();
  let dev = matches.get_flag("dev");
  let default_registry = if matches.get_flag("npm") {
    Some(DefaultRegistry::Npm)
  } else if matches.get_flag("jsr") {
    Some(DefaultRegistry::Jsr)
  } else {
    // Default to npm when no --npm or --jsr flag is provided.
    // This allows `deno add express` to work without requiring `npm:` prefix.
    Some(DefaultRegistry::Npm)
  };
  AddFlags {
    packages,
    dev,
    optional: matches.get_flag("save-optional"),
    no_save: matches.get_flag("no-save"),
    default_registry,
    lockfile_only: matches.get_flag("lockfile-only"),
    save_exact: matches.get_flag("save-exact"),
    package_json: matches.get_flag("package-json"),
    unscoped: matches.get_flag("unscoped"),
  }
}

fn remove_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  lock_args_parse(flags, matches);
  min_dep_age_arg_parse(flags, matches);
  let packages = matches.remove_many::<String>("packages").unwrap();

  // `deno remove --global <name>...` is an alias for `deno uninstall --global
  // <name>...`: it removes globally installed executables rather than
  // dependencies from the configuration file, so route it through the same
  // subcommand the uninstaller uses.
  if matches.get_flag("global") {
    let root = matches.remove_one::<String>("root");
    flags.subcommand = DenoSubcommand::Uninstall(UninstallFlags {
      kind: UninstallKind::Global(UninstallFlagsGlobal {
        packages: packages.collect(),
        root,
      }),
    });
    return Ok(());
  }

  flags.subcommand = DenoSubcommand::Remove(RemoveFlags {
    packages: packages.collect(),
    lockfile_only: matches.get_flag("lockfile-only"),
    package_json: matches.get_flag("package-json"),
  });
  Ok(())
}

fn link_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  lock_args_parse(flags, matches);
  flags.subcommand = DenoSubcommand::Link(LinkFlags {
    paths: matches.remove_many::<String>("paths").unwrap().collect(),
    lockfile_only: matches.get_flag("lockfile-only"),
  });
  Ok(())
}

fn unlink_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  lock_args_parse(flags, matches);
  flags.subcommand = DenoSubcommand::Unlink(UnlinkFlags {
    names_or_paths: matches
      .remove_many::<String>("names_or_paths")
      .unwrap()
      .collect(),
    lockfile_only: matches.get_flag("lockfile-only"),
  });
  Ok(())
}

fn bump_version_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  let increment =
    matches
      .remove_one::<String>("increment")
      .and_then(|s| match s.as_str() {
        "major" => Some(VersionIncrement::Major),
        "minor" => Some(VersionIncrement::Minor),
        "patch" => Some(VersionIncrement::Patch),
        "premajor" => Some(VersionIncrement::Premajor),
        "preminor" => Some(VersionIncrement::Preminor),
        "prepatch" => Some(VersionIncrement::Prepatch),
        "prerelease" => Some(VersionIncrement::Prerelease),
        _ => None,
      });

  let workspace = if matches.get_flag("workspace") {
    Some(true)
  } else if matches.get_flag("no-workspace") {
    Some(false)
  } else {
    None
  };

  flags.subcommand = DenoSubcommand::BumpVersion(VersionFlags {
    increment,
    workspace,
    start: matches.remove_one::<String>("start"),
    base: matches.remove_one::<String>("base"),
    import_map: matches.remove_one::<String>("import-map"),
    release_notes: matches.remove_one::<String>("release-notes"),
    dry_run: matches.get_flag("dry-run"),
    config: matches.remove_one::<String>("config"),
  });
}

fn outdated_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
  is_update_command: bool,
) -> clap::error::Result<()> {
  let filters = match matches.remove_many::<String>("filters") {
    Some(f) => f.collect(),
    None => vec![],
  };
  let recursive = matches.get_flag("recursive");
  let update = is_update_command || matches.get_flag("update");
  let kind = if update {
    let latest = matches.get_flag("latest");
    let interactive = matches.get_flag("interactive");
    OutdatedKind::Update {
      latest,
      interactive,
      lockfile_only: matches.get_flag("lockfile-only"),
    }
  } else {
    let compatible = matches.get_flag("compatible");
    OutdatedKind::PrintOutdated { compatible }
  };
  flags.subcommand = DenoSubcommand::Outdated(OutdatedFlags {
    filters,
    recursive,
    kind,
  });
  lock_args_parse(flags, matches);
  min_dep_age_arg_parse(flags, matches);
  env_file_arg_parse(flags, matches);
  Ok(())
}

fn list_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  let filters = match matches.remove_many::<String>("filters") {
    Some(f) => f.collect(),
    None => vec![],
  };
  flags.subcommand = DenoSubcommand::List(ListFlags {
    recursive: matches.get_flag("recursive"),
    depth: matches.remove_one::<u16>("depth").unwrap_or(0),
    prod: matches.get_flag("prod"),
    dev: matches.get_flag("dev"),
    filters,
  });
  Ok(())
}

fn bench_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  flags.type_check_mode = TypeCheckMode::Local;

  runtime_args_parse(flags, matches, true, false, true)?;
  ext_arg_parse(flags, matches);

  // NOTE: `deno bench` always uses `--no-prompt`, tests shouldn't ever do
  // interactive prompts, unless done by user code
  flags.permissions.no_prompt = true;

  let json = matches.get_flag("json");
  let ignore = match matches.remove_many::<String>("ignore") {
    Some(f) => f
      .flat_map(flat_escape_split_commas)
      .collect::<Result<_, _>>()?,
    None => vec![],
  };

  let filter = matches.remove_one::<String>("filter");

  if matches.contains_id("script_arg") {
    flags
      .argv
      .extend(matches.remove_many::<String>("script_arg").unwrap());
  }

  let include = match matches.remove_many::<String>("files") {
    Some(files) => files.collect(),
    _ => Vec::new(),
  };

  let no_run = matches.get_flag("no-run");

  flags.watch = watch_arg_parse_with_paths(matches)?;
  flags.subcommand = DenoSubcommand::Bench(BenchFlags {
    files: FileFlags { include, ignore },
    filter,
    json,
    no_run,
    permit_no_files: permit_no_files_parse(matches),
  });

  Ok(())
}

fn bundle_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  let file = matches.remove_many::<String>("file").unwrap();
  let output = matches.remove_one::<String>("output");
  let outdir = matches.remove_one::<String>("outdir");
  compile_args_without_check_parse(flags, matches)?;
  // `bundle_subcommand` advertises `--check`, but the parser dropped the
  // value so `flags.type_check_mode` stayed at its default (`None`),
  // which made the bundle silently skip type-checking even with
  // `--check=all` (denoland/deno#30159).
  check_arg_parse(flags, matches);
  unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionAndRuntime);
  allow_and_deny_import_parse(flags, matches)?;
  env_file_arg_parse(flags, matches);
  flags.subcommand = DenoSubcommand::Bundle(BundleFlags {
    entrypoints: file.collect(),
    watch: matches.get_flag("watch"),
    output_path: output,
    output_dir: outdir,
    external: matches
      .remove_many::<String>("external")
      .map(|f| f.collect::<Vec<_>>())
      .unwrap_or_default(),
    format: matches.remove_one::<BundleFormat>("format").unwrap(),
    packages: matches.remove_one::<PackageHandling>("packages").unwrap(),
    minify: matches.get_flag("minify"),
    keep_names: matches.get_flag("keep-names"),
    code_splitting: matches.get_flag("code-splitting"),
    inline_imports: matches.get_flag("inline-imports"),
    platform: matches.remove_one::<BundlePlatform>("platform").unwrap(),
    sourcemap: matches.remove_one::<SourceMapType>("sourcemap"),
    declaration: matches.get_flag("declaration"),
  });
  Ok(())
}

fn cache_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  compile_args_parse(flags, matches)?;
  unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionOnly);
  allow_scripts_arg_parse(flags, matches)?;
  allow_and_deny_import_parse(flags, matches)?;
  env_file_arg_parse(flags, matches);
  let files = matches.remove_many::<String>("file").unwrap().collect();
  flags.subcommand = DenoSubcommand::Cache(CacheFlags { files });
  Ok(())
}

fn check_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  flags.type_check_mode = TypeCheckMode::Local;
  compile_args_without_check_parse(flags, matches)?;
  unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionAndRuntime);
  v8_flags_arg_parse(flags, matches);
  let files = match matches.remove_many::<String>("file") {
    Some(f) => f.collect(),
    None => vec![".".to_string()], // default
  };
  if matches.get_flag("all") || matches.get_flag("remote") {
    flags.type_check_mode = TypeCheckMode::All;
  }
  if matches.get_flag("desktop") {
    flags.internal.is_desktop = true;
  }
  flags.watch = watch_arg_parse(matches)?;
  flags.subcommand = DenoSubcommand::Check(CheckFlags {
    files,
    doc: matches.get_flag("doc"),
    doc_only: matches.get_flag("doc-only"),
    check_js: matches.get_flag("check-js"),
  });
  flags.code_cache_enabled = !matches.get_flag("no-code-cache");
  allow_and_deny_import_parse(flags, matches)?;
  env_file_arg_parse(flags, matches);
  Ok(())
}

fn clean_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  let mut clean_flags = CleanFlags {
    except_paths: Vec::new(),
    dry_run: matches.get_flag("dry-run"),
  };
  if matches.get_flag("except") {
    clean_flags.except_paths = matches
      .remove_many::<String>("except-paths")
      .unwrap()
      .collect::<Vec<_>>();
    flags.cached_only = true;
    node_modules_and_vendor_dir_arg_parse(flags, matches);
  }
  flags.subcommand = DenoSubcommand::Clean(clean_flags);
}

fn compile_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  flags.type_check_mode = TypeCheckMode::Local;
  runtime_args_parse(flags, matches, true, false, true)?;

  if let Some(initial_cwd) = flags.initial_cwd.take() {
    // Usually we don't canonicalize because it's more sys calls, but with deno
    // compile we want to reduce the chance of the virtual file system having a
    // deep tree due to the initial directory being in some symlink somewhere.
    // This is really common when compiling on GitHub Actions for example, which
    // uses a symlinked directory for the cwd.
    flags.initial_cwd = Some(
      crate::util::fs::canonicalize_path(&initial_cwd)
        .ok()
        .unwrap_or(initial_cwd),
    );
  }

  let mut script = matches.remove_many::<String>("script_arg").unwrap();
  let source_file = script.next().unwrap();
  let args = script.collect();
  let output = matches.remove_one::<String>("output");
  let target = matches.remove_one::<String>("target");
  let engine = matches
    .remove_one::<String>("engine")
    .map(|value| value.parse().expect("engine is validated by clap"))
    .unwrap_or_default();
  flags.watch = watch_arg_parse(matches)?;
  let icon = matches.remove_one::<String>("icon");
  let no_terminal = matches.get_flag("no-terminal");
  let eszip = matches.get_flag("eszip-internal-do-not-use");
  let self_extracting = matches.get_flag("self-extracting");
  let bundle = matches.get_flag("bundle");
  let app_name = matches.remove_one::<String>("app-name");
  let minify = matches.get_flag("minify");
  let exclude_unused_npm = matches.get_flag("exclude-unused-npm");
  let include = matches
    .remove_many::<String>("include")
    .map(|f| f.collect::<Vec<_>>())
    .unwrap_or_default();
  let exclude = matches
    .remove_many::<String>("exclude")
    .map(|f| f.collect::<Vec<_>>())
    .unwrap_or_default();
  ext_arg_parse(flags, matches);

  flags.code_cache_enabled = !matches.get_flag("no-code-cache");

  flags.subcommand = DenoSubcommand::Compile(CompileFlags {
    source_file,
    output,
    args,
    target,
    no_terminal,
    icon,
    include,
    exclude,
    eszip,
    self_extracting,
    bundle,
    app_name,
    minify,
    exclude_unused_npm,
    engine,
  });

  Ok(())
}

fn desktop_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  flags.type_check_mode = TypeCheckMode::Local;
  runtime_args_parse(flags, matches, true, true, true)?;

  if let Some(initial_cwd) = flags.initial_cwd.take() {
    flags.initial_cwd = Some(
      crate::util::fs::canonicalize_path(&initial_cwd)
        .ok()
        .unwrap_or(initial_cwd),
    );
  }

  // The entrypoint is optional: a bare `deno desktop` defaults to "." so
  // framework detection runs against the current directory.
  let mut script = matches
    .remove_many::<String>("script_arg")
    .map(|s| s.collect::<Vec<_>>())
    .unwrap_or_default()
    .into_iter();
  let source_file = script.next().unwrap_or_else(|| ".".to_string());
  let args = script.collect();
  let output = matches.remove_one::<String>("output");
  let target = matches.remove_one::<String>("target");
  let icon = matches.remove_one::<String>("icon");
  let hmr = matches.get_flag("hmr");
  let backend = matches.remove_one::<String>("backend");
  let engine = matches
    .remove_one::<String>("engine")
    .map(|value| value.parse().expect("engine is validated by clap"))
    .unwrap_or_default();
  let all_targets = matches.get_flag("all-targets");
  // Self-extracting packaging is opt-in via `--compress [<fmt>]`. Bare
  // `--compress` defaults to xz (decompressed by the system `tar` with no
  // external tool); zstd is smaller/faster but needs the `zstd` binary at
  // runtime. The `lzma` alias normalizes to xz (same liblzma container).
  let compress = matches
    .remove_one::<String>("compress")
    .map(|c| if c == "lzma" { "xz".to_string() } else { c });
  let inspect_renderer = matches.remove_one::<SocketAddr>("inspect-renderer");
  let include = matches
    .remove_many::<String>("include")
    .map(|f| f.collect::<Vec<_>>())
    .unwrap_or_default();
  let exclude = matches
    .remove_many::<String>("exclude")
    .map(|f| f.collect::<Vec<_>>())
    .unwrap_or_default();
  let exclude_unused_npm = matches.get_flag("exclude-unused-npm");
  ext_arg_parse(flags, matches);

  flags.code_cache_enabled = !matches.get_flag("no-code-cache");

  flags.subcommand = DenoSubcommand::Desktop(DesktopFlags {
    source_file,
    output,
    args,
    target,
    icon: icon.map(IconConfig::Single),
    include,
    exclude,
    hmr,
    backend,
    engine,
    all_targets,
    identifier: None,
    deep_links: Vec::new(),
    codesign_identity: None,
    inspect_renderer,
    compress,
    exclude_unused_npm,
  });

  Ok(())
}

fn completions_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
  mut app: Command,
) {
  use clap_complete::aot::Bash;
  use clap_complete::aot::Fish;
  use clap_complete::aot::PowerShell;
  use clap_complete::aot::Zsh;
  use clap_complete::aot::generate;
  use clap_complete_fig::Fig;

  let mut buf: Vec<u8> = vec![];
  let name = "deno";

  let dynamic = matches.get_flag("dynamic");

  let shell = matches.get_one::<String>("shell").unwrap().as_str();

  if dynamic && matches!(shell, "bash" | "fish" | "zsh") {
    let shell = shell.to_string();
    flags.subcommand = DenoSubcommand::Completions(CompletionsFlags::Dynamic(
      Arc::new(move || {
        // SAFETY: unavoidable
        // Clap uses this to detect if it should generate dynamic completions, so if it isn't set, clap
        // will just bail out instead of actually printing out the completion command.
        unsafe {
          std::env::set_var("COMPLETE", &shell);
        }
        let cwd = resolve_cwd(None)?;
        handle_shell_completion_with_args(std::env::args_os().take(1), &cwd)?;
        Ok(())
      }),
    ));
    return;
  } else if dynamic {
    log::warn!(
      "dynamic completions are currently only supported for bash, fish, and zsh"
    );
  }

  match shell {
    "bash" => generate(Bash, &mut app, name, &mut buf),
    "fish" => generate(Fish, &mut app, name, &mut buf),
    "powershell" => generate(PowerShell, &mut app, name, &mut buf),
    "zsh" => {
      generate(Zsh, &mut app, name, &mut buf);
      // The hidden script_arg (for implicit run subcommand) must come AFTER _deno_commands.
      // Otherwise zsh parses 'run' into script_arg before trying subcommands.
      let content = String::from_utf8_lossy(&buf).into_owned();
      buf = content
        .replacen(
          "'::script_arg -- Script arg:_files' \\\n\":: :_deno_commands\"",
          "\":: :_deno_commands\" \\\n'::script_arg -- Script arg:_files'",
          1,
        )
        .into_bytes();
    }
    "fig" => generate(Fig, &mut app, name, &mut buf),
    _ => unreachable!(),
  }

  flags.subcommand = DenoSubcommand::Completions(CompletionsFlags::Static(
    buf.into_boxed_slice(),
  ));
}

fn coverage_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  let files = match matches.remove_many::<String>("files") {
    Some(f) => f.collect(),
    None => vec!["coverage".to_string()], // default
  };
  let ignore = match matches.remove_many::<String>("ignore") {
    Some(f) => f
      .flat_map(flat_escape_split_commas)
      .collect::<Result<Vec<_>, _>>()?,
    None => vec![],
  };
  let include = match matches.remove_many::<String>("include") {
    Some(f) => f.collect(),
    None => vec![],
  };
  let exclude = match matches.remove_many::<String>("exclude") {
    Some(f) => f.collect(),
    None => vec![],
  };
  let r#type = if matches.get_flag("lcov") {
    CoverageType::Lcov
  } else if matches.get_flag("html") {
    CoverageType::Html
  } else if matches.get_flag("detailed") {
    CoverageType::Detailed
  } else {
    CoverageType::Summary
  };
  let output = matches.remove_one::<String>("output");
  let threshold = matches.remove_one::<u32>("threshold");
  flags.subcommand = DenoSubcommand::Coverage(CoverageFlags {
    files: FileFlags {
      include: files,
      ignore,
    },
    output,
    include,
    exclude,
    r#type,
    threshold,
  });
  Ok(())
}

fn deploy_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
  sandbox: bool,
) -> clap::error::Result<()> {
  let mut args: Vec<String> = matches
    .remove_many("args")
    .map(|args| args.collect())
    .unwrap_or_default();

  if matches.contains_id("help") {
    args.push(String::from("--help"));
  }

  flags.argv = args;
  flags.subcommand = DenoSubcommand::Deploy(DeployFlags { sandbox });
  Ok(())
}

fn doc_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionOnly);
  import_map_arg_parse(flags, matches);
  reload_arg_parse(flags, matches)?;
  lock_args_parse(flags, matches);
  no_npm_arg_parse(flags, matches);
  no_remote_arg_parse(flags, matches);
  allow_and_deny_import_parse(flags, matches)?;
  env_file_arg_parse(flags, matches);

  let source_files_val = matches.remove_many::<String>("source_file");
  let source_files = if let Some(val) = source_files_val {
    let vals: Vec<String> = val.collect();

    if vals.len() == 1 {
      if vals[0] == "--builtin" {
        DocSourceFileFlag::Builtin
      } else {
        DocSourceFileFlag::Paths(vec![vals[0].to_string()])
      }
    } else {
      DocSourceFileFlag::Paths(
        vals.into_iter().filter(|v| v != "--builtin").collect(),
      )
    }
  } else {
    DocSourceFileFlag::Builtin
  };
  let private = matches.get_flag("private");
  let lint = matches.get_flag("lint");
  let json = matches.get_flag("json");
  let filter = matches.remove_one::<String>("filter");
  let html = if matches.get_flag("html") {
    let name = matches.remove_one::<String>("name");
    let category_docs_path = matches.remove_one::<String>("category-docs");
    let symbol_redirect_map_path =
      matches.remove_one::<String>("symbol-redirect-map");
    let strip_trailing_html = matches.get_flag("strip-trailing-html");
    let default_symbol_map_path =
      matches.remove_one::<String>("default-symbol-map");
    let output = matches
      .remove_one::<String>("output")
      .unwrap_or(String::from("./docs/"));
    Some(DocHtmlFlag {
      name,
      category_docs_path,
      symbol_redirect_map_path,
      default_symbol_map_path,
      strip_trailing_html,
      output,
    })
  } else {
    None
  };

  flags.subcommand = DenoSubcommand::Doc(DocFlags {
    source_files,
    json,
    lint,
    html,
    filter,
    private,
  });
  Ok(())
}

fn eval_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  runtime_args_parse(flags, matches, false, true, false)?;
  unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionAndRuntime);
  flags.allow_all();

  ext_arg_parse(flags, matches);
  flags.cpu_prof = cpu_prof_parse(matches);

  let print = matches.get_flag("print");
  let mut code_args = matches.remove_many::<String>("code_arg").unwrap();
  let code = code_args.next().unwrap();
  flags.argv.extend(code_args);

  flags.subcommand = DenoSubcommand::Eval(EvalFlags { print, code });
  Ok(())
}

fn fmt_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  config_args_parse(flags, matches);
  ext_arg_parse(flags, matches);

  let include = match matches.remove_many::<String>("files") {
    Some(f) => f.collect(),
    None => vec![],
  };
  let ignore = match matches.remove_many::<String>("ignore") {
    Some(f) => f
      .flat_map(flat_escape_split_commas)
      .collect::<Result<Vec<_>, _>>()?,
    None => vec![],
  };

  let use_tabs = matches.remove_one::<bool>("use-tabs");
  let line_width = matches.remove_one::<NonZeroU32>("line-width");
  let indent_width = matches.remove_one::<NonZeroU8>("indent-width");
  let single_quote = matches.remove_one::<bool>("single-quote");
  let prose_wrap = matches.remove_one::<String>("prose-wrap");
  let no_semicolons = matches.remove_one::<bool>("no-semicolons");
  let no_editorconfig = matches.get_flag("no-editorconfig");
  let unstable_component = matches.get_flag("unstable-component");
  let unstable_sql = matches.get_flag("unstable-sql");

  flags.subcommand = DenoSubcommand::Fmt(FmtFlags {
    check: matches.get_flag("check"),
    fail_fast: matches.get_flag("fail-fast"),
    files: FileFlags { include, ignore },
    permit_no_files: permit_no_files_parse(matches),
    use_tabs,
    line_width,
    indent_width,
    single_quote,
    prose_wrap,
    no_semicolons,
    no_editorconfig,
    unstable_component,
    unstable_sql,
  });
  flags.watch = watch_arg_parse_with_paths(matches)?;
  Ok(())
}

fn init_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> Result<(), clap::Error> {
  let mut lib = matches.get_flag("lib");
  let mut serve = matches.get_flag("serve");
  let mut empty = matches.get_flag("empty");
  let mut yes = matches.get_flag("yes");
  let mut dir = None;
  let mut package = None;
  let mut package_args = vec![];

  let use_npm = matches.get_flag("npm");
  let use_jsr = matches.get_flag("jsr");

  if let Some(mut args) = matches.remove_many::<String>("args") {
    let name = args.next().unwrap();
    let mut args = args.collect::<Vec<_>>();

    if use_npm {
      package = Some(format!(
        "npm:{}",
        name.strip_prefix("npm:").unwrap_or(&name)
      ));
      package_args = args;
    } else if use_jsr {
      package = Some(format!(
        "jsr:{}",
        name.strip_prefix("jsr:").unwrap_or(&name)
      ));
      package_args = args;
    } else {
      dir = Some(name);

      if !args.is_empty() {
        args.insert(0, "init".to_string());
        let inner_matches = init_subcommand().try_get_matches_from_mut(args)?;
        lib = inner_matches.get_flag("lib");
        serve = inner_matches.get_flag("serve");
        empty = inner_matches.get_flag("empty");
        yes = inner_matches.get_flag("yes");
      }
    }
  } else if use_npm {
    return Err(clap::Error::raw(
      clap::error::ErrorKind::MissingRequiredArgument,
      "Missing package name for --npm.",
    ));
  } else if use_jsr {
    return Err(clap::Error::raw(
      clap::error::ErrorKind::MissingRequiredArgument,
      "Missing package name for --jsr.",
    ));
  }

  flags.subcommand = DenoSubcommand::Init(InitFlags {
    package,
    package_args,
    dir,
    lib,
    serve,
    empty,
    yes,
  });

  Ok(())
}

fn create_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> Result<(), clap::Error> {
  let package = matches.remove_one::<String>("package").unwrap();
  let use_npm = matches.get_flag("npm");
  let use_jsr = matches.get_flag("jsr");
  let package_args = matches
    .remove_many::<String>("package_args")
    .map(|args| args.collect::<Vec<_>>())
    .unwrap_or_default();
  let package = normalize_create_package_name(package, use_npm, use_jsr)?;

  // `create` is a thin alias to init's package-generator path, not template mode.
  flags.subcommand = DenoSubcommand::Init(InitFlags {
    package: Some(package),
    package_args,
    dir: None,
    lib: false,
    serve: false,
    empty: false,
    yes: matches.get_flag("yes"),
  });

  Ok(())
}

fn normalize_create_package_name(
  package: String,
  use_npm: bool,
  use_jsr: bool,
) -> Result<String, clap::Error> {
  if let Some(name) = package.strip_prefix("jsr:") {
    if use_npm {
      Err(clap::Error::raw(
        clap::error::ErrorKind::InvalidValue,
        "Cannot use `--npm` with a `jsr:` specifier.",
      ))
    } else if name.is_empty() {
      Err(clap::Error::raw(
        clap::error::ErrorKind::InvalidValue,
        "Missing package name after 'jsr:' prefix.",
      ))
    } else {
      Ok(package)
    }
  } else if let Some(name) = package.strip_prefix("npm:") {
    if use_jsr {
      Err(clap::Error::raw(
        clap::error::ErrorKind::InvalidValue,
        "Cannot use `--jsr` with an `npm:` specifier.",
      ))
    } else if name.is_empty() {
      Err(clap::Error::raw(
        clap::error::ErrorKind::InvalidValue,
        "Missing package name after 'npm:' prefix.",
      ))
    } else {
      Ok(package)
    }
  } else if use_npm {
    Ok(format!("npm:{package}"))
  } else if use_jsr {
    Ok(format!("jsr:{package}"))
  } else {
    Err(clap::Error::raw(
      clap::error::ErrorKind::InvalidValue,
      "Missing `jsr:` or `npm:` prefix. For example: `deno create npm:vite`.",
    ))
  }
}

fn info_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionOnly);
  reload_arg_parse(flags, matches)?;
  config_args_parse(flags, matches);
  import_map_arg_parse(flags, matches);
  location_arg_parse(flags, matches);
  ca_file_arg_parse(flags, matches);
  unsafely_ignore_certificate_errors_parse(flags, matches)?;
  node_modules_and_vendor_dir_arg_parse(flags, matches);
  lock_args_parse(flags, matches);
  no_remote_arg_parse(flags, matches);
  no_npm_arg_parse(flags, matches);
  allow_and_deny_import_parse(flags, matches)?;
  min_dep_age_arg_parse(flags, matches);
  env_file_arg_parse(flags, matches);
  let json = matches.get_flag("json");
  flags.subcommand = DenoSubcommand::Info(InfoFlags {
    file: matches.remove_one::<String>("file"),
    json,
  });

  Ok(())
}

fn install_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
  app: Command,
) -> clap::error::Result<()> {
  runtime_args_parse(flags, matches, true, true, false)?;

  let global = matches.get_flag("global");
  allow_scripts_arg_parse(flags, matches)?;
  if global {
    let root = matches.remove_one::<String>("root");
    let force = matches.get_flag("force");
    let compile = matches.get_flag("compile");
    let name = matches.remove_one::<String>("name");
    let module_urls = matches
      .remove_many::<String>("cmd")
      .map(|values| values.collect::<Vec<_>>())
      .unwrap_or_default();
    let args = matches
      .remove_many::<String>("script_arg")
      .map(|values| values.collect::<Vec<_>>())
      .unwrap_or_default();

    if module_urls.len() > 1 && name.is_some() {
      return Err(clap::Error::raw(
        clap::error::ErrorKind::InvalidValue,
        format!(
          "Cannot specify --name when providing multiple packages to install ({}).",
          module_urls.join(", ")
        ),
      ));
    }

    if compile && module_urls.len() > 1 {
      return Err(clap::Error::raw(
        clap::error::ErrorKind::InvalidValue,
        format!(
          "Cannot compile multiple packages ({}).",
          module_urls.join(", ")
        ),
      ));
    }

    if compile {
      flags.type_check_mode = TypeCheckMode::Local;
    }

    flags.subcommand =
      DenoSubcommand::Install(InstallFlags::Global(InstallFlagsGlobal {
        name,
        module_urls,
        args,
        root,
        force,
        compile,
      }));

    return Ok(());
  }
  let lockfile_only = matches.get_flag("lockfile-only");
  let npm_target = NpmInstallTargetFlags {
    os: matches.remove_one::<String>("os"),
    arch: matches.remove_one::<String>("arch"),
  };
  let production = matches.get_flag("prod");
  let skip_types = matches.get_flag("skip-types");
  if matches.get_flag("entrypoint") {
    let entrypoints = matches.remove_many::<String>("cmd").unwrap_or_default();
    flags.subcommand = DenoSubcommand::Install(InstallFlags::Local(
      InstallFlagsLocal::Entrypoints(InstallEntrypointsFlags {
        entrypoints: entrypoints.collect(),
        lockfile_only,
        production,
        skip_types,
      }),
      npm_target,
    ));
  } else if let Some(add_files) = matches
    .remove_many("cmd")
    .map(|packages| add_parse_inner(matches, Some(packages)))
  {
    if matches!(flags.config_flag, ConfigFlag::Disabled) {
      return Err(app.override_usage("deno install [OPTIONS] [PACKAGE]...").error(
        clap::error::ErrorKind::ArgumentConflict,
        format!("deno install can't be used to add packages if `--no-config` is passed.\n{} to cache the packages without adding to a config, pass the `--entrypoint` flag", deno_terminal::colors::cyan("hint:")),
      ));
    }

    flags.subcommand = DenoSubcommand::Install(InstallFlags::Local(
      InstallFlagsLocal::Add(add_files),
      npm_target,
    ))
  } else {
    flags.subcommand = DenoSubcommand::Install(InstallFlags::Local(
      InstallFlagsLocal::TopLevel(InstallTopLevelFlags {
        lockfile_only,
        production,
        skip_types,
      }),
      npm_target,
    ));
  }
  Ok(())
}

fn ci_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionAndRuntime);
  env_file_arg_parse(flags, matches);
  flags.frozen_lockfile = Some(true);
  flags.subcommand = DenoSubcommand::Ci(CiFlags {
    production: matches.get_flag("prod"),
    skip_types: matches.get_flag("skip-types"),
  });
  Ok(())
}

fn json_reference_parse(
  flags: &mut Flags,
  _matches: &mut ArgMatches,
  mut app: Command,
) {
  use deno_core::serde_json::json;

  app.build();

  fn serialize_command(
    mut command: Command,
    top_level: bool,
  ) -> deno_core::serde_json::Value {
    let args = command
      .get_arguments()
      .filter(|arg| {
        !arg.is_hide_set()
          && if top_level {
            arg.is_global_set()
          } else {
            !arg.is_global_set()
          }
      })
      .map(|arg| {
        let name = arg.get_id().as_str();
        let short = arg.get_short();
        let long = arg.get_long();
        let required = arg.is_required_set();
        let help = arg.get_help().map(|help| help.ansi().to_string());
        let help_heading = arg
          .get_help_heading()
          .map(|help_heading| help_heading.to_string());
        let usage = arg.to_string();

        json!({
          "name": name,
          "short": short,
          "long": long,
          "required": required,
          "help": help,
          "help_heading": help_heading,
          "usage": usage,
        })
      })
      .collect::<Vec<_>>();

    let name = command.get_name().to_string();
    let about = command.get_about().map(|about| about.ansi().to_string());
    let usage = command.render_usage().ansi().to_string();

    let subcommands = command
      .get_subcommands()
      .map(|command| {
        serialize_command(
          if command
            .get_arguments()
            .any(|arg| arg.get_id().as_str() == "unstable")
          {
            enable_unstable(command.clone())
          } else {
            command.clone()
          },
          false,
        )
      })
      .collect::<Vec<_>>();

    let mut out = json!({
      "name": name,
      "about": about,
      "args": args,
      "subcommands": subcommands,
      "usage": usage,
    });

    if top_level {
      out
        .as_object_mut()
        .unwrap()
        .insert("env".to_string(), serde_json::to_value(ENV_VARS).unwrap());
    }

    out
  }

  flags.subcommand = DenoSubcommand::JSONReference(JSONReferenceFlags {
    json: serialize_command(app, true),
  })
}

fn jupyter_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionAndRuntime);

  let conn_file = matches.remove_one::<String>("conn");
  let kernel = matches.get_flag("kernel");
  let install = matches.get_flag("install");
  let display = matches.remove_one::<String>("display");
  let name = matches.remove_one::<String>("name");
  let force = matches.get_flag("force");

  flags.subcommand = DenoSubcommand::Jupyter(JupyterFlags {
    install,
    kernel,
    conn_file,
    name,
    display,
    force,
  });
}

fn uninstall_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  lock_args_parse(flags, matches);
  let name = matches.remove_one::<String>("name-or-package").unwrap();
  let packages: Vec<_> = vec![name]
    .into_iter()
    .chain(
      matches
        .remove_many::<String>("additional-packages")
        .unwrap_or_default(),
    )
    .collect();

  let kind = if matches.get_flag("global") {
    let root = matches.remove_one::<String>("root");
    UninstallKind::Global(UninstallFlagsGlobal { packages, root })
  } else {
    UninstallKind::Local(RemoveFlags {
      packages,
      lockfile_only: matches.get_flag("lockfile-only"),
      package_json: matches.get_flag("package-json"),
    })
  };

  flags.subcommand = DenoSubcommand::Uninstall(UninstallFlags { kind });
}

fn lsp_parse(flags: &mut Flags, _matches: &mut ArgMatches) {
  flags.subcommand = DenoSubcommand::Lsp;
}

fn lint_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionOnly);
  ext_arg_parse(flags, matches);
  config_args_parse(flags, matches);
  allow_and_deny_import_parse(flags, matches)?;

  let files = match matches.remove_many::<String>("files") {
    Some(f) => f.collect(),
    None => vec![],
  };
  let ignore = match matches.remove_many::<String>("ignore") {
    Some(f) => f
      .flat_map(flat_escape_split_commas)
      .collect::<Result<Vec<_>, _>>()?,
    None => vec![],
  };
  let fix = matches.get_flag("fix");
  let rules = matches.get_flag("rules");
  let maybe_rules_tags = matches
    .remove_many::<String>("rules-tags")
    .map(|f| f.collect());

  let maybe_rules_include = matches
    .remove_many::<String>("rules-include")
    .map(|f| f.collect());

  let maybe_rules_exclude = matches
    .remove_many::<String>("rules-exclude")
    .map(|f| f.collect());

  let json = matches.get_flag("json");
  let compact = matches.get_flag("compact");

  flags.subcommand = DenoSubcommand::Lint(LintFlags {
    files: FileFlags {
      include: files,
      ignore,
    },
    fix,
    rules,
    maybe_rules_tags,
    maybe_rules_include,
    maybe_rules_exclude,
    permit_no_files: permit_no_files_parse(matches),
    json,
    compact,
  });
  flags.watch = watch_arg_parse_with_paths(matches)?;
  Ok(())
}

fn repl_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionAndRuntime);
  compile_args_without_check_parse(flags, matches)?;
  cached_only_arg_parse(flags, matches);
  permission_args_parse(flags, matches)?;
  inspect_arg_parse(flags, matches);
  location_arg_parse(flags, matches);
  v8_flags_arg_parse(flags, matches);
  seed_arg_parse(flags, matches);
  enable_testing_features_arg_parse(flags, matches);
  env_file_arg_parse(flags, matches);
  trace_ops_parse(flags, matches);

  let eval_files = matches
    .remove_many::<String>("eval-file")
    .map(|values| {
      values
        .flat_map(flat_escape_split_commas)
        .collect::<Result<Vec<_>, _>>()
    })
    .transpose()?;

  if let Some(args) = matches.remove_many::<String>("args") {
    flags.argv.extend(args);
  }

  let json = matches.remove_one::<bool>("json").unwrap_or(false);

  handle_repl_flags(
    flags,
    ReplFlags {
      eval_files,
      eval: matches.remove_one::<String>("eval"),
      is_default_command: false,
      json,
    },
  );
  Ok(())
}

fn run_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
  app: Command,
  bare: bool,
  force_hmr: bool,
) -> clap::error::Result<()> {
  runtime_args_parse(flags, matches, true, true, true)?;
  ext_arg_parse(flags, matches);

  flags.tunnel = matches.get_flag("tunnel");
  if matches.get_flag("use-env-proxy") {
    // Node's --use-env-proxy is process-wide. Deno's node polyfills read the
    // same env variable as the Node tests when selecting global proxy config.
    // SAFETY: CLI parsing runs before worker startup and before Deno starts
    // any threads that could concurrently read the process environment.
    unsafe { std::env::set_var("NODE_USE_ENV_PROXY", "1") };
  } else if matches.get_flag("no-use-env-proxy") {
    // SAFETY: see the --use-env-proxy branch above.
    unsafe { std::env::set_var("NODE_USE_ENV_PROXY", "0") };
  }
  flags.code_cache_enabled = !matches.get_flag("no-code-cache");
  let coverage_dir = matches.remove_one::<String>("coverage");
  flags.cpu_prof = cpu_prof_parse(matches);

  match matches.remove_many::<String>("script_arg") {
    Some(mut script_arg) => {
      let script = script_arg.next().unwrap();
      flags.argv.extend(script_arg);
      let mut watch = watch_arg_parse_with_paths(matches)?;
      // `deno watch` is an alias for `deno run --watch-hmr`, so enable hot
      // module replacement watching by default when no explicit watch flag was
      // passed.
      if force_hmr {
        match &mut watch {
          Some(watch) => watch.hmr = true,
          None => {
            watch = Some(WatchFlagsWithPaths {
              hmr: true,
              paths: vec![],
              no_clear_screen: matches.get_flag("no-clear-screen"),
              exclude: matches
                .remove_many::<String>("watch-exclude")
                .map(|f| {
                  f.flat_map(flat_escape_split_commas)
                    .collect::<Result<_, _>>()
                })
                .transpose()?
                .unwrap_or_default(),
            });
          }
        }
      }
      if script == "-" && watch.is_some() {
        return Err(clap::Error::raw(
          clap::error::ErrorKind::ArgumentConflict,
          "--watch and --watch-hmr cannot be used while reading from stdin.\n",
        ));
      }
      flags.watch = watch;
      flags.subcommand = DenoSubcommand::Run(RunFlags {
        script,
        bare,
        coverage_dir,
        print_task_list: false,
      });
    }
    _ => {
      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 {
        // When no script argument is provided, show available tasks like `deno task`
        flags.subcommand = DenoSubcommand::Run(RunFlags {
          script: "".to_string(),
          bare: false,
          coverage_dir: None,
          print_task_list: true,
        });
      }
    }
  }

  Ok(())
}

fn serve_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
  app: Command,
) -> clap::error::Result<()> {
  // deno serve implies --allow-net=host:port
  let port = matches.remove_one::<u16>("port").unwrap_or(8000);
  let host = matches
    .remove_one::<String>("host")
    .unwrap_or_else(|| "0.0.0.0".to_owned());
  let open_site = matches.remove_one::<bool>("open").unwrap_or(false);

  runtime_args_parse(flags, matches, true, true, true)?;
  flags.code_cache_enabled = !matches.get_flag("no-code-cache");

  flags.tunnel = matches.get_flag("tunnel");

  let mut script_arg =
    matches.remove_many::<String>("script_arg").ok_or_else(|| {
      let mut app = app;
      let subcommand = &mut app.find_subcommand_mut("serve").unwrap();
      subcommand.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);
  flags.cpu_prof = cpu_prof_parse(matches);

  flags.watch = watch_arg_parse_with_paths(matches)?;
  flags.subcommand = DenoSubcommand::Serve(ServeFlags {
    script,
    port,
    host,
    parallel: matches.get_flag("parallel"),
    open_site,
  });

  Ok(())
}

fn task_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
  mut app: Command,
) -> clap::error::Result<()> {
  flags.config_flag = matches
    .remove_one::<String>("config")
    .map(ConfigFlag::Path)
    .unwrap_or(ConfigFlag::Discover);

  unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionAndRuntime);
  node_modules_arg_parse(flags, matches);
  node_modules_linker_arg_parse(flags, matches);
  lock_args_parse(flags, matches);
  env_file_arg_parse(flags, matches);

  let mut recursive = matches.get_flag("recursive");
  let members = matches.get_flag("members");
  let filter = if let Some(filter) = matches.remove_one::<String>("filter") {
    recursive = false;
    Some(filter)
  } else if recursive || members {
    Some("*".to_string())
  } else {
    None
  };

  flags.tunnel = matches.get_flag("tunnel");

  let mut task_flags = TaskFlags {
    cwd: matches.remove_one::<String>("cwd"),
    task: None,
    is_run: false,
    recursive,
    members,
    filter,
    eval: matches.get_flag("eval"),
    no_prefix: matches.get_flag("no-prefix"),
    concurrency: matches.remove_one::<NonZeroUsize>("jobs"),
    if_present: matches.get_flag("if-present"),
  };

  match matches.remove_subcommand() {
    Some((task, mut matches)) => {
      task_flags.task = Some(task);

      flags.argv.extend(
        matches
          .remove_many::<std::ffi::OsString>("")
          .into_iter()
          .flatten()
          .filter_map(|arg| arg.into_string().ok()),
      );
    }
    _ => {
      if task_flags.eval {
        return Err(app.find_subcommand_mut("task").unwrap().error(
          clap::error::ErrorKind::MissingRequiredArgument,
          "[TASK] must be specified when using --eval",
        ));
      }
    }
  }

  flags.subcommand = DenoSubcommand::Task(task_flags);
  Ok(())
}

pub fn handle_shell_completion(cwd: &Path) -> Result<(), AnyError> {
  handle_shell_completion_with_args(std::env::args_os(), cwd)
}

struct ZshCompleterUnsorted;

// dynamic completion implementation for zsh that retains the order we give completions to zsh
impl EnvCompleter for ZshCompleterUnsorted {
  fn name(&self) -> &'static str {
    "zsh"
  }

  fn is(&self, name: &str) -> bool {
    name == "zsh"
  }

  fn write_registration(
    &self,
    var: &str,
    name: &str,
    bin: &str,
    completer: &str,
    buf: &mut dyn std::io::Write,
  ) -> Result<(), std::io::Error> {
    // copy pasted from clap_complete::env::Zsh::write_registration and modified the script slightly
    let escaped_name = name.replace('-', "_");
    let bin = shlex::try_quote(bin).unwrap_or(std::borrow::Cow::Borrowed(bin));
    let completer = shlex::try_quote(completer)
      .unwrap_or(std::borrow::Cow::Borrowed(completer));

    let script = r#"#compdef BIN
function _clap_dynamic_completer_NAME() {
  local _CLAP_COMPLETE_INDEX=$(expr $CURRENT - 1)
  local _CLAP_IFS=$'\n'

  local completions=("${(@f)$( \
      _CLAP_IFS="$_CLAP_IFS" \
      _CLAP_COMPLETE_INDEX="$_CLAP_COMPLETE_INDEX" \
      VAR="zsh" \
      COMPLETER -- "${words[@]}" 2>/dev/null \
  )}")

  if [[ -n $completions ]]; then
      local -a dirs=()
      local -a other=()
      local completion
      for completion in $completions; do
          local value="${completion%%:*}"
          if [[ "$value" == */ ]]; then
              local dir_no_slash="${value%/}"
              if [[ "$completion" == *:* ]]; then
                  local desc="${completion#*:}"
                  dirs+=("$dir_no_slash:$desc")
              else
                  dirs+=("$dir_no_slash")
              fi
          else
              other+=("$completion")
          fi
      done
      [[ -n $dirs ]] && _describe -V 'values' dirs -o nosort -S '/' -r '/'
      [[ -n $other ]] && _describe -V 'values' other -o nosort
  fi
}

compdef _clap_dynamic_completer_NAME BIN"#
      .replace("NAME", &escaped_name)
      .replace("COMPLETER", &completer)
      .replace("BIN", &bin)
      .replace("VAR", var);

    writeln!(buf, "{script}")?;
    Ok(())
  }

  fn write_complete(
    &self,
    cmd: &mut clap::Command,
    args: Vec<OsString>,
    current_dir: Option<&std::path::Path>,
    buf: &mut dyn std::io::Write,
  ) -> Result<(), std::io::Error> {
    clap_complete::env::Zsh.write_complete(cmd, args, current_dir, buf)
  }
}

fn handle_shell_completion_with_args(
  args: impl IntoIterator<Item = OsString>,
  cwd: &Path,
) -> Result<(), AnyError> {
  let args = args.into_iter().collect::<Vec<_>>();
  let app = clap_root();

  let ran_completion = clap_complete::CompleteEnv::with_factory(|| app.clone())
    .shells(Shells(&[
      &clap_complete::env::Bash,
      &clap_complete::env::Elvish,
      &clap_complete::env::Fish,
      &clap_complete::env::Powershell,
      &ZshCompleterUnsorted,
    ]))
    .try_complete(args, Some(cwd))?;

  // we should only run this function when we're doing completions
  assert!(ran_completion);

  Ok(())
}

fn test_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  flags.type_check_mode = TypeCheckMode::Local;
  runtime_args_parse(flags, matches, true, true, true)?;
  ext_arg_parse(flags, matches);

  // NOTE: `deno test` always uses `--no-prompt`, tests shouldn't ever do
  // interactive prompts, unless done by user code
  flags.permissions.no_prompt = true;

  let ignore = match matches.remove_many::<String>("ignore") {
    Some(f) => f
      .flat_map(flat_escape_split_commas)
      .collect::<Result<_, _>>()?,
    None => vec![],
  };

  let no_run = matches.get_flag("no-run");
  let trace_leaks = matches.get_flag("trace-leaks");
  let sanitize_ops = matches.get_flag("sanitize-ops");
  let sanitize_resources = matches.get_flag("sanitize-resources");
  let doc = matches.get_flag("doc");
  let filter = matches.remove_one::<String>("filter");
  let clean = matches.get_flag("clean");

  let fail_fast = if matches.contains_id("fail-fast") {
    Some(
      matches
        .remove_one::<NonZeroUsize>("fail-fast")
        .unwrap_or_else(|| NonZeroUsize::new(1).unwrap()),
    )
  } else {
    None
  };

  let shuffle = if matches.contains_id("shuffle") {
    Some(
      matches
        .remove_one::<u64>("shuffle")
        .unwrap_or_else(rand::random),
    )
  } else {
    None
  };

  if let Some(script_arg) = matches.remove_many::<String>("script_arg") {
    flags.argv.extend(script_arg);
  }

  let include = match matches.remove_many::<String>("files") {
    Some(files) => files.collect(),
    _ => Vec::new(),
  };

  let junit_path = matches.remove_one::<String>("junit-path");

  let reporter =
    if let Some(reporter) = matches.remove_one::<String>("reporter") {
      match reporter.as_str() {
        "pretty" => TestReporterConfig::Pretty,
        "junit" => TestReporterConfig::Junit,
        "dot" => TestReporterConfig::Dot,
        "tap" => TestReporterConfig::Tap,
        _ => unreachable!(),
      }
    } else {
      TestReporterConfig::Pretty
    };

  if matches!(reporter, TestReporterConfig::Dot | TestReporterConfig::Tap) {
    flags.log_level = Some(Level::Error);
  }

  let hide_stacktraces = matches.get_flag("hide-stacktraces");
  let update_snapshots = matches.get_flag("update-snapshots");

  let changed = if matches.contains_id("changed") {
    Some(matches.remove_one::<String>("changed"))
  } else {
    None
  };

  let related = match matches.remove_many::<String>("related") {
    Some(f) => f
      .flat_map(flat_escape_split_commas)
      .collect::<Result<_, _>>()?,
    None => vec![],
  };

  flags.watch = watch_arg_parse_with_paths(matches)?;
  flags.subcommand = DenoSubcommand::Test(TestFlags {
    no_run,
    doc,
    coverage_dir: matches.remove_one::<String>("coverage"),
    coverage_raw_data_only: matches.get_flag("coverage-raw-data-only"),
    coverage_threshold: matches.remove_one::<u32>("coverage-threshold"),
    clean,
    fail_fast,
    files: FileFlags { include, ignore },
    filter,
    shuffle,
    retry: matches.remove_one::<u32>("retry").unwrap_or(0),
    repeats: matches.remove_one::<u32>("repeats").unwrap_or(0),
    shard: matches.remove_one::<(usize, usize)>("shard"),
    permit_no_files: permit_no_files_parse(matches),
    parallel: matches.get_flag("parallel"),
    trace_leaks,
    sanitize_ops,
    sanitize_resources,
    reporter,
    junit_path,
    hide_stacktraces,
    changed,
    related,
    update_snapshots,
  });
  Ok(())
}

fn transpile_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  compile_args_without_check_parse(flags, matches)?;
  unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionOnly);
  let files: Vec<String> = match matches.remove_many::<String>("file") {
    Some(f) => f.collect(),
    None => vec![],
  };
  let output = matches.remove_one::<String>("output");
  let output_dir = matches.remove_one::<String>("outdir");
  let source_map = match matches.remove_one::<String>("source-map").as_deref() {
    Some("inline") => SourceMapMode::Inline,
    Some("separate") => SourceMapMode::Separate,
    _ => SourceMapMode::None,
  };
  let declaration = matches.get_flag("declaration");
  flags.subcommand = DenoSubcommand::Transpile(TranspileFlags {
    files,
    output,
    output_dir,
    declaration,
    source_map,
  });
  Ok(())
}

fn types_parse(flags: &mut Flags, _matches: &mut ArgMatches) {
  flags.subcommand = DenoSubcommand::Types;
}

fn upgrade_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  ca_file_arg_parse(flags, matches);
  unsafely_ignore_certificate_errors_parse(flags, matches)?;

  let dry_run = matches.get_flag("dry-run");
  let force = matches.get_flag("force");
  let canary = matches.get_flag("canary");
  let release_candidate = matches.get_flag("release-candidate");
  let no_delta = matches.get_flag("no-delta");
  let version = matches.remove_one::<String>("version");
  let output = matches.remove_one::<String>("output");
  let positional_args: Vec<String> = matches
    .remove_many::<String>("version-or-hash-or-channel")
    .map(|v| v.collect())
    .unwrap_or_default();

  let first_arg = positional_args.first().map(|s| s.as_str());
  let (version_or_hash_or_channel, pr, branch) = if first_arg == Some("pr") {
    let pr_number = positional_args
      .get(1)
      .and_then(|s| s.strip_prefix('#').unwrap_or(s).parse::<u64>().ok());
    if pr_number.is_none() {
      return Err(clap::Error::raw(
        clap::error::ErrorKind::InvalidValue,
        "Missing or invalid PR number. Usage: deno upgrade pr <number>\n",
      ));
    }
    (None, pr_number, None)
  } else if first_arg == Some("compass") {
    (None, None, Some("compass".to_string()))
  } else {
    (positional_args.into_iter().next(), None, None)
  };

  let checksum = matches.remove_one::<String>("checksum");
  flags.subcommand = DenoSubcommand::Upgrade(UpgradeFlags {
    dry_run,
    force,
    release_candidate,
    canary,
    no_delta,
    version,
    output,
    version_or_hash_or_channel,
    checksum,
    pr,
    branch,
  });
  Ok(())
}

fn vendor_parse(flags: &mut Flags, _matches: &mut ArgMatches) {
  flags.subcommand = DenoSubcommand::Vendor
}

fn publish_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  flags.type_check_mode = TypeCheckMode::Local; // local by default
  unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionOnly);
  no_check_arg_parse(flags, matches);
  check_arg_parse(flags, matches);
  config_args_parse(flags, matches);
  env_file_arg_parse(flags, matches);

  flags.subcommand = DenoSubcommand::Publish(PublishFlags {
    token: matches.remove_one("token"),
    dry_run: matches.get_flag("dry-run"),
    allow_slow_types: matches.get_flag("allow-slow-types"),
    allow_dirty: matches.get_flag("allow-dirty"),
    no_provenance: matches.get_flag("no-provenance"),
    set_version: matches.remove_one::<String>("set-version"),
  });

  Ok(())
}

fn pack_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  flags.type_check_mode = TypeCheckMode::Local; // local by default
  unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionOnly);
  config_args_parse(flags, matches);
  env_file_arg_parse(flags, matches);

  let include = match matches.remove_many::<String>("files") {
    Some(f) => f.collect(),
    None => vec![],
  };
  let ignore = match matches.remove_many::<String>("ignore") {
    Some(f) => f
      .flat_map(flat_escape_split_commas)
      .collect::<Result<Vec<_>, _>>()?,
    None => vec![],
  };

  flags.subcommand = DenoSubcommand::Pack(PackFlags {
    files: FileFlags { include, ignore },
    output: matches.remove_one("output"),
    dry_run: matches.get_flag("dry-run"),
    allow_slow_types: matches.get_flag("allow-slow-types"),
    allow_dirty: matches.get_flag("allow-dirty"),
    set_version: matches.remove_one::<String>("set-version"),
    no_source_maps: matches.get_flag("no-source-maps"),
  });

  Ok(())
}

fn compile_args_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  compile_args_without_check_parse(flags, matches)?;
  no_check_arg_parse(flags, matches);
  check_arg_parse(flags, matches);
  Ok(())
}

fn compile_args_without_check_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  import_map_arg_parse(flags, matches);
  no_remote_arg_parse(flags, matches);
  no_npm_arg_parse(flags, matches);
  node_modules_and_vendor_dir_arg_parse(flags, matches);
  node_conditions_args_parse(flags, matches);
  config_args_parse(flags, matches);
  reload_arg_parse(flags, matches)?;
  lock_args_parse(flags, matches);
  ca_file_arg_parse(flags, matches);
  unsafely_ignore_certificate_errors_parse(flags, matches)?;
  min_dep_age_arg_parse(flags, matches);
  Ok(())
}

fn x_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  let kind = if let Some(shim_name) =
    matches.remove_one::<DenoXShimName>("install-alias")
  {
    XFlagsKind::InstallAlias(shim_name)
  } else if let Some(mut script_arg) =
    matches.remove_many::<String>("script_arg")
  {
    if let Some(command) = script_arg.next() {
      let yes = matches.get_flag("yes");
      let ignore_scripts = ignore_scripts_arg_parse(matches)?;
      let package = matches.remove_one::<String>("package");
      flags.argv.extend(script_arg);
      runtime_args_parse(flags, matches, true, true, true)?;
      if matches!(ignore_scripts, PackagesAllowedScripts::All)
        && !matches!(flags.allow_scripts, PackagesAllowedScripts::None)
      {
        return Err(clap::Error::raw(
          clap::error::ErrorKind::ArgumentConflict,
          "--ignore-scripts cannot be used with --allow-scripts",
        ));
      }
      XFlagsKind::Command(XCommandFlags {
        yes,
        command,
        ignore_scripts,
        package,
      })
    } else {
      XFlagsKind::Print
    }
  } else {
    XFlagsKind::Print
  };
  if !flags.permissions.has_permission() && flags.permission_set.is_none() {
    flags.permissions.allow_all = true;
  }
  flags.subcommand = DenoSubcommand::X(XFlags { kind });
  Ok(())
}

fn escape_and_split_commas(s: String) -> Result<Vec<String>, clap::Error> {
  let mut result = vec![];
  let mut current = String::new();
  let mut chars = s.chars();

  while let Some(c) = chars.next() {
    if c == ',' {
      if let Some(next) = chars.next() {
        if next == ',' {
          current.push(',');
        } else {
          if current.is_empty() {
            return Err(
              std::io::Error::other(String::from(
                "Empty values are not allowed",
              ))
              .into(),
            );
          }

          result.push(current.clone());
          current.clear();
          current.push(next);
        }
      } else {
        return Err(
          std::io::Error::other(String::from("Empty values are not allowed"))
            .into(),
        );
      }
    } else {
      current.push(c);
    }
  }

  if current.is_empty() {
    return Err(
      std::io::Error::other(String::from("Empty values are not allowed"))
        .into(),
    );
  }

  result.push(current);

  Ok(result)
}

fn flat_escape_split_commas(str: String) -> Vec<Result<String, clap::Error>> {
  match escape_and_split_commas(str) {
    Ok(vec) => vec.into_iter().map(Ok).collect::<Vec<_>>(),
    Err(e) => vec![Err(e)],
  }
}

fn permission_args_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  if let Some(set) = matches.remove_one::<String>("permission-set") {
    flags.permission_set = Some(set);
  }
  if let Some(read_wl) = matches.remove_many::<String>("allow-read") {
    let read_wl = read_wl
      .flat_map(flat_escape_split_commas)
      .collect::<Result<Vec<_>, _>>()?;
    flags.permissions.allow_read = Some(read_wl);
  }

  if let Some(read_wl) = matches.remove_many::<String>("deny-read") {
    let read_wl = read_wl
      .flat_map(flat_escape_split_commas)
      .collect::<Result<Vec<_>, _>>()?;
    flags.permissions.deny_read = Some(read_wl);
  }

  if let Some(read_wl) = matches.remove_many::<String>("ignore-read") {
    let read_wl = read_wl
      .flat_map(flat_escape_split_commas)
      .collect::<Result<Vec<_>, _>>()?;
    flags.permissions.ignore_read = Some(read_wl);
    debug!("read ignorelist: {:#?}", &flags.permissions.ignore_read);
  }

  if let Some(write_wl) = matches.remove_many::<String>("allow-write") {
    let write_wl = write_wl
      .flat_map(flat_escape_split_commas)
      .collect::<Result<Vec<_>, _>>()?;
    flags.permissions.allow_write = Some(write_wl);
  }

  if let Some(write_wl) = matches.remove_many::<String>("deny-write") {
    let write_wl = write_wl
      .flat_map(flat_escape_split_commas)
      .collect::<Result<Vec<_>, _>>()?;
    flags.permissions.deny_write = Some(write_wl);
  }

  if let Some(net_wl) = matches.remove_many::<String>("allow-net") {
    let net_allowlist = flags_net::parse(net_wl.collect())?;
    flags.permissions.allow_net = Some(net_allowlist);
  }

  if let Some(net_wl) = matches.remove_many::<String>("deny-net") {
    let net_denylist = flags_net::parse(net_wl.collect())?;
    flags.permissions.deny_net = Some(net_denylist);
  }

  if let Some(env_wl) = matches.remove_many::<String>("allow-env") {
    flags.permissions.allow_env = Some(env_wl.collect());
    debug!("env allowlist: {:#?}", &flags.permissions.allow_env);
  }

  if let Some(env_wl) = matches.remove_many::<String>("deny-env") {
    flags.permissions.deny_env = Some(env_wl.collect());
    debug!("env denylist: {:#?}", &flags.permissions.deny_env);
  }

  if let Some(env_wl) = matches.remove_many::<String>("ignore-env") {
    flags.permissions.ignore_env = Some(env_wl.collect());
    debug!("env ignorelist: {:#?}", &flags.permissions.ignore_env);
  }

  if let Some(run_wl) = matches.remove_many::<String>("allow-run") {
    flags.permissions.allow_run = Some(run_wl.collect());
    debug!("run allowlist: {:#?}", &flags.permissions.allow_run);
  }

  if let Some(run_wl) = matches.remove_many::<String>("deny-run") {
    flags.permissions.deny_run = Some(run_wl.collect());
    debug!("run denylist: {:#?}", &flags.permissions.deny_run);
  }

  if let Some(sys_wl) = matches.remove_many::<String>("allow-sys") {
    flags.permissions.allow_sys = Some(sys_wl.collect());
    debug!("sys info allowlist: {:#?}", &flags.permissions.allow_sys);
  }

  if let Some(sys_wl) = matches.remove_many::<String>("deny-sys") {
    flags.permissions.deny_sys = Some(sys_wl.collect());
    debug!("sys info denylist: {:#?}", &flags.permissions.deny_sys);
  }

  if let Some(ffi_wl) = matches.remove_many::<String>("allow-ffi") {
    let ffi_wl = ffi_wl
      .flat_map(flat_escape_split_commas)
      .collect::<Result<Vec<_>, _>>()?;
    flags.permissions.allow_ffi = Some(ffi_wl);
    debug!("ffi allowlist: {:#?}", &flags.permissions.allow_ffi);
  }

  if let Some(ffi_wl) = matches.remove_many::<String>("deny-ffi") {
    let ffi_wl = ffi_wl
      .flat_map(flat_escape_split_commas)
      .collect::<Result<Vec<_>, _>>()?;
    flags.permissions.deny_ffi = Some(ffi_wl);
    debug!("ffi denylist: {:#?}", &flags.permissions.deny_ffi);
  }

  if matches.get_flag("allow-hrtime") || matches.get_flag("deny-hrtime") {
    // use eprintln instead of log::warn because logging hasn't been initialized yet
    #[allow(clippy::print_stderr, reason = "can't use log crate yet")]
    {
      eprintln!(
        "{} `allow-hrtime` and `deny-hrtime` have been removed in Deno 2, as high resolution time is now always allowed",
        deno_runtime::colors::yellow("Warning")
      );
    }
  }

  if matches.get_count("allow-all") > 0 {
    flags.allow_all();
  }

  allow_and_deny_import_parse(flags, matches)?;

  if matches.get_flag("no-prompt") {
    flags.permissions.no_prompt = true;
  }

  Ok(())
}

fn allow_and_deny_import_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  if let Some(imports_wl) = matches.remove_many::<String>("allow-import") {
    let imports_allowlist = flags_net::parse(imports_wl.collect())?;
    flags.permissions.allow_import = Some(imports_allowlist);
  }
  if let Some(imports_wl) = matches.remove_many::<String>("deny-import") {
    let imports_denylist = flags_net::parse(imports_wl.collect())?;
    flags.permissions.deny_import = Some(imports_denylist);
  }
  Ok(())
}

fn unsafely_ignore_certificate_errors_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  if let Some(ic_wl) =
    matches.remove_many::<String>("unsafely-ignore-certificate-errors")
  {
    let ic_allowlist = flags_net::parse(ic_wl.collect())?;
    flags.unsafely_ignore_certificate_errors = Some(ic_allowlist);
  }
  Ok(())
}

fn runtime_args_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
  include_perms: bool,
  include_inspector: bool,
  include_allow_scripts: bool,
) -> clap::error::Result<()> {
  unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionAndRuntime);
  compile_args_parse(flags, matches)?;
  cached_only_arg_parse(flags, matches);
  if include_perms {
    permission_args_parse(flags, matches)?;
  }
  if include_inspector {
    inspect_arg_parse(flags, matches);
  }
  if include_allow_scripts {
    allow_scripts_arg_parse(flags, matches)?;
  }
  location_arg_parse(flags, matches);
  v8_flags_arg_parse(flags, matches);
  seed_arg_parse(flags, matches);
  enable_testing_features_arg_parse(flags, matches);
  env_file_arg_parse(flags, matches);
  trace_ops_parse(flags, matches);
  eszip_arg_parse(flags, matches);
  preload_arg_parse(flags, matches);
  require_arg_parse(flags, matches);
  Ok(())
}

fn eszip_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  if matches.get_flag("eszip-internal-do-not-use") {
    flags.eszip = true;
  }
}

fn inspect_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  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");
  flags.inspect_publish_uid =
    matches.remove_one::<InspectPublishUid>("inspect-publish-uid");
}

fn import_map_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  flags.import_map_path = matches.remove_one::<String>("import-map");
}

fn env_file_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  flags.env_file = matches
    .get_many::<String>("env-file")
    .map(|values| values.cloned().collect());
}

fn reload_arg_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) -> clap::error::Result<()> {
  if let Some(cache_bl) = matches.remove_many::<String>("reload") {
    let raw_cache_blocklist: Vec<String> = cache_bl
      .flat_map(flat_escape_split_commas)
      .map(|s| s.and_then(reload_arg_validate))
      .collect::<Result<Vec<_>, _>>()?;
    if raw_cache_blocklist.is_empty() {
      flags.reload = true;
    } else {
      flags.cache_blocklist = resolve_urls(raw_cache_blocklist);
      debug!("cache blocklist: {:#?}", &flags.cache_blocklist);
      flags.reload = false;
    }
  }

  Ok(())
}

fn preload_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  if let Some(preload) = matches.remove_many::<String>("preload") {
    flags.preload = preload.collect();
  }
}

fn require_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  if let Some(require) = matches.remove_many::<String>("require") {
    flags.require = require.collect();
  }
}

fn min_dep_age_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  flags.minimum_dependency_age = matches.remove_one("minimum-dependency-age");
}

fn ca_file_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  flags.ca_data = matches.remove_one::<String>("cert").and_then(CaData::parse);
}

fn enable_testing_features_arg_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) {
  if matches.get_flag("enable-testing-features-do-not-use") {
    flags.enable_testing_features = true
  }
}

fn trace_ops_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  if let Some(patterns) = matches.remove_many::<String>("trace-ops") {
    flags.trace_ops = Some(patterns.collect());
  }
}

fn cached_only_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  if matches.get_flag("cached-only") {
    flags.cached_only = true;
  }
}

fn ext_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  flags.ext = matches.remove_one::<String>("ext");
}

fn location_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  flags.location = matches.remove_one::<Url>("location");
}

fn v8_flags_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  if let Some(v8_flags) = matches.remove_many::<String>("v8-flags") {
    flags.v8_flags = v8_flags.collect();
  }
}

fn seed_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  if let Some(seed) = matches.remove_one::<u64>("seed") {
    flags.seed = Some(seed);

    flags.v8_flags.push(format!("--random-seed={seed}"));
  }
}

fn no_check_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  if let Some(cache_type) = matches.get_one::<String>("no-check") {
    match cache_type.as_str() {
      "remote" => flags.type_check_mode = TypeCheckMode::Local,
      _ => debug!(
        "invalid value for 'no-check' of '{}' using default",
        cache_type
      ),
    }
  } else if matches.contains_id("no-check") {
    flags.type_check_mode = TypeCheckMode::None;
  }
}

fn check_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  if let Some(cache_type) = matches.get_one::<String>("check") {
    match cache_type.as_str() {
      "all" => flags.type_check_mode = TypeCheckMode::All,
      _ => debug!(
        "invalid value for 'check' of '{}' using default",
        cache_type
      ),
    }
  } else if matches.contains_id("check") {
    flags.type_check_mode = TypeCheckMode::Local;
  }
}

fn lock_args_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  if matches.contains_id("lock") {
    let lockfile = matches.remove_one::<String>("lock").unwrap();
    flags.lock = Some(lockfile);
  }
  if matches.get_flag("no-lock") {
    flags.no_lock = true;
  }
  if let Some(&v) = matches.get_one::<bool>("frozen") {
    flags.frozen_lockfile = Some(v);
  }
}

fn node_conditions_args_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  if let Some(conditions) = matches.remove_many::<String>("conditions") {
    flags.node_conditions = conditions.collect();
  }
}

fn config_args_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  flags.config_flag = if matches.get_flag("no-config") {
    ConfigFlag::Disabled
  } else if let Some(config) = matches.remove_one::<String>("config") {
    ConfigFlag::Path(config)
  } else {
    ConfigFlag::Discover
  };
}

fn no_remote_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  if matches.get_flag("no-remote") {
    flags.no_remote = true;
  }
}

fn no_npm_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
  if matches.get_flag("no-npm") {
    flags.no_npm = true;
  }
}

fn node_modules_and_vendor_dir_arg_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
) {
  node_modules_arg_parse(flags, matches);
  node_modules_linker_arg_parse(flags, matches);
  flags.vendor = matches.remove_one::<bool>("vendor");
}

fn reload_arg_validate(urlstr: String) -> Result<String, clap::Error> {
  if urlstr.is_empty() {
    return Err(
      std::io::Error::other(String::from(
        "Missing url. Check for extra commas.",
      ))
      .into(),
    );
  }
  match Url::from_str(&urlstr) {
    Ok(_) => Ok(urlstr),
    Err(e) => Err(std::io::Error::other(e.to_string()).into()),
  }
}

fn permit_no_files_parse(matches: &mut ArgMatches) -> bool {
  matches.get_flag("permit-no-files")
}

fn watch_arg_parse(
  matches: &mut ArgMatches,
) -> clap::error::Result<Option<WatchFlagsWithPaths>> {
  if matches.get_flag("watch") {
    Ok(Some(WatchFlagsWithPaths {
      hmr: false,
      paths: vec![],
      no_clear_screen: matches.get_flag("no-clear-screen"),
      exclude: matches
        .remove_many::<String>("watch-exclude")
        .map(|f| {
          f.flat_map(flat_escape_split_commas)
            .collect::<Result<_, _>>()
        })
        .transpose()?
        .unwrap_or_default(),
    }))
  } else {
    Ok(None)
  }
}

fn watch_arg_parse_with_paths(
  matches: &mut ArgMatches,
) -> clap::error::Result<Option<WatchFlagsWithPaths>> {
  if let Some(paths) = matches.remove_many::<String>("watch") {
    return Ok(Some(WatchFlagsWithPaths {
      paths: paths
        .flat_map(flat_escape_split_commas)
        .collect::<Result<Vec<_>, _>>()?,
      hmr: false,
      no_clear_screen: matches.get_flag("no-clear-screen"),
      exclude: matches
        .remove_many::<String>("watch-exclude")
        .map(|f| {
          f.flat_map(flat_escape_split_commas)
            .collect::<Result<Vec<_>, _>>()
        })
        .transpose()?
        .unwrap_or_default(),
    }));
  }

  if matches.try_contains_id("hmr").is_ok() {
    return matches
      .remove_many::<String>("hmr")
      .map(|paths| {
        Ok(WatchFlagsWithPaths {
          paths: paths
            .flat_map(flat_escape_split_commas)
            .collect::<Result<Vec<_>, _>>()?,
          hmr: true,
          no_clear_screen: matches.get_flag("no-clear-screen"),
          exclude: matches
            .remove_many::<String>("watch-exclude")
            .map(|f| {
              f.flat_map(flat_escape_split_commas)
                .collect::<Result<Vec<_>, _>>()
            })
            .transpose()?
            .unwrap_or_default(),
        })
      })
      .transpose();
  }

  Ok(None)
}

fn unstable_args_parse(
  flags: &mut Flags,
  matches: &mut ArgMatches,
  cfg: UnstableArgsConfig,
) {
  // TODO(bartlomieju): remove in Deno 2.5
  if matches.get_flag("unstable") {
    flags.unstable_config.legacy_flag_enabled = true;
  }

  // TODO(bartlomieju): this should be factored out since these are configured via UNSTABLE_FEATURES
  flags.unstable_config.detect_cjs = matches.get_flag("unstable-detect-cjs");
  flags.unstable_config.lazy_dynamic_imports =
    matches.get_flag("unstable-lazy-dynamic-imports");
  flags.unstable_config.raw_imports = matches.get_flag("unstable-raw-imports");
  flags.unstable_config.sloppy_imports =
    matches.get_flag("unstable-sloppy-imports");
  flags.unstable_config.npm_lazy_caching =
    matches.get_flag("unstable-npm-lazy-caching");
  flags.unstable_config.tsgo = matches.get_flag("unstable-tsgo");

  if matches!(cfg, UnstableArgsConfig::ResolutionAndRuntime) {
    for feature in deno_runtime::UNSTABLE_FEATURES {
      if matches.get_flag(feature.flag_name) {
        flags
          .unstable_config
          .features
          .push(feature.name.to_string());
      }
    }
  }
}

// TODO(ry) move this to utility module and add test.
/// Strips fragment part of URL. Panics on bad URL.
pub fn resolve_urls(urls: Vec<String>) -> Vec<String> {
  let mut out: Vec<String> = vec![];
  for urlstr in urls.iter() {
    if let Ok(mut url) = Url::from_str(urlstr) {
      url.set_fragment(None);
      let mut full_url = String::from(url.as_str());
      if full_url.len() > 1 && full_url.ends_with('/') {
        full_url.pop();
      }
      out.push(full_url);
    } else {
      panic!("Bad Url: {urlstr}");
    }
  }
  out
}

#[cfg(test)]
mod tests {
  use deno_semver::package::PackageReq;
  use pretty_assertions::assert_eq;

  use super::*;

  /// Creates vector of strings, Vec<String>
  macro_rules! svec {
    ($($x:expr),* $(,)?) => (vec![$($x.to_string().into()),*]);
  }

  #[test]
  fn global_flags() {
    #[rustfmt::skip]
    let r = flags_from_vec(svec!["deno", "--log-level", "debug", "--quiet", "run", "script.ts"]);

    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string()
        )),
        log_level: Some(Level::Error),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
    #[rustfmt::skip]
    let r2 = flags_from_vec(svec!["deno", "run", "--log-level", "debug", "--quiet", "script.ts"]);
    let flags2 = r2.unwrap();
    assert_eq!(flags2, flags);
  }

  #[test]
  fn crlf_shebang_arg() {
    // A script saved with CRLF line endings whose first line is
    // `#!/usr/bin/env -S deno run --allow-net` is invoked by the kernel as
    // roughly `deno run --allow-net\r script.ts`. The stray `\r` on the last
    // shebang token must not break flag parsing.
    let r = flags_from_vec(svec!["deno", "run", "--allow-net\r", "script.ts"]);
    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string()
        )),
        permissions: PermissionFlags {
          allow_net: Some(vec![]),
          ..PermissionFlags::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn upgrade() {
    let r = flags_from_vec(svec!["deno", "upgrade", "--dry-run", "--force"]);
    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Upgrade(UpgradeFlags {
          force: true,
          dry_run: true,
          canary: false,
          no_delta: false,
          release_candidate: false,
          version: None,
          output: None,
          version_or_hash_or_channel: None,
          checksum: None,
          pr: None,
          branch: None,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn upgrade_with_output_flag() {
    let r = flags_from_vec(svec!["deno", "upgrade", "--output", "example.txt"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Upgrade(UpgradeFlags {
          force: false,
          dry_run: false,
          canary: false,
          no_delta: false,
          release_candidate: false,
          version: None,
          output: Some(String::from("example.txt")),
          version_or_hash_or_channel: None,
          checksum: None,
          pr: None,
          branch: None,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn version() {
    let r = flags_from_vec(svec!["deno", "--version"]);
    assert_eq!(
      r.unwrap_err().kind(),
      clap::error::ErrorKind::DisplayVersion
    );
    let r = flags_from_vec(svec!["deno", "-V"]);
    assert_eq!(
      r.unwrap_err().kind(),
      clap::error::ErrorKind::DisplayVersion
    );
  }

  #[test]
  fn run_reload() {
    let r = flags_from_vec(svec!["deno", "run", "-r", "script.ts"]);
    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string()
        )),
        reload: true,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_watch() {
    let r = flags_from_vec(svec!["deno", "run", "--watch", "script.ts"]);
    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: false,
          coverage_dir: None,
          print_task_list: false,
        }),
        code_cache_enabled: true,
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![],
          no_clear_screen: false,
          exclude: vec![],
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "--watch",
      "--no-clear-screen",
      "script.ts"
    ]);
    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: true,
          coverage_dir: None,
          print_task_list: false,
        }),
        code_cache_enabled: true,
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![],
          no_clear_screen: true,
          exclude: vec![],
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--watch-hmr",
      "--no-clear-screen",
      "script.ts"
    ]);
    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: false,
          coverage_dir: None,
          print_task_list: false,
        }),
        code_cache_enabled: true,
        watch: Some(WatchFlagsWithPaths {
          hmr: true,
          paths: vec![],
          no_clear_screen: true,
          exclude: vec![],
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--unstable-hmr",
      "--no-clear-screen",
      "script.ts"
    ]);
    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: false,
          coverage_dir: None,
          print_task_list: false,
        }),
        code_cache_enabled: true,
        watch: Some(WatchFlagsWithPaths {
          hmr: true,
          paths: vec![],
          no_clear_screen: true,
          exclude: vec![],
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--watch-hmr=foo.txt",
      "--no-clear-screen",
      "script.ts"
    ]);
    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: false,
          coverage_dir: None,
          print_task_list: false,
        }),
        code_cache_enabled: true,
        watch: Some(WatchFlagsWithPaths {
          hmr: true,
          paths: vec![String::from("foo.txt")],
          no_clear_screen: true,
          exclude: vec![],
        }),
        ..Flags::default()
      }
    );

    let r =
      flags_from_vec(svec!["deno", "run", "--hmr", "--watch", "script.ts"]);
    assert!(r.is_err());
  }

  #[test]
  fn watch_subcommand() {
    // `deno watch script.ts` is an alias for `deno run --watch-hmr script.ts`.
    let r = flags_from_vec(svec!["deno", "watch", "script.ts"]);
    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: false,
          coverage_dir: None,
          print_task_list: false,
        }),
        code_cache_enabled: true,
        watch: Some(WatchFlagsWithPaths {
          hmr: true,
          paths: vec![],
          no_clear_screen: false,
          exclude: vec![],
        }),
        ..Flags::default()
      }
    );

    // Additional watched paths and watch options are still respected.
    let r = flags_from_vec(svec![
      "deno",
      "watch",
      "--watch-hmr=foo.txt",
      "--no-clear-screen",
      "--watch-exclude=bar.txt",
      "script.ts"
    ]);
    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: false,
          coverage_dir: None,
          print_task_list: false,
        }),
        code_cache_enabled: true,
        watch: Some(WatchFlagsWithPaths {
          hmr: true,
          paths: vec![String::from("foo.txt")],
          no_clear_screen: true,
          exclude: vec![String::from("bar.txt")],
        }),
        ..Flags::default()
      }
    );

    // `--watch-exclude` is honored even without an explicit `--watch-hmr` flag.
    let r = flags_from_vec(svec![
      "deno",
      "watch",
      "--watch-exclude=bar.txt",
      "script.ts"
    ]);
    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: false,
          coverage_dir: None,
          print_task_list: false,
        }),
        code_cache_enabled: true,
        watch: Some(WatchFlagsWithPaths {
          hmr: true,
          paths: vec![],
          no_clear_screen: false,
          exclude: vec![String::from("bar.txt")],
        }),
        ..Flags::default()
      }
    );

    // Reading from stdin while watching is not supported.
    let r = flags_from_vec(svec!["deno", "watch", "-"]);
    assert!(r.is_err());
  }

  #[test]
  fn run_watch_with_external() {
    let r = flags_from_vec(svec!["deno", "--watch=file1,file2", "script.ts"]);
    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: true,
          coverage_dir: None,
          print_task_list: false,
        }),
        code_cache_enabled: true,
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![String::from("file1"), String::from("file2")],
          no_clear_screen: false,
          exclude: vec![],
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_watch_with_no_clear_screen() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--watch",
      "--no-clear-screen",
      "script.ts"
    ]);

    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: false,
          coverage_dir: None,
          print_task_list: false,
        }),
        code_cache_enabled: true,
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![],
          no_clear_screen: true,
          exclude: vec![],
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_watch_with_excluded_paths() {
    let r = flags_from_vec(svec!(
      "deno",
      "--watch",
      "--watch-exclude=foo",
      "script.ts"
    ));

    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: true,
          coverage_dir: None,
          print_task_list: false,
        }),
        code_cache_enabled: true,
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![],
          no_clear_screen: false,
          exclude: vec![String::from("foo")],
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!(
      "deno",
      "run",
      "--watch=foo",
      "--watch-exclude=bar",
      "script.ts"
    ));
    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: false,
          coverage_dir: None,
          print_task_list: false,
        }),
        code_cache_enabled: true,
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![String::from("foo")],
          no_clear_screen: false,
          exclude: vec![String::from("bar")],
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--watch",
      "--watch-exclude=foo,bar",
      "script.ts"
    ]);

    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: false,
          coverage_dir: None,
          print_task_list: false,
        }),
        code_cache_enabled: true,
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![],
          no_clear_screen: false,
          exclude: vec![String::from("foo"), String::from("bar")],
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "--watch=foo,bar",
      "--watch-exclude=baz,qux",
      "script.ts"
    ]);

    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: true,
          coverage_dir: None,
          print_task_list: false,
        }),
        code_cache_enabled: true,
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![String::from("foo"), String::from("bar")],
          no_clear_screen: false,
          exclude: vec![String::from("baz"), String::from("qux"),],
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_watch_with_stdin_is_error() {
    // `--watch` and `--watch-hmr` cannot be combined with reading from stdin,
    // previously `--watch-hmr -` panicked and `--watch -` was silently ignored.
    for watch_flag in ["--watch", "--watch-hmr", "--unstable-hmr"] {
      let r = flags_from_vec(svec!["deno", "run", watch_flag, "-"]);
      assert_eq!(
        r.unwrap_err().kind(),
        clap::error::ErrorKind::ArgumentConflict,
        "expected conflict for {watch_flag}"
      );
    }

    // a regular script with the same flags must still parse fine
    let r = flags_from_vec(svec!["deno", "run", "--watch-hmr", "script.ts"]);
    assert!(r.is_ok());
  }

  #[test]
  fn run_reload_allow_write() {
    let r =
      flags_from_vec(svec!["deno", "run", "-r", "--allow-write", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        reload: true,
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string()
        )),
        permissions: PermissionFlags {
          allow_write: Some(vec![]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_coverage() {
    let r = flags_from_vec(svec!["deno", "run", "--coverage=foo", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: false,
          coverage_dir: Some("foo".to_string()),
          print_task_list: false,
        }),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_v8_flags() {
    let r = flags_from_vec(svec!["deno", "run", "--v8-flags=--help"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default("_".to_string())),
        v8_flags: svec!["--help"],
        code_cache_enabled: true,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--v8-flags=--expose-gc,--gc-stats=1",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        v8_flags: svec!["--expose-gc", "--gc-stats=1"],
        code_cache_enabled: true,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "run", "--v8-flags=--expose-gc"]);
    assert!(r.is_ok());
  }

  #[test]
  fn serve_flags() {
    let r = flags_from_vec(svec!["deno", "serve", "main.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Serve(ServeFlags::new_default(
          "main.ts".to_string(),
          8000,
          "0.0.0.0"
        )),
        permissions: PermissionFlags {
          allow_net: None,
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
    let r = flags_from_vec(svec!["deno", "serve", "--port", "5000", "main.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Serve(ServeFlags::new_default(
          "main.ts".to_string(),
          5000,
          "0.0.0.0"
        )),
        permissions: PermissionFlags {
          allow_net: None,
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
    let r = flags_from_vec(svec![
      "deno",
      "serve",
      "--port",
      "5000",
      "--allow-net=example.com",
      "main.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Serve(ServeFlags::new_default(
          "main.ts".to_string(),
          5000,
          "0.0.0.0"
        )),
        permissions: PermissionFlags {
          allow_net: Some(vec!["example.com".to_string(),]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
    let r = flags_from_vec(svec![
      "deno",
      "serve",
      "--port",
      "5000",
      "--allow-net",
      "main.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Serve(ServeFlags::new_default(
          "main.ts".to_string(),
          5000,
          "0.0.0.0"
        )),
        permissions: PermissionFlags {
          allow_net: Some(vec![]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn has_permission() {
    let r = flags_from_vec(svec!["deno", "--allow-read", "x.ts"]);
    assert_eq!(r.unwrap().has_permission(), true);

    let r = flags_from_vec(svec!["deno", "run", "--deny-read", "x.ts"]);
    assert_eq!(r.unwrap().has_permission(), true);

    let r = flags_from_vec(svec!["deno", "run", "x.ts"]);
    assert_eq!(r.unwrap().has_permission(), false);
  }

  #[test]
  fn has_permission_in_argv() {
    let r = flags_from_vec(svec!["deno", "run", "x.ts", "--allow-read"]);
    assert_eq!(r.unwrap().has_permission_in_argv(), true);

    let r = flags_from_vec(svec!["deno", "x.ts", "--deny-read"]);
    assert_eq!(r.unwrap().has_permission_in_argv(), true);

    let r = flags_from_vec(svec!["deno", "run", "x.ts"]);
    assert_eq!(r.unwrap().has_permission_in_argv(), false);
  }

  #[test]
  fn script_args() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--allow-net",
      "gist.ts",
      "--title",
      "X"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "gist.ts".to_string()
        )),
        argv: svec!["--title", "X"],
        permissions: PermissionFlags {
          allow_net: Some(vec![]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn allow_all() {
    let r = flags_from_vec(svec!["deno", "run", "--allow-all", "gist.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "gist.ts".to_string()
        )),
        permissions: PermissionFlags {
          allow_all: true,
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn allow_read() {
    let r = flags_from_vec(svec!["deno", "run", "--allow-read", "gist.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "gist.ts".to_string()
        )),
        permissions: PermissionFlags {
          allow_read: Some(vec![]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn short_permission_flags() {
    let r = flags_from_vec(svec!["deno", "run", "-RNESWI", "gist.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "gist.ts".to_string()
        )),
        permissions: PermissionFlags {
          allow_read: Some(vec![]),
          allow_write: Some(vec![]),
          allow_env: Some(vec![]),
          allow_import: Some(vec![]),
          allow_net: Some(vec![]),
          allow_sys: Some(vec![]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn deny_read() {
    let r = flags_from_vec(svec!["deno", "--deny-read", "gist.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "gist.ts".to_string(),
          bare: true,
          coverage_dir: None,
          print_task_list: false,
        }),
        permissions: PermissionFlags {
          deny_read: Some(vec![]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn double_hyphen() {
    // notice that flags passed after double dash will not
    // be parsed to Flags but instead forwarded to
    // script args as Deno.args
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--allow-write",
      "script.ts",
      "--",
      "-D",
      "--allow-net"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        argv: svec!["--", "-D", "--allow-net"],
        permissions: PermissionFlags {
          allow_write: Some(vec![]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn fmt() {
    let r = flags_from_vec(svec!["deno", "fmt", "script_1.ts", "script_2.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Fmt(FmtFlags {
          check: false,
          fail_fast: false,
          files: FileFlags {
            include: vec!["script_1.ts".to_string(), "script_2.ts".to_string()],
            ignore: vec![],
          },
          permit_no_files: false,
          use_tabs: None,
          line_width: None,
          indent_width: None,
          single_quote: None,
          prose_wrap: None,
          no_semicolons: None,
          no_editorconfig: false,
          unstable_component: false,
          unstable_sql: false,
        }),
        ..Flags::default()
      }
    );

    let r =
      flags_from_vec(svec!["deno", "fmt", "--permit-no-files", "--check"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Fmt(FmtFlags {
          check: true,
          fail_fast: false,
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          permit_no_files: true,
          use_tabs: None,
          line_width: None,
          indent_width: None,
          single_quote: None,
          prose_wrap: None,
          no_semicolons: None,
          no_editorconfig: false,
          unstable_component: false,
          unstable_sql: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "fmt"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Fmt(FmtFlags {
          check: false,
          fail_fast: false,
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          permit_no_files: false,
          use_tabs: None,
          line_width: None,
          indent_width: None,
          single_quote: None,
          prose_wrap: None,
          no_semicolons: None,
          no_editorconfig: false,
          unstable_component: false,
          unstable_sql: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "fmt", "--watch"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Fmt(FmtFlags {
          check: false,
          fail_fast: false,
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          permit_no_files: false,
          use_tabs: None,
          line_width: None,
          indent_width: None,
          single_quote: None,
          prose_wrap: None,
          no_semicolons: None,
          no_editorconfig: false,
          unstable_component: false,
          unstable_sql: false,
        }),
        watch: Some(Default::default()),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "fmt", "--watch=foo,bar"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Fmt(FmtFlags {
          ..Default::default()
        }),
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![String::from("foo"), String::from("bar")],
          no_clear_screen: false,
          exclude: vec![],
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "fmt",
      "--watch",
      "--no-clear-screen",
      "--unstable-css",
      "--unstable-html",
      "--unstable-component",
      "--unstable-yaml",
      "--unstable-sql"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Fmt(FmtFlags {
          check: false,
          fail_fast: false,
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          permit_no_files: false,
          use_tabs: None,
          line_width: None,
          indent_width: None,
          single_quote: None,
          prose_wrap: None,
          no_semicolons: None,
          no_editorconfig: false,
          unstable_component: true,
          unstable_sql: true,
        }),
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![],
          no_clear_screen: true,
          exclude: vec![],
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "fmt",
      "--check",
      "--watch",
      "foo.ts",
      "--ignore=bar.js"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Fmt(FmtFlags {
          check: true,
          fail_fast: false,
          files: FileFlags {
            include: vec!["foo.ts".to_string()],
            ignore: vec!["bar.js".to_string()],
          },
          permit_no_files: false,
          use_tabs: None,
          line_width: None,
          indent_width: None,
          single_quote: None,
          prose_wrap: None,
          no_semicolons: None,
          no_editorconfig: false,
          unstable_component: false,
          unstable_sql: false,
        }),
        watch: Some(Default::default()),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "fmt", "--config", "deno.jsonc"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Fmt(FmtFlags {
          check: false,
          fail_fast: false,
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          permit_no_files: false,
          use_tabs: None,
          line_width: None,
          indent_width: None,
          single_quote: None,
          prose_wrap: None,
          no_semicolons: None,
          no_editorconfig: false,
          unstable_component: false,
          unstable_sql: false,
        }),
        config_flag: ConfigFlag::Path("deno.jsonc".to_string()),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "fmt",
      "--config",
      "deno.jsonc",
      "--watch",
      "foo.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Fmt(FmtFlags {
          check: false,
          fail_fast: false,
          files: FileFlags {
            include: vec!["foo.ts".to_string()],
            ignore: vec![],
          },
          permit_no_files: false,
          use_tabs: None,
          line_width: None,
          indent_width: None,
          single_quote: None,
          prose_wrap: None,
          no_semicolons: None,
          no_editorconfig: false,
          unstable_component: false,
          unstable_sql: false,
        }),
        config_flag: ConfigFlag::Path("deno.jsonc".to_string()),
        watch: Some(Default::default()),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "fmt",
      "--use-tabs",
      "--line-width",
      "60",
      "--indent-width",
      "4",
      "--single-quote",
      "--prose-wrap",
      "never",
      "--no-semicolons",
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Fmt(FmtFlags {
          check: false,
          fail_fast: false,
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          permit_no_files: false,
          use_tabs: Some(true),
          line_width: Some(NonZeroU32::new(60).unwrap()),
          indent_width: Some(NonZeroU8::new(4).unwrap()),
          single_quote: Some(true),
          prose_wrap: Some("never".to_string()),
          no_semicolons: Some(true),
          no_editorconfig: false,
          unstable_component: false,
          unstable_sql: false,
        }),
        ..Flags::default()
      }
    );

    // try providing =false to the booleans
    let r = flags_from_vec(svec![
      "deno",
      "fmt",
      "--use-tabs=false",
      "--single-quote=false",
      "--no-semicolons=false",
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Fmt(FmtFlags {
          check: false,
          fail_fast: false,
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          permit_no_files: false,
          use_tabs: Some(false),
          line_width: None,
          indent_width: None,
          single_quote: Some(false),
          prose_wrap: None,
          no_semicolons: Some(false),
          no_editorconfig: false,
          unstable_component: false,
          unstable_sql: false,
        }),
        ..Flags::default()
      }
    );

    // --no-editorconfig opts out of reading .editorconfig
    let r = flags_from_vec(svec!["deno", "fmt", "--no-editorconfig"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Fmt(FmtFlags {
          check: false,
          fail_fast: false,
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          permit_no_files: false,
          use_tabs: None,
          line_width: None,
          indent_width: None,
          single_quote: None,
          prose_wrap: None,
          no_semicolons: None,
          no_editorconfig: true,
          unstable_component: false,
          unstable_sql: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "fmt", "--ext", "html"]);
    assert!(r.is_err());
    let r = flags_from_vec(svec!["deno", "fmt", "--ext", "html", "./**"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Fmt(FmtFlags {
          check: false,
          fail_fast: false,
          files: FileFlags {
            include: vec!["./**".to_string()],
            ignore: vec![],
          },
          permit_no_files: false,
          use_tabs: None,
          line_width: None,
          indent_width: None,
          single_quote: None,
          prose_wrap: None,
          no_semicolons: None,
          no_editorconfig: false,
          unstable_component: false,
          unstable_sql: false,
        }),
        ext: Some("html".to_string()),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn lint() {
    let r = flags_from_vec(svec!["deno", "lint", "script_1.ts", "script_2.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Lint(LintFlags {
          files: FileFlags {
            include: vec!["script_1.ts".to_string(), "script_2.ts".to_string(),],
            ignore: vec![],
          },
          fix: false,
          rules: false,
          maybe_rules_tags: None,
          maybe_rules_include: None,
          maybe_rules_exclude: None,
          permit_no_files: false,
          json: false,
          compact: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "lint",
      "--permit-no-files",
      "--allow-import",
      "--watch",
      "script_1.ts",
      "script_2.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Lint(LintFlags {
          files: FileFlags {
            include: vec!["script_1.ts".to_string(), "script_2.ts".to_string()],
            ignore: vec![],
          },
          fix: false,
          rules: false,
          maybe_rules_tags: None,
          maybe_rules_include: None,
          maybe_rules_exclude: None,
          permit_no_files: true,
          json: false,
          compact: false,
        }),
        permissions: PermissionFlags {
          allow_import: Some(vec![]),
          ..Default::default()
        },
        watch: Some(Default::default()),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "lint", "--watch=foo,bar"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Lint(LintFlags {
          ..Default::default()
        }),
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![String::from("foo"), String::from("bar")],
          no_clear_screen: false,
          exclude: vec![],
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "lint",
      "--watch",
      "--no-clear-screen",
      "script_1.ts",
      "script_2.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Lint(LintFlags {
          files: FileFlags {
            include: vec!["script_1.ts".to_string(), "script_2.ts".to_string()],
            ignore: vec![],
          },
          fix: false,
          rules: false,
          maybe_rules_tags: None,
          maybe_rules_include: None,
          maybe_rules_exclude: None,
          permit_no_files: false,
          json: false,
          compact: false,
        }),
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![],
          no_clear_screen: true,
          exclude: vec![],
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "lint",
      "--fix",
      "--ignore=script_1.ts,script_2.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Lint(LintFlags {
          files: FileFlags {
            include: vec![],
            ignore: vec!["script_1.ts".to_string(), "script_2.ts".to_string()],
          },
          fix: true,
          rules: false,
          maybe_rules_tags: None,
          maybe_rules_include: None,
          maybe_rules_exclude: None,
          permit_no_files: false,
          json: false,
          compact: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "lint", "--rules"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Lint(LintFlags {
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          fix: false,
          rules: true,
          maybe_rules_tags: None,
          maybe_rules_include: None,
          maybe_rules_exclude: None,
          permit_no_files: false,
          json: false,
          compact: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "lint",
      "--rules",
      "--rules-tags=recommended"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Lint(LintFlags {
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          fix: false,
          rules: true,
          maybe_rules_tags: Some(svec!["recommended"]),
          maybe_rules_include: None,
          maybe_rules_exclude: None,
          permit_no_files: false,
          json: false,
          compact: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "lint",
      "--rules-tags=",
      "--rules-include=ban-untagged-todo,no-undef",
      "--rules-exclude=no-const-assign"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Lint(LintFlags {
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          fix: false,
          rules: false,
          maybe_rules_tags: Some(svec![""]),
          maybe_rules_include: Some(svec!["ban-untagged-todo", "no-undef"]),
          maybe_rules_exclude: Some(svec!["no-const-assign"]),
          permit_no_files: false,
          json: false,
          compact: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "lint", "--json", "script_1.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Lint(LintFlags {
          files: FileFlags {
            include: vec!["script_1.ts".to_string()],
            ignore: vec![],
          },
          fix: false,
          rules: false,
          maybe_rules_tags: None,
          maybe_rules_include: None,
          maybe_rules_exclude: None,
          permit_no_files: false,
          json: true,
          compact: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "lint",
      "--config",
      "Deno.jsonc",
      "--json",
      "script_1.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Lint(LintFlags {
          files: FileFlags {
            include: vec!["script_1.ts".to_string()],
            ignore: vec![],
          },
          fix: false,
          rules: false,
          maybe_rules_tags: None,
          maybe_rules_include: None,
          maybe_rules_exclude: None,
          permit_no_files: false,
          json: true,
          compact: false,
        }),
        config_flag: ConfigFlag::Path("Deno.jsonc".to_string()),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "lint",
      "--config",
      "Deno.jsonc",
      "--compact",
      "script_1.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Lint(LintFlags {
          files: FileFlags {
            include: vec!["script_1.ts".to_string()],
            ignore: vec![],
          },
          fix: false,
          rules: false,
          maybe_rules_tags: None,
          maybe_rules_include: None,
          maybe_rules_exclude: None,
          permit_no_files: false,
          json: false,
          compact: true,
        }),
        config_flag: ConfigFlag::Path("Deno.jsonc".to_string()),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn types() {
    let r = flags_from_vec(svec!["deno", "types"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Types,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn cache() {
    let r = flags_from_vec(svec!["deno", "cache", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Cache(CacheFlags {
          files: svec!["script.ts"],
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "cache", "--env-file", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Cache(CacheFlags {
          files: svec!["script.ts"],
        }),
        env_file: Some(svec![".env"]),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn check() {
    let r = flags_from_vec(svec!["deno", "check", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Check(CheckFlags {
          files: svec!["script.ts"],
          doc: false,
          doc_only: false,
          check_js: false,
        }),
        type_check_mode: TypeCheckMode::Local,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "check"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Check(CheckFlags {
          files: svec!["."],
          doc: false,
          doc_only: false,
          check_js: false,
        }),
        type_check_mode: TypeCheckMode::Local,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "check", "--doc", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Check(CheckFlags {
          files: svec!["script.ts"],
          doc: true,
          doc_only: false,
          check_js: false,
        }),
        type_check_mode: TypeCheckMode::Local,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "check", "--doc-only", "markdown.md"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Check(CheckFlags {
          files: svec!["markdown.md"],
          doc: false,
          doc_only: true,
          check_js: false,
        }),
        type_check_mode: TypeCheckMode::Local,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );

    // `--doc` and `--doc-only` are mutually exclusive
    let r = flags_from_vec(svec![
      "deno",
      "check",
      "--doc",
      "--doc-only",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap_err().kind(),
      clap::error::ErrorKind::ArgumentConflict
    );

    for all_flag in ["--remote", "--all"] {
      let r = flags_from_vec(svec!["deno", "check", all_flag, "script.ts"]);
      assert_eq!(
        r.unwrap(),
        Flags {
          subcommand: DenoSubcommand::Check(CheckFlags {
            files: svec!["script.ts"],
            doc: false,
            doc_only: false,
            check_js: false,
          }),
          type_check_mode: TypeCheckMode::All,
          code_cache_enabled: true,
          ..Flags::default()
        }
      );

      let r = flags_from_vec(svec![
        "deno",
        "check",
        all_flag,
        "--no-remote",
        "script.ts"
      ]);
      assert_eq!(
        r.unwrap_err().kind(),
        clap::error::ErrorKind::ArgumentConflict
      );
    }

    let r = flags_from_vec(svec!["deno", "check", "--check-js", "script.js"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Check(CheckFlags {
          files: svec!["script.js"],
          doc: false,
          doc_only: false,
          check_js: true,
        }),
        type_check_mode: TypeCheckMode::Local,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "check", "--desktop", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Check(CheckFlags {
          files: svec!["script.ts"],
          doc: false,
          doc_only: false,
          check_js: false,
        }),
        type_check_mode: TypeCheckMode::Local,
        code_cache_enabled: true,
        internal: InternalFlags {
          is_desktop: true,
          ..Default::default()
        },
        ..Flags::default()
      }
    );
  }

  #[test]
  fn info() {
    let r = flags_from_vec(svec!["deno", "info", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Info(InfoFlags {
          json: false,
          file: Some("script.ts".to_string()),
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "info", "--reload", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Info(InfoFlags {
          json: false,
          file: Some("script.ts".to_string()),
        }),
        reload: true,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "info", "--json", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Info(InfoFlags {
          json: true,
          file: Some("script.ts".to_string()),
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "info"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Info(InfoFlags {
          json: false,
          file: None
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "info", "--json"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Info(InfoFlags {
          json: true,
          file: None
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "info",
      "--no-npm",
      "--no-remote",
      "--config",
      "tsconfig.json"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Info(InfoFlags {
          json: false,
          file: None
        }),
        config_flag: ConfigFlag::Path("tsconfig.json".to_owned()),
        no_npm: true,
        no_remote: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn tsconfig() {
    let r =
      flags_from_vec(svec!["deno", "run", "-c", "tsconfig.json", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        config_flag: ConfigFlag::Path("tsconfig.json".to_owned()),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn eval() {
    let r = flags_from_vec(svec!["deno", "eval", "'console.log(\"hello\")'"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Eval(EvalFlags {
          print: false,
          code: "'console.log(\"hello\")'".to_string(),
        }),
        permissions: PermissionFlags {
          allow_all: true,
          ..Default::default()
        },
        ..Flags::default()
      }
    );
  }

  #[test]
  fn eval_p() {
    let r = flags_from_vec(svec!["deno", "eval", "-p", "1+2"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Eval(EvalFlags {
          print: true,
          code: "1+2".to_string(),
        }),
        permissions: PermissionFlags {
          allow_all: true,
          ..Default::default()
        },
        ..Flags::default()
      }
    );
  }

  #[test]
  fn eval_typescript() {
    let r = flags_from_vec(svec![
      "deno",
      "eval",
      "--ext=ts",
      "'console.log(\"hello\")'"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Eval(EvalFlags {
          print: false,
          code: "'console.log(\"hello\")'".to_string(),
        }),
        permissions: PermissionFlags {
          allow_all: true,
          ..Default::default()
        },
        ext: Some("ts".to_string()),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn eval_with_flags() {
    #[rustfmt::skip]
    let r = flags_from_vec(svec!["deno", "eval", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--reload", "--lock", "lock.json", "--cert", "example.crt", "--cached-only", "--location", "https:foo", "--v8-flags=--help", "--seed", "1", "--inspect=127.0.0.1:9229", "--env=.example.env", "42"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Eval(EvalFlags {
          print: false,
          code: "42".to_string(),
        }),
        import_map_path: Some("import_map.json".to_string()),
        no_remote: true,
        config_flag: ConfigFlag::Path("tsconfig.json".to_owned()),
        type_check_mode: TypeCheckMode::None,
        reload: true,
        lock: Some(String::from("lock.json")),
        ca_data: Some(CaData::File("example.crt".to_string())),
        cached_only: true,
        location: Some(Url::parse("https://foo/").unwrap()),
        v8_flags: svec!["--help", "--random-seed=1"],
        seed: Some(1),
        inspect: Some("127.0.0.1:9229".parse().unwrap()),
        permissions: PermissionFlags {
          allow_all: true,
          ..Default::default()
        },
        env_file: Some(vec![".example.env".to_owned()]),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn eval_args() {
    let r = flags_from_vec(svec![
      "deno",
      "eval",
      "console.log(Deno.args)",
      "arg1",
      "arg2"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Eval(EvalFlags {
          print: false,
          code: "console.log(Deno.args)".to_string(),
        }),
        argv: svec!["arg1", "arg2"],
        permissions: PermissionFlags {
          allow_all: true,
          ..Default::default()
        },
        ..Flags::default()
      }
    );
  }

  #[test]
  fn repl() {
    let r = flags_from_vec(svec!["deno"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Repl(ReplFlags {
          eval_files: None,
          eval: None,
          is_default_command: true,
          json: false,
        }),
        unsafely_ignore_certificate_errors: None,
        permissions: PermissionFlags {
          allow_all: true,
          ..Default::default()
        },
        ..Flags::default()
      }
    );
  }

  #[test]
  fn repl_trace_ops() {
    // Lightly test this undocumented flag
    let r = flags_from_vec(svec!["deno", "repl", "--trace-ops"]);
    assert_eq!(r.unwrap().trace_ops, Some(vec![]));
    let r = flags_from_vec(svec!["deno", "repl", "--trace-ops=http,websocket"]);
    assert_eq!(
      r.unwrap().trace_ops,
      Some(vec!["http".to_string(), "websocket".to_string()])
    );
  }

  #[test]
  fn repl_with_flags() {
    #[rustfmt::skip]
    let r = flags_from_vec(svec!["deno", "repl", "-A", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--reload", "--lock", "lock.json", "--cert", "example.crt", "--cached-only", "--location", "https:foo", "--v8-flags=--help", "--seed", "1", "--inspect=127.0.0.1:9229", "--unsafely-ignore-certificate-errors", "--env=.example.env"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Repl(ReplFlags {
          eval_files: None,
          eval: None,
          is_default_command: false,
          json: false,
        }),
        import_map_path: Some("import_map.json".to_string()),
        no_remote: true,
        config_flag: ConfigFlag::Path("tsconfig.json".to_owned()),
        type_check_mode: TypeCheckMode::None,
        reload: true,
        lock: Some(String::from("lock.json")),
        ca_data: Some(CaData::File("example.crt".to_string())),
        cached_only: true,
        location: Some(Url::parse("https://foo/").unwrap()),
        v8_flags: svec!["--help", "--random-seed=1"],
        seed: Some(1),
        inspect: Some("127.0.0.1:9229".parse().unwrap()),
        permissions: PermissionFlags {
          allow_all: true,
          ..Default::default()
        },
        env_file: Some(vec![".example.env".to_owned()]),
        unsafely_ignore_certificate_errors: Some(vec![]),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn repl_with_eval_flag() {
    #[rustfmt::skip]
    let r = flags_from_vec(svec!["deno", "repl", "--allow-write", "--eval", "console.log('hello');"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Repl(ReplFlags {
          eval_files: None,
          eval: Some("console.log('hello');".to_string()),
          is_default_command: false,
          json: false,
        }),
        permissions: PermissionFlags {
          allow_write: Some(vec![]),
          ..Default::default()
        },
        ..Flags::default()
      }
    );
  }

  #[test]
  fn repl_with_eval_file_flag() {
    #[rustfmt::skip]
    let r = flags_from_vec(svec!["deno", "repl", "--eval-file=./a.js,./b.ts,https://docs.deno.com/hello_world.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Repl(ReplFlags {
          eval_files: Some(vec![
            "./a.js".to_string(),
            "./b.ts".to_string(),
            "https://docs.deno.com/hello_world.ts".to_string()
          ]),
          eval: None,
          is_default_command: false,
          json: false,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn repl_with_eval_file_flag_no_equals() {
    // Test without equals sign (for hashbang usage)
    #[rustfmt::skip]
    let r = flags_from_vec(svec!["deno", "repl", "--eval-file", "./script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Repl(ReplFlags {
          eval_files: Some(vec!["./script.ts".to_string()]),
          eval: None,
          is_default_command: false,
          json: false,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn repl_with_eval_file_flag_multiple() {
    // Test multiple --eval-file flags
    #[rustfmt::skip]
    let r = flags_from_vec(svec!["deno", "repl", "--eval-file", "./a.ts", "--eval-file", "./b.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Repl(ReplFlags {
          eval_files: Some(vec!["./a.ts".to_string(), "./b.ts".to_string()]),
          eval: None,
          is_default_command: false,
          json: false,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn allow_read_allowlist() {
    use test_util::TempDir;
    let temp_dir_guard = TempDir::new();
    let temp_dir = temp_dir_guard.path().to_string();

    let r = flags_from_vec(svec![
      "deno",
      "run",
      format!("--allow-read=.,{}", temp_dir),
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        permissions: PermissionFlags {
          allow_read: Some(vec![String::from("."), temp_dir]),
          ..Default::default()
        },
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn deny_read_denylist() {
    use test_util::TempDir;
    let temp_dir_guard = TempDir::new();
    let temp_dir = temp_dir_guard.path().to_string();

    let r = flags_from_vec(svec![
      "deno",
      "run",
      format!("--deny-read=.,{}", temp_dir),
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        permissions: PermissionFlags {
          deny_read: Some(vec![String::from("."), temp_dir]),
          ..Default::default()
        },
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn ignore_read_ignorelist() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--ignore-read=something.txt",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          ignore_read: Some(svec!["something.txt"]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn ignore_read_ignorelist_multiple() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--ignore-read=something.txt",
      "--ignore-read=something2.txt",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          ignore_read: Some(svec!["something.txt", "something2.txt"]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn ignore_read_ignorelist_comma_separated() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--ignore-read=something.txt,something2.txt",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          ignore_read: Some(svec!["something.txt", "something2.txt"]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn allow_write_allowlist() {
    use test_util::TempDir;
    let temp_dir_guard = TempDir::new();
    let temp_dir = temp_dir_guard.path().to_string();

    let r = flags_from_vec(svec![
      "deno",
      "run",
      format!("--allow-write=.,{}", temp_dir),
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        permissions: PermissionFlags {
          allow_write: Some(vec![String::from("."), temp_dir]),
          ..Default::default()
        },
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn deny_write_denylist() {
    use test_util::TempDir;
    let temp_dir_guard = TempDir::new();
    let temp_dir = temp_dir_guard.path().to_string();

    let r = flags_from_vec(svec![
      "deno",
      "run",
      format!("--deny-write=.,{}", temp_dir),
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        permissions: PermissionFlags {
          deny_write: Some(vec![String::from("."), temp_dir]),
          ..Default::default()
        },
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn allow_net_allowlist() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--allow-net=127.0.0.1",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          allow_net: Some(svec!["127.0.0.1"]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn deny_net_denylist() {
    let r = flags_from_vec(svec!["deno", "--deny-net=127.0.0.1", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: true,
          coverage_dir: None,
          print_task_list: false,
        }),
        permissions: PermissionFlags {
          deny_net: Some(svec!["127.0.0.1"]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn allow_env_allowlist() {
    let r =
      flags_from_vec(svec!["deno", "run", "--allow-env=HOME", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          allow_env: Some(svec!["HOME"]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn deny_env_denylist() {
    let r =
      flags_from_vec(svec!["deno", "run", "--deny-env=HOME", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          deny_env: Some(svec!["HOME"]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn ignore_env_ignorelist() {
    let r =
      flags_from_vec(svec!["deno", "run", "--ignore-env=HOME", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          ignore_env: Some(svec!["HOME"]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn allow_env_allowlist_multiple() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--allow-env=HOME,PATH",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          allow_env: Some(svec!["HOME", "PATH"]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn deny_env_denylist_multiple() {
    let r =
      flags_from_vec(svec!["deno", "run", "--deny-env=HOME,PATH", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          deny_env: Some(svec!["HOME", "PATH"]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn deny_env_ignorelist_multiple() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--ignore-env=HOME,PATH",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          ignore_env: Some(svec!["HOME", "PATH"]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn allow_env_allowlist_validator() {
    let r =
      flags_from_vec(svec!["deno", "run", "--allow-env=HOME", "script.ts"]);
    assert!(r.is_ok());
    let r = flags_from_vec(svec!["deno", "--allow-env=H=ME", "script.ts"]);
    assert!(r.is_err());
    let r =
      flags_from_vec(svec!["deno", "run", "--allow-env=H\0ME", "script.ts"]);
    assert!(r.is_err());
  }

  #[test]
  fn deny_env_denylist_validator() {
    let r =
      flags_from_vec(svec!["deno", "run", "--deny-env=HOME", "script.ts"]);
    assert!(r.is_ok());
    let r =
      flags_from_vec(svec!["deno", "run", "--deny-env=H=ME", "script.ts"]);
    assert!(r.is_err());
    let r = flags_from_vec(svec!["deno", "--deny-env=H\0ME", "script.ts"]);
    assert!(r.is_err());
  }

  #[test]
  fn allow_sys() {
    let r = flags_from_vec(svec!["deno", "run", "--allow-sys", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          allow_sys: Some(vec![]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn deny_sys() {
    let r = flags_from_vec(svec!["deno", "run", "--deny-sys", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          deny_sys: Some(vec![]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn allow_sys_allowlist() {
    let r =
      flags_from_vec(svec!["deno", "run", "--allow-sys=hostname", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          allow_sys: Some(svec!["hostname"]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn deny_sys_denylist() {
    let r = flags_from_vec(svec!["deno", "--deny-sys=hostname", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: true,
          coverage_dir: None,
          print_task_list: false,
        }),
        permissions: PermissionFlags {
          deny_sys: Some(svec!["hostname"]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn allow_sys_allowlist_multiple() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--allow-sys=hostname,osRelease",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          allow_sys: Some(svec!["hostname", "osRelease"]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn deny_sys_denylist_multiple() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--deny-sys=hostname,osRelease",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          deny_sys: Some(svec!["hostname", "osRelease"]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn allow_sys_allowlist_validator() {
    let r =
      flags_from_vec(svec!["deno", "run", "--allow-sys=hostname", "script.ts"]);
    assert!(r.is_ok());
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--allow-sys=hostname,osRelease",
      "script.ts"
    ]);
    assert!(r.is_ok());
    let r =
      flags_from_vec(svec!["deno", "run", "--allow-sys=foo", "script.ts"]);
    assert!(r.is_err());
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--allow-sys=hostname,foo",
      "script.ts"
    ]);
    assert!(r.is_err());
  }

  #[test]
  fn deny_sys_denylist_validator() {
    let r =
      flags_from_vec(svec!["deno", "run", "--deny-sys=hostname", "script.ts"]);
    assert!(r.is_ok());
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--deny-sys=hostname,osRelease",
      "script.ts"
    ]);
    assert!(r.is_ok());
    let r = flags_from_vec(svec!["deno", "run", "--deny-sys=foo", "script.ts"]);
    assert!(r.is_err());
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--deny-sys=hostname,foo",
      "script.ts"
    ]);
    assert!(r.is_err());
  }

  #[test]
  fn reload_validator() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--reload=http://deno.land/",
      "script.ts"
    ]);
    assert!(r.is_ok(), "should accept valid urls");

    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--reload=http://deno.land/a,http://deno.land/b",
      "script.ts"
    ]);
    assert!(r.is_ok(), "should accept accept multiple valid urls");

    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--reload=./relativeurl/",
      "script.ts"
    ]);
    assert!(r.is_err(), "Should reject relative urls that start with ./");

    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--reload=relativeurl/",
      "script.ts"
    ]);
    assert!(r.is_err(), "Should reject relative urls");

    let r =
      flags_from_vec(svec!["deno", "run", "--reload=/absolute", "script.ts"]);
    assert!(r.is_err(), "Should reject absolute urls");

    let r = flags_from_vec(svec!["deno", "--reload=/", "script.ts"]);
    assert!(r.is_err(), "Should reject absolute root url");

    let r = flags_from_vec(svec!["deno", "run", "--reload=", "script.ts"]);
    assert!(r.is_err(), "Should reject when nothing is provided");

    let r = flags_from_vec(svec!["deno", "run", "--reload=,", "script.ts"]);
    assert!(r.is_err(), "Should reject when a single comma is provided");

    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--reload=,http://deno.land/a",
      "script.ts"
    ]);
    assert!(r.is_err(), "Should reject a leading comma");

    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--reload=http://deno.land/a,",
      "script.ts"
    ]);
    assert!(r.is_err(), "Should reject a trailing comma");
  }

  #[test]
  fn run_import_map() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--import-map=import_map.json",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        import_map_path: Some("import_map.json".to_owned()),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn info_import_map() {
    let r = flags_from_vec(svec![
      "deno",
      "info",
      "--import-map=import_map.json",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Info(InfoFlags {
          file: Some("script.ts".to_string()),
          json: false,
        }),
        import_map_path: Some("import_map.json".to_owned()),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn cache_import_map() {
    let r = flags_from_vec(svec![
      "deno",
      "cache",
      "--import-map=import_map.json",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Cache(CacheFlags {
          files: svec!["script.ts"],
        }),
        import_map_path: Some("import_map.json".to_owned()),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn doc_import_map() {
    let r = flags_from_vec(svec![
      "deno",
      "doc",
      "--import-map=import_map.json",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Doc(DocFlags {
          source_files: DocSourceFileFlag::Paths(vec!["script.ts".to_owned()]),
          private: false,
          json: false,
          html: None,
          lint: false,
          filter: None,
        }),
        import_map_path: Some("import_map.json".to_owned()),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_env_default() {
    let r = flags_from_vec(svec!["deno", "run", "--env", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        env_file: Some(vec![".env".to_owned()]),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_env_file_default() {
    let r = flags_from_vec(svec!["deno", "run", "--env-file", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        env_file: Some(vec![".env".to_owned()]),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_no_code_cache() {
    let r = flags_from_vec(svec!["deno", "--no-code-cache", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: true,
          coverage_dir: None,
          print_task_list: false,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_env_defined() {
    let r =
      flags_from_vec(svec!["deno", "run", "--env=.another_env", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        env_file: Some(vec![".another_env".to_owned()]),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_env_file_defined() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--env-file=.another_env",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        env_file: Some(vec![".another_env".to_owned()]),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_multiple_env_file_defined() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--env-file",
      "--env-file=.two_env",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        env_file: Some(vec![".env".to_owned(), ".two_env".to_owned()]),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  // The dependency and registry subcommands also accept --env-file so that
  // registry credentials kept in a .env file can be loaded before the command
  // resolves a dependency graph or talks to a registry.
  #[test]
  fn dep_and_registry_subcommands_env_file() {
    // (args, expected env_file) pairs, one per supported subcommand.
    let cases: Vec<(Vec<std::ffi::OsString>, Vec<String>)> = vec![
      (
        svec!["deno", "add", "--env-file", "@david/which"],
        svec![".env"],
      ),
      (svec!["deno", "audit", "--env-file"], svec![".env"]),
      (svec!["deno", "why", "--env-file", "express"], svec![".env"]),
      (svec!["deno", "outdated", "--env-file"], svec![".env"]),
      (svec!["deno", "update", "--env-file"], svec![".env"]),
      (
        svec!["deno", "bundle", "--env-file", "main.ts"],
        svec![".env"],
      ),
      (
        svec!["deno", "check", "--env-file", "main.ts"],
        svec![".env"],
      ),
      (svec!["deno", "doc", "--env-file", "main.ts"], svec![".env"]),
      (
        svec!["deno", "info", "--env-file", "main.ts"],
        svec![".env"],
      ),
      (svec!["deno", "ci", "--env-file"], svec![".env"]),
      (svec!["deno", "publish", "--env-file"], svec![".env"]),
      (svec!["deno", "pack", "--env-file"], svec![".env"]),
    ];
    for (args, expected) in cases {
      let flags = flags_from_vec(args.clone())
        .unwrap_or_else(|e| panic!("failed to parse {args:?}: {e}"));
      assert_eq!(
        flags.env_file,
        Some(expected),
        "unexpected env_file for {args:?}",
      );
    }
  }

  #[test]
  fn dep_and_registry_subcommands_env_file_explicit_and_multiple() {
    // An explicit path is honored.
    let flags =
      flags_from_vec(svec!["deno", "check", "--env-file=.prod.env", "main.ts"])
        .unwrap();
    assert_eq!(flags.env_file, Some(svec![".prod.env"]));

    // The --env alias works the same as --env-file.
    let flags =
      flags_from_vec(svec!["deno", "outdated", "--env=.prod.env"]).unwrap();
    assert_eq!(flags.env_file, Some(svec![".prod.env"]));

    // Multiple --env-file flags accumulate in order.
    let flags = flags_from_vec(svec![
      "deno",
      "publish",
      "--env-file",
      "--env-file=.prod.env"
    ])
    .unwrap();
    assert_eq!(
      flags.env_file,
      Some(svec![".env".to_owned(), ".prod.env".to_owned()])
    );
  }

  #[test]
  fn cache_multiple() {
    let r =
      flags_from_vec(svec!["deno", "cache", "script.ts", "script_two.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Cache(CacheFlags {
          files: svec!["script.ts", "script_two.ts"],
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_seed() {
    let r = flags_from_vec(svec!["deno", "run", "--seed", "250", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        seed: Some(250_u64),
        v8_flags: svec!["--random-seed=250"],
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn minimum_dependency_age_alias() {
    for flag in ["--minimum-dependency-age=0", "--min-dep-age=0"] {
      let flags =
        flags_from_vec(svec!["deno", "run", flag, "script.ts"]).unwrap();
      assert_eq!(
        flags.minimum_dependency_age,
        Some(NewestDependencyDate::Disabled)
      );
    }
  }

  #[test]
  fn minimum_dependency_age_pm_subcommands() {
    // The package management subcommands that resolve versions must accept the
    // flag so users can act on the "blocked by minimum dependency age" hint.
    let cases = [
      svec!["deno", "add", "--min-dep-age=0", "jsr:@std/path"],
      svec!["deno", "add", "--minimum-dependency-age=0", "jsr:@std/path"],
      svec!["deno", "remove", "--min-dep-age=0", "@std/path"],
    ];
    for args in cases {
      let flags = flags_from_vec(args.clone())
        .unwrap_or_else(|e| panic!("failed to parse {args:?}: {e}"));
      assert_eq!(
        flags.minimum_dependency_age,
        Some(NewestDependencyDate::Disabled),
        "{args:?}"
      );
    }
  }

  #[test]
  fn run_seed_with_v8_flags() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--seed",
      "250",
      "--v8-flags=--expose-gc",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        seed: Some(250_u64),
        v8_flags: svec!["--expose-gc", "--random-seed=250"],
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn install() {
    let r = flags_from_vec(svec![
      "deno",
      "install",
      "-g",
      "jsr:@std/http/file-server",
      "npm:chalk",
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Install(InstallFlags::Global(
          InstallFlagsGlobal {
            name: None,
            module_urls: svec!["jsr:@std/http/file-server", "npm:chalk"],
            args: vec![],
            root: None,
            force: false,
            compile: false,
          }
        ),),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "install",
      "-g",
      "jsr:@std/http/file-server"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Install(InstallFlags::Global(
          InstallFlagsGlobal {
            name: None,
            module_urls: svec!["jsr:@std/http/file-server"],
            args: vec![],
            root: None,
            force: false,
            compile: false,
          }
        ),),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn install_with_flags() {
    #[rustfmt::skip]
    let r = flags_from_vec(svec!["deno", "install", "--global", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--unsafely-ignore-certificate-errors", "--reload", "--lock", "lock.json", "--cert", "example.crt", "--cached-only", "--allow-read", "--allow-net", "--v8-flags=--help", "--seed", "1", "--inspect=127.0.0.1:9229", "--name", "file_server", "--root", "/foo", "--force", "--env=.example.env", "jsr:@std/http/file-server", "--", "foo", "bar"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Install(InstallFlags::Global(
          InstallFlagsGlobal {
            name: Some("file_server".to_string()),
            module_urls: svec!["jsr:@std/http/file-server"],
            args: svec!["foo", "bar"],
            root: Some("/foo".to_string()),
            force: true,
            compile: false,
          }
        ),),
        import_map_path: Some("import_map.json".to_string()),
        no_remote: true,
        config_flag: ConfigFlag::Path("tsconfig.json".to_owned()),
        type_check_mode: TypeCheckMode::None,
        reload: true,
        lock: Some(String::from("lock.json")),
        ca_data: Some(CaData::File("example.crt".to_string())),
        cached_only: true,
        v8_flags: svec!["--help", "--random-seed=1"],
        seed: Some(1),
        inspect: Some("127.0.0.1:9229".parse().unwrap()),
        unsafely_ignore_certificate_errors: Some(vec![]),
        permissions: PermissionFlags {
          allow_net: Some(vec![]),
          allow_read: Some(vec![]),
          ..Default::default()
        },
        env_file: Some(vec![".example.env".to_owned()]),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn uninstall() {
    let r = flags_from_vec(svec!["deno", "uninstall"]);
    assert!(r.is_err(),);

    let r = flags_from_vec(svec![
      "deno",
      "uninstall",
      "--frozen",
      "--lockfile-only",
      "@std/load"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Uninstall(UninstallFlags {
          kind: UninstallKind::Local(RemoveFlags {
            packages: vec!["@std/load".to_string()],
            lockfile_only: true,
            package_json: false,
          }),
        }),
        frozen_lockfile: Some(true),
        ..Flags::default()
      }
    );

    let r =
      flags_from_vec(svec!["deno", "uninstall", "file_server", "@std/load"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Uninstall(UninstallFlags {
          kind: UninstallKind::Local(RemoveFlags {
            packages: vec!["file_server".to_string(), "@std/load".to_string()],
            lockfile_only: false,
            package_json: false,
          }),
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "uninstall", "-g", "file_server"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Uninstall(UninstallFlags {
          kind: UninstallKind::Global(UninstallFlagsGlobal {
            packages: svec!["file_server"],
            root: None,
          }),
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "uninstall",
      "-g",
      "--root",
      "/user/foo/bar",
      "file_server"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Uninstall(UninstallFlags {
          kind: UninstallKind::Global(UninstallFlagsGlobal {
            packages: svec!["file_server"],
            root: Some("/user/foo/bar".to_string()),
          }),
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "uninstall",
      "-g",
      "--root",
      "/user/foo/bar",
      "cowsay",
      "file_server"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Uninstall(UninstallFlags {
          kind: UninstallKind::Global(UninstallFlagsGlobal {
            packages: svec!["cowsay", "file_server"],
            root: Some("/user/foo/bar".to_string()),
          }),
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn uninstall_with_help_flag() {
    let r = flags_from_vec(svec!["deno", "uninstall", "--help"]);
    assert!(r.is_ok());
  }

  #[test]
  fn log_level() {
    let r =
      flags_from_vec(svec!["deno", "run", "--log-level=debug", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        log_level: Some(Level::Debug),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn quiet() {
    let r = flags_from_vec(svec!["deno", "-q", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: true,
          coverage_dir: None,
          print_task_list: false,
        }),
        log_level: Some(Level::Error),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn completions() {
    let r = flags_from_vec(svec!["deno", "completions", "zsh"]).unwrap();

    match r.subcommand {
      DenoSubcommand::Completions(CompletionsFlags::Static(buf)) => {
        assert!(!buf.is_empty())
      }
      _ => unreachable!(),
    }
  }

  #[test]
  fn run_with_args() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "script.ts",
      "--allow-read",
      "--allow-net"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        argv: svec!["--allow-read", "--allow-net"],
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--location",
      "https:foo",
      "--allow-read",
      "script.ts",
      "--allow-net",
      "-r",
      "--help",
      "--foo",
      "bar"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        location: Some(Url::parse("https://foo/").unwrap()),
        permissions: PermissionFlags {
          allow_read: Some(vec![]),
          ..Default::default()
        },
        argv: svec!["--allow-net", "-r", "--help", "--foo", "bar"],
        code_cache_enabled: true,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "run", "script.ts", "foo", "bar"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        argv: svec!["foo", "bar"],
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
    let r = flags_from_vec(svec!["deno", "run", "script.ts", "-"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        argv: svec!["-"],
        code_cache_enabled: true,
        ..Flags::default()
      }
    );

    let r =
      flags_from_vec(svec!["deno", "run", "script.ts", "-", "foo", "bar"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        argv: svec!["-", "foo", "bar"],
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn no_check() {
    let r = flags_from_vec(svec!["deno", "--no-check", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: true,
          coverage_dir: None,
          print_task_list: false,
        }),
        type_check_mode: TypeCheckMode::None,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn no_check_remote() {
    let r =
      flags_from_vec(svec!["deno", "run", "--no-check=remote", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        type_check_mode: TypeCheckMode::Local,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn repl_with_unsafely_ignore_certificate_errors() {
    let r = flags_from_vec(svec![
      "deno",
      "repl",
      "--eval",
      "console.log('hello');",
      "--unsafely-ignore-certificate-errors"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Repl(ReplFlags {
          eval_files: None,
          eval: Some("console.log('hello');".to_string()),
          is_default_command: false,
          json: false,
        }),
        unsafely_ignore_certificate_errors: Some(vec![]),
        type_check_mode: TypeCheckMode::None,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_with_unsafely_ignore_certificate_errors() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--unsafely-ignore-certificate-errors",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        unsafely_ignore_certificate_errors: Some(vec![]),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_with_unsafely_treat_insecure_origin_as_secure_with_ipv6_address() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--unsafely-ignore-certificate-errors=deno.land,localhost,[::],127.0.0.1,[::1],1.2.3.4",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        unsafely_ignore_certificate_errors: Some(svec![
          "deno.land",
          "localhost",
          "[::]",
          "127.0.0.1",
          "[::1]",
          "1.2.3.4"
        ]),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn repl_with_unsafely_treat_insecure_origin_as_secure_with_ipv6_address() {
    let r = flags_from_vec(svec![
      "deno",
      "repl",
      "--unsafely-ignore-certificate-errors=deno.land,localhost,[::],127.0.0.1,[::1],1.2.3.4"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Repl(ReplFlags {
          eval_files: None,
          eval: None,
          is_default_command: false,
          json: false,
        }),
        unsafely_ignore_certificate_errors: Some(svec![
          "deno.land",
          "localhost",
          "[::]",
          "127.0.0.1",
          "[::1]",
          "1.2.3.4"
        ]),
        type_check_mode: TypeCheckMode::None,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn no_remote() {
    let r = flags_from_vec(svec!["deno", "run", "--no-remote", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        no_remote: true,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn no_npm() {
    let r = flags_from_vec(svec!["deno", "run", "--no-npm", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        no_npm: true,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn local_npm() {
    let r = flags_from_vec(svec!["deno", "--node-modules-dir", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: true,
          coverage_dir: None,
          print_task_list: false,
        }),
        node_modules_dir: Some(NodeModulesDirMode::Auto),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn vendor_flag() {
    let r = flags_from_vec(svec!["deno", "run", "--vendor", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        vendor: Some(true),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "run", "--vendor=false", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        vendor: Some(false),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn cached_only() {
    let r = flags_from_vec(svec!["deno", "run", "--cached-only", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        cached_only: true,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn allow_net_allowlist_with_ports() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--allow-net=deno.land,:8000,:4545",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          allow_net: Some(svec![
            "deno.land",
            "0.0.0.0:8000",
            "127.0.0.1:8000",
            "localhost:8000",
            "0.0.0.0:4545",
            "127.0.0.1:4545",
            "localhost:4545"
          ]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn deny_net_denylist_with_ports() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--deny-net=deno.land,:8000,:4545",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          deny_net: Some(svec![
            "deno.land",
            "0.0.0.0:8000",
            "127.0.0.1:8000",
            "localhost:8000",
            "0.0.0.0:4545",
            "127.0.0.1:4545",
            "localhost:4545"
          ]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn allow_net_allowlist_with_ipv6_address() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--allow-net=deno.land,deno.land:80,[::],127.0.0.1,[::1],1.2.3.4:5678,:5678,[::1]:8080",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          allow_net: Some(svec![
            "deno.land",
            "deno.land:80",
            "[::]",
            "127.0.0.1",
            "[::1]",
            "1.2.3.4:5678",
            "0.0.0.0:5678",
            "127.0.0.1:5678",
            "localhost:5678",
            "[::1]:8080"
          ]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn deny_net_denylist_with_ipv6_address() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--deny-net=deno.land,deno.land:80,[::],127.0.0.1,[::1],1.2.3.4:5678,:5678,[::1]:8080",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        permissions: PermissionFlags {
          deny_net: Some(svec![
            "deno.land",
            "deno.land:80",
            "[::]",
            "127.0.0.1",
            "[::1]",
            "1.2.3.4:5678",
            "0.0.0.0:5678",
            "127.0.0.1:5678",
            "localhost:5678",
            "[::1]:8080"
          ]),
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn test_no_colon_in_value_name() {
    let app = runtime_args(
      Command::new("test_inspect_completion_value"),
      true,
      true,
      false,
    );
    let inspect_args = app
      .get_arguments()
      .filter(|arg| arg.get_id() == "inspect")
      .collect::<Vec<_>>();
    // The value_name cannot have a : otherwise it breaks shell completions for zsh.
    let value_name = "HOST_PORT";
    let arg = inspect_args
      .iter()
      .any(|v| v.get_value_names().unwrap() == [value_name]);

    assert_eq!(arg, true);
  }

  #[test]
  fn test_with_flags() {
    #[rustfmt::skip]
    let r = flags_from_vec(svec!["deno", "test", "--no-npm", "--no-remote", "--trace-leaks", "--no-run", "--filter", "- foo", "--coverage=cov", "--clean", "--location", "https:foo", "--allow-net", "--permit-no-files", "dir1/", "dir2/", "--", "arg1", "arg2"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          no_run: true,
          doc: false,
          fail_fast: None,
          filter: Some("- foo".to_string()),
          permit_no_files: true,
          files: FileFlags {
            include: vec!["dir1/".to_string(), "dir2/".to_string()],
            ignore: vec![],
          },
          shuffle: None,
          retry: 0,
          repeats: 0,
          shard: None,
          parallel: false,
          trace_leaks: true,
          sanitize_ops: false,
          sanitize_resources: false,
          coverage_dir: Some("cov".to_string()),
          coverage_raw_data_only: false,
          coverage_threshold: None,
          clean: true,
          reporter: Default::default(),
          junit_path: None,
          hide_stacktraces: false,
          changed: None,
          related: vec![],
          update_snapshots: false,
        }),
        no_npm: true,
        no_remote: true,
        location: Some(Url::parse("https://foo/").unwrap()),
        type_check_mode: TypeCheckMode::Local,
        permissions: PermissionFlags {
          no_prompt: true,
          allow_net: Some(vec![]),
          ..Default::default()
        },
        argv: svec!["arg1", "arg2"],
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_with_cafile() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--cert",
      "example.crt",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        ca_data: Some(CaData::File("example.crt".to_owned())),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_with_base64_cafile() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--cert",
      "base64:bWVvdw==",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        ca_data: Some(CaData::Bytes(b"meow".into())),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_with_enable_testing_features() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--enable-testing-features-do-not-use",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        enable_testing_features: true,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn test_with_fail_fast() {
    let r = flags_from_vec(svec!["deno", "test", "--fail-fast=3"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          no_run: false,
          doc: false,
          fail_fast: Some(NonZeroUsize::new(3).unwrap()),
          filter: None,
          permit_no_files: false,
          shuffle: None,
          retry: 0,
          repeats: 0,
          shard: None,
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          parallel: false,
          trace_leaks: false,
          sanitize_ops: false,
          sanitize_resources: false,
          coverage_dir: None,
          coverage_raw_data_only: false,
          coverage_threshold: None,
          clean: false,
          reporter: Default::default(),
          junit_path: None,
          hide_stacktraces: false,
          changed: None,
          related: vec![],
          update_snapshots: false,
        }),
        type_check_mode: TypeCheckMode::Local,
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "test", "--fail-fast=0"]);
    assert!(r.is_err());
  }

  #[test]
  fn test_with_enable_testing_features() {
    let r = flags_from_vec(svec![
      "deno",
      "test",
      "--enable-testing-features-do-not-use"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          no_run: false,
          doc: false,
          fail_fast: None,
          filter: None,
          permit_no_files: false,
          shuffle: None,
          retry: 0,
          repeats: 0,
          shard: None,
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          parallel: false,
          trace_leaks: false,
          sanitize_ops: false,
          sanitize_resources: false,
          coverage_dir: None,
          coverage_raw_data_only: false,
          coverage_threshold: None,
          clean: false,
          reporter: Default::default(),
          junit_path: None,
          hide_stacktraces: false,
          changed: None,
          related: vec![],
          update_snapshots: false,
        }),
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        type_check_mode: TypeCheckMode::Local,
        enable_testing_features: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn test_changed() {
    let r = flags_from_vec(svec!["deno", "test", "--changed"]);
    assert_eq!(
      r.unwrap().subcommand,
      DenoSubcommand::Test(TestFlags {
        changed: Some(None),
        ..Default::default()
      })
    );

    let r = flags_from_vec(svec!["deno", "test", "--changed=origin/main"]);
    assert_eq!(
      r.unwrap().subcommand,
      DenoSubcommand::Test(TestFlags {
        changed: Some(Some("origin/main".to_string())),
        ..Default::default()
      })
    );

    // space-separated value is not allowed (would be ambiguous with file args)
    let r = flags_from_vec(svec!["deno", "test", "--changed", "HEAD~1"]);
    assert_eq!(
      r.unwrap().subcommand,
      DenoSubcommand::Test(TestFlags {
        changed: Some(None),
        files: FileFlags {
          include: vec!["HEAD~1".to_string()],
          ignore: vec![],
        },
        ..Default::default()
      })
    );
  }

  #[test]
  fn test_related() {
    let r =
      flags_from_vec(svec!["deno", "test", "--related=src/a.ts,src/b.ts"]);
    assert_eq!(
      r.unwrap().subcommand,
      DenoSubcommand::Test(TestFlags {
        related: svec!["src/a.ts", "src/b.ts"],
        ..Default::default()
      })
    );
  }

  #[test]
  fn test_changed_conflicts_with_watch() {
    let r = flags_from_vec(svec!["deno", "test", "--changed", "--watch"]);
    assert!(r.is_err());
    let r = flags_from_vec(svec!["deno", "test", "--related=a.ts", "--watch"]);
    assert!(r.is_err());
  }

  #[test]
  fn test_reporter() {
    let r = flags_from_vec(svec!["deno", "test", "--reporter=pretty"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          reporter: TestReporterConfig::Pretty,
          ..Default::default()
        }),
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        type_check_mode: TypeCheckMode::Local,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "test", "--reporter=dot"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          reporter: TestReporterConfig::Dot,
          ..Default::default()
        }),
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        type_check_mode: TypeCheckMode::Local,
        log_level: Some(Level::Error),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "test", "--reporter=junit"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          reporter: TestReporterConfig::Junit,
          ..Default::default()
        }),
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        type_check_mode: TypeCheckMode::Local,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "test", "--reporter=tap"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          reporter: TestReporterConfig::Tap,
          ..Default::default()
        }),
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        type_check_mode: TypeCheckMode::Local,
        log_level: Some(Level::Error),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "test",
      "--reporter=dot",
      "--junit-path=report.xml"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          reporter: TestReporterConfig::Dot,
          junit_path: Some("report.xml".to_string()),
          ..Default::default()
        }),
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        type_check_mode: TypeCheckMode::Local,
        log_level: Some(Level::Error),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "test", "--junit-path"]);
    assert!(r.is_err());
  }

  #[test]
  fn test_shuffle() {
    let r = flags_from_vec(svec!["deno", "test", "--shuffle=1"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          no_run: false,
          doc: false,
          fail_fast: None,
          filter: None,
          permit_no_files: false,
          shuffle: Some(1),
          retry: 0,
          repeats: 0,
          shard: None,
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          parallel: false,
          trace_leaks: false,
          sanitize_ops: false,
          sanitize_resources: false,
          coverage_dir: None,
          coverage_raw_data_only: false,
          coverage_threshold: None,
          clean: false,
          reporter: Default::default(),
          junit_path: None,
          hide_stacktraces: false,
          changed: None,
          related: vec![],
          update_snapshots: false,
        }),
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        type_check_mode: TypeCheckMode::Local,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn test_retry_and_repeats() {
    let r = flags_from_vec(svec!["deno", "test", "--retry=3", "--repeats=2"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          no_run: false,
          doc: false,
          fail_fast: None,
          filter: None,
          permit_no_files: false,
          shuffle: None,
          retry: 3,
          repeats: 2,
          shard: None,
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          parallel: false,
          trace_leaks: false,
          sanitize_ops: false,
          sanitize_resources: false,
          coverage_dir: None,
          coverage_raw_data_only: false,
          coverage_threshold: None,
          clean: false,
          reporter: Default::default(),
          junit_path: None,
          hide_stacktraces: false,
          changed: None,
          related: vec![],
          update_snapshots: false,
        }),
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        type_check_mode: TypeCheckMode::Local,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn test_shard() {
    let r = flags_from_vec(svec!["deno", "test", "--shard=2/3"]);
    let flags = r.unwrap();
    assert!(matches!(
      flags.subcommand,
      DenoSubcommand::Test(TestFlags {
        shard: Some((2, 3)),
        ..
      })
    ));

    // Invalid shard values are rejected at parse time.
    assert!(flags_from_vec(svec!["deno", "test", "--shard=3/2"]).is_err());
    assert!(flags_from_vec(svec!["deno", "test", "--shard=0/2"]).is_err());
    assert!(flags_from_vec(svec!["deno", "test", "--shard=1/0"]).is_err());
    assert!(flags_from_vec(svec!["deno", "test", "--shard=foo"]).is_err());
  }

  #[test]
  fn test_watch() {
    let r = flags_from_vec(svec!["deno", "test", "--watch"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          no_run: false,
          doc: false,
          fail_fast: None,
          filter: None,
          permit_no_files: false,
          shuffle: None,
          retry: 0,
          repeats: 0,
          shard: None,
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          parallel: false,
          trace_leaks: false,
          sanitize_ops: false,
          sanitize_resources: false,
          coverage_dir: None,
          coverage_raw_data_only: false,
          coverage_threshold: None,
          clean: false,
          reporter: Default::default(),
          junit_path: None,
          hide_stacktraces: false,
          changed: None,
          related: vec![],
          update_snapshots: false,
        }),
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        type_check_mode: TypeCheckMode::Local,
        watch: Some(Default::default()),
        ..Flags::default()
      }
    );
  }
  #[test]
  fn test_watch_explicit_cwd() {
    let r = flags_from_vec(svec!["deno", "test", "--watch", "./"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          no_run: false,
          doc: false,
          fail_fast: None,
          filter: None,
          permit_no_files: false,
          shuffle: None,
          retry: 0,
          repeats: 0,
          shard: None,
          files: FileFlags {
            include: vec!["./".to_string()],
            ignore: vec![],
          },
          parallel: false,
          trace_leaks: false,
          sanitize_ops: false,
          sanitize_resources: false,
          coverage_dir: None,
          coverage_raw_data_only: false,
          coverage_threshold: None,
          clean: false,
          reporter: Default::default(),
          junit_path: None,
          hide_stacktraces: false,
          changed: None,
          related: vec![],
          update_snapshots: false,
        }),
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        type_check_mode: TypeCheckMode::Local,
        watch: Some(Default::default()),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn test_watch_with_no_clear_screen() {
    let r =
      flags_from_vec(svec!["deno", "test", "--watch", "--no-clear-screen"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          no_run: false,
          doc: false,
          fail_fast: None,
          filter: None,
          permit_no_files: false,
          shuffle: None,
          retry: 0,
          repeats: 0,
          shard: None,
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          parallel: false,
          trace_leaks: false,
          sanitize_ops: false,
          sanitize_resources: false,
          coverage_dir: None,
          coverage_raw_data_only: false,
          coverage_threshold: None,
          clean: false,
          reporter: Default::default(),
          junit_path: None,
          hide_stacktraces: false,
          changed: None,
          related: vec![],
          update_snapshots: false,
        }),
        type_check_mode: TypeCheckMode::Local,
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          no_clear_screen: true,
          exclude: vec![],
          paths: vec![],
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn test_watch_with_paths() {
    let r = flags_from_vec(svec!("deno", "test", "--watch=foo"));

    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          ..TestFlags::default()
        }),
        type_check_mode: TypeCheckMode::Local,
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![String::from("foo")],
          no_clear_screen: false,
          exclude: vec![],
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "test", "--watch=foo,bar"]);

    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          ..TestFlags::default()
        }),
        type_check_mode: TypeCheckMode::Local,
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![String::from("foo"), String::from("bar")],
          no_clear_screen: false,
          exclude: vec![],
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn test_watch_with_excluded_paths() {
    let r =
      flags_from_vec(svec!("deno", "test", "--watch", "--watch-exclude=foo",));

    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          ..TestFlags::default()
        }),
        type_check_mode: TypeCheckMode::Local,
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![],
          no_clear_screen: false,
          exclude: vec![String::from("foo")],
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!(
      "deno",
      "test",
      "--watch=foo",
      "--watch-exclude=bar",
    ));
    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          ..TestFlags::default()
        }),
        type_check_mode: TypeCheckMode::Local,
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![String::from("foo")],
          no_clear_screen: false,
          exclude: vec![String::from("bar")],
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "test",
      "--watch",
      "--watch-exclude=foo,bar",
    ]);

    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          ..TestFlags::default()
        }),
        type_check_mode: TypeCheckMode::Local,
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![],
          no_clear_screen: false,
          exclude: vec![String::from("foo"), String::from("bar")],
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "test",
      "--watch=foo,bar",
      "--watch-exclude=baz,qux",
    ]);

    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          ..TestFlags::default()
        }),
        type_check_mode: TypeCheckMode::Local,
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![String::from("foo"), String::from("bar")],
          no_clear_screen: false,
          exclude: vec![String::from("baz"), String::from("qux"),],
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn test_coverage_default_dir() {
    let r = flags_from_vec(svec!["deno", "test", "--coverage"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          coverage_dir: Some("coverage".to_string()),
          ..TestFlags::default()
        }),
        type_check_mode: TypeCheckMode::Local,
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        ..Flags::default()
      }
    );
  }

  #[test]
  fn test_hide_stacktraces() {
    let r = flags_from_vec(svec!["deno", "test", "--hide-stacktraces"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags {
          hide_stacktraces: true,
          update_snapshots: false,
          ..TestFlags::default()
        }),
        type_check_mode: TypeCheckMode::Local,
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        ..Flags::default()
      }
    );
  }

  #[test]
  fn upgrade_with_ca_file() {
    let r = flags_from_vec(svec!["deno", "upgrade", "--cert", "example.crt"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Upgrade(UpgradeFlags {
          force: false,
          dry_run: false,
          canary: false,
          no_delta: false,
          release_candidate: false,
          version: None,
          output: None,
          version_or_hash_or_channel: None,
          checksum: None,
          pr: None,
          branch: None,
        }),
        ca_data: Some(CaData::File("example.crt".to_owned())),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn upgrade_release_candidate() {
    let r = flags_from_vec(svec!["deno", "upgrade", "--rc"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Upgrade(UpgradeFlags {
          force: false,
          dry_run: false,
          canary: false,
          no_delta: false,
          release_candidate: true,
          version: None,
          output: None,
          version_or_hash_or_channel: None,
          checksum: None,
          pr: None,
          branch: None,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "upgrade", "--rc", "--canary"]);
    assert!(r.is_err());

    let r = flags_from_vec(svec!["deno", "upgrade", "--rc", "--version"]);
    assert!(r.is_err());
  }

  #[test]
  fn upgrade_pr() {
    let r = flags_from_vec(svec!["deno", "upgrade", "pr", "12345"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Upgrade(UpgradeFlags {
          force: false,
          dry_run: false,
          canary: false,
          no_delta: false,
          release_candidate: false,
          version: None,
          output: None,
          version_or_hash_or_channel: None,
          checksum: None,
          pr: Some(12345),
          branch: None,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn upgrade_pr_with_hash_prefix() {
    let r = flags_from_vec(svec!["deno", "upgrade", "pr", "#6789"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Upgrade(UpgradeFlags {
          force: false,
          dry_run: false,
          canary: false,
          no_delta: false,
          release_candidate: false,
          version: None,
          output: None,
          version_or_hash_or_channel: None,
          checksum: None,
          pr: Some(6789),
          branch: None,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn upgrade_pr_with_flags() {
    let r =
      flags_from_vec(svec!["deno", "upgrade", "--dry-run", "pr", "33250"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Upgrade(UpgradeFlags {
          force: false,
          dry_run: true,
          canary: false,
          no_delta: false,
          release_candidate: false,
          version: None,
          output: None,
          version_or_hash_or_channel: None,
          checksum: None,
          pr: Some(33250),
          branch: None,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn upgrade_pr_missing_number() {
    let r = flags_from_vec(svec!["deno", "upgrade", "pr"]);
    assert!(r.is_err());
  }

  #[test]
  fn upgrade_pr_invalid_number() {
    let r = flags_from_vec(svec!["deno", "upgrade", "pr", "abc"]);
    assert!(r.is_err());
  }

  #[test]
  fn cache_with_cafile() {
    let r = flags_from_vec(svec![
      "deno",
      "cache",
      "--cert",
      "example.crt",
      "script.ts",
      "script_two.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Cache(CacheFlags {
          files: svec!["script.ts", "script_two.ts"],
        }),
        ca_data: Some(CaData::File("example.crt".to_owned())),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn info_with_cafile() {
    let r = flags_from_vec(svec![
      "deno",
      "info",
      "--cert",
      "example.crt",
      "https://example.com"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Info(InfoFlags {
          json: false,
          file: Some("https://example.com".to_string()),
        }),
        ca_data: Some(CaData::File("example.crt".to_owned())),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn doc() {
    let r = flags_from_vec(svec!["deno", "doc", "--json", "path/to/module.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Doc(DocFlags {
          private: false,
          json: true,
          html: None,
          lint: false,
          source_files: DocSourceFileFlag::Paths(svec!["path/to/module.ts"]),
          filter: None,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "doc", "--html", "path/to/module.ts"]);
    assert!(r.is_ok());

    let r = flags_from_vec(svec![
      "deno",
      "doc",
      "--html",
      "--name=My library",
      "path/to/module.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Doc(DocFlags {
          private: false,
          json: false,
          lint: false,
          html: Some(DocHtmlFlag {
            name: Some("My library".to_string()),
            category_docs_path: None,
            symbol_redirect_map_path: None,
            default_symbol_map_path: None,
            strip_trailing_html: false,
            output: String::from("./docs/"),
          }),
          source_files: DocSourceFileFlag::Paths(svec!["path/to/module.ts"]),
          filter: None,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "doc",
      "--html",
      "--name=My library",
      "--lint",
      "--output=./foo",
      "path/to/module.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Doc(DocFlags {
          private: false,
          json: false,
          html: Some(DocHtmlFlag {
            name: Some("My library".to_string()),
            category_docs_path: None,
            symbol_redirect_map_path: None,
            default_symbol_map_path: None,
            strip_trailing_html: false,
            output: String::from("./foo"),
          }),
          lint: true,
          source_files: DocSourceFileFlag::Paths(svec!["path/to/module.ts"]),
          filter: None,
        }),
        ..Flags::default()
      }
    );

    let r =
      flags_from_vec(svec!["deno", "doc", "--html", "--name=My library",]);
    assert!(r.is_err());

    let r = flags_from_vec(svec![
      "deno",
      "doc",
      "--filter",
      "SomeClass.someField",
      "path/to/module.ts",
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Doc(DocFlags {
          private: false,
          json: false,
          html: None,
          lint: false,
          source_files: DocSourceFileFlag::Paths(vec![
            "path/to/module.ts".to_string()
          ]),
          filter: Some("SomeClass.someField".to_string()),
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "doc"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Doc(DocFlags {
          private: false,
          json: false,
          html: None,
          lint: false,
          source_files: Default::default(),
          filter: None,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "doc",
      "--filter",
      "Deno.Listener",
      "--builtin"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Doc(DocFlags {
          private: false,
          lint: false,
          json: false,
          html: None,
          source_files: DocSourceFileFlag::Builtin,
          filter: Some("Deno.Listener".to_string()),
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "doc",
      "--no-npm",
      "--no-remote",
      "--private",
      "path/to/module.js"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Doc(DocFlags {
          private: true,
          lint: false,
          json: false,
          html: None,
          source_files: DocSourceFileFlag::Paths(svec!["path/to/module.js"]),
          filter: None,
        }),
        no_npm: true,
        no_remote: true,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "doc",
      "path/to/module.js",
      "path/to/module2.js"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Doc(DocFlags {
          private: false,
          lint: false,
          json: false,
          html: None,
          source_files: DocSourceFileFlag::Paths(vec![
            "path/to/module.js".to_string(),
            "path/to/module2.js".to_string()
          ]),
          filter: None,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "doc",
      "path/to/module.js",
      "--builtin",
      "path/to/module2.js"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Doc(DocFlags {
          private: false,
          json: false,
          html: None,
          lint: false,
          source_files: DocSourceFileFlag::Paths(vec![
            "path/to/module.js".to_string(),
            "path/to/module2.js".to_string()
          ]),
          filter: None,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "doc", "--lint",]);
    assert!(r.is_err());

    let r = flags_from_vec(svec![
      "deno",
      "doc",
      "--lint",
      "path/to/module.js",
      "path/to/module2.js"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Doc(DocFlags {
          private: false,
          lint: true,
          json: false,
          html: None,
          source_files: DocSourceFileFlag::Paths(vec![
            "path/to/module.js".to_string(),
            "path/to/module2.js".to_string()
          ]),
          filter: None,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn inspect_default_host() {
    let r = flags_from_vec(svec!["deno", "run", "--inspect", "foo.js"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "foo.js".to_string(),
        )),
        inspect: Some("127.0.0.1:9229".parse().unwrap()),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn inspect_wait() {
    let r = flags_from_vec(svec!["deno", "--inspect-wait", "foo.js"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "foo.js".to_string(),
          bare: true,
          coverage_dir: None,
          print_task_list: false,
        }),
        inspect_wait: Some("127.0.0.1:9229".parse().unwrap()),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--inspect-wait=127.0.0.1:3567",
      "foo.js"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "foo.js".to_string(),
        )),
        inspect_wait: Some("127.0.0.1:3567".parse().unwrap()),
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn compile() {
    let r = flags_from_vec(svec![
      "deno",
      "compile",
      "https://examples.deno.land/color-logging.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Compile(CompileFlags {
          source_file: "https://examples.deno.land/color-logging.ts"
            .to_string(),
          output: None,
          args: vec![],
          target: None,
          no_terminal: false,
          icon: None,
          include: Default::default(),
          exclude: Default::default(),
          eszip: false,
          self_extracting: false,
          bundle: false,
          app_name: None,
          minify: false,
          exclude_unused_npm: false,
          engine: Default::default(),
        }),
        type_check_mode: TypeCheckMode::Local,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn compile_and_desktop_engine() {
    let flags = flags_from_vec(svec![
      "deno", "compile", "--engine", "quickjs", "main.ts"
    ])
    .unwrap();
    let DenoSubcommand::Compile(compile) = flags.subcommand else {
      panic!("expected compile subcommand");
    };
    assert_eq!(compile.engine, JavaScriptEngine::QuickJs);

    let flags = flags_from_vec(svec![
      "deno", "desktop", "--engine", "quickjs", "main.tsx"
    ])
    .unwrap();
    let DenoSubcommand::Desktop(desktop) = flags.subcommand else {
      panic!("expected desktop subcommand");
    };
    assert_eq!(desktop.engine, JavaScriptEngine::QuickJs);
  }

  #[test]
  fn desktop_backend_default() {
    let r = flags_from_vec(svec!["deno", "desktop", "main.tsx"]);
    let flags = r.unwrap();
    let DenoSubcommand::Desktop(desktop) = flags.subcommand else {
      panic!("expected desktop subcommand");
    };
    assert_eq!(desktop.source_file, "main.tsx");
    // No --backend flag leaves the field unset so `desktop.backend` in
    // deno.json (or the webview default) can take effect during config merge.
    assert_eq!(desktop.backend.as_deref(), None);
  }

  #[test]
  fn desktop_backend_explicit() {
    let r =
      flags_from_vec(svec!["deno", "desktop", "--backend", "cef", "main.tsx"]);
    let flags = r.unwrap();
    let DenoSubcommand::Desktop(desktop) = flags.subcommand else {
      panic!("expected desktop subcommand");
    };
    assert_eq!(desktop.backend.as_deref(), Some("cef"));
  }

  #[test]
  fn desktop_exclude_unused_npm() {
    let r = flags_from_vec(svec!["deno", "desktop", "main.tsx"]);
    let DenoSubcommand::Desktop(desktop) = r.unwrap().subcommand else {
      panic!("expected desktop subcommand");
    };
    // Off by default: the full managed npm snapshot keeps
    // non-statically-analyzable dynamic imports working.
    assert!(!desktop.exclude_unused_npm);

    let r = flags_from_vec(svec![
      "deno",
      "desktop",
      "--exclude-unused-npm",
      "main.tsx"
    ]);
    let DenoSubcommand::Desktop(desktop) = r.unwrap().subcommand else {
      panic!("expected desktop subcommand");
    };
    assert!(desktop.exclude_unused_npm);
  }

  #[test]
  fn compile_watch_with_no_clear_screen() {
    let r = flags_from_vec(svec![
      "deno",
      "compile",
      "--watch",
      "--no-clear-screen",
      "main.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Compile(CompileFlags {
          source_file: "main.ts".to_string(),
          output: None,
          args: vec![],
          target: None,
          no_terminal: false,
          icon: None,
          include: Default::default(),
          exclude: Default::default(),
          eszip: false,
          self_extracting: false,
          bundle: false,
          app_name: None,
          minify: false,
          exclude_unused_npm: false,
          engine: Default::default(),
        }),
        type_check_mode: TypeCheckMode::Local,
        code_cache_enabled: true,
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![],
          no_clear_screen: true,
          exclude: vec![],
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn compile_with_flags() {
    #[rustfmt::skip]
    let r = flags_from_vec(svec!["deno", "compile", "--include", "include.txt", "--exclude", "exclude.txt", "--import-map", "import_map.json", "--no-code-cache", "--no-remote", "--config", "tsconfig.json", "--no-check", "--unsafely-ignore-certificate-errors", "--reload", "--lock", "lock.json", "--cert", "example.crt", "--cached-only", "--location", "https:foo", "--allow-read", "--allow-net", "--v8-flags=--help", "--seed", "1", "--no-terminal", "--icon", "favicon.ico", "--output", "colors", "--env=.example.env", "https://examples.deno.land/color-logging.ts", "foo", "bar", "-p", "8080"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Compile(CompileFlags {
          source_file: "https://examples.deno.land/color-logging.ts"
            .to_string(),
          output: Some(String::from("colors")),
          args: svec!["foo", "bar", "-p", "8080"],
          target: None,
          no_terminal: true,
          icon: Some(String::from("favicon.ico")),
          include: vec!["include.txt".to_string()],
          exclude: vec!["exclude.txt".to_string()],
          eszip: false,
          self_extracting: false,
          bundle: false,
          app_name: None,
          minify: false,
          exclude_unused_npm: false,
          engine: Default::default(),
        }),
        import_map_path: Some("import_map.json".to_string()),
        no_remote: true,
        code_cache_enabled: false,
        config_flag: ConfigFlag::Path("tsconfig.json".to_owned()),
        type_check_mode: TypeCheckMode::None,
        reload: true,
        lock: Some(String::from("lock.json")),
        ca_data: Some(CaData::File("example.crt".to_string())),
        cached_only: true,
        location: Some(Url::parse("https://foo/").unwrap()),
        permissions: PermissionFlags {
          allow_read: Some(vec![]),
          allow_net: Some(vec![]),
          ..Default::default()
        },
        unsafely_ignore_certificate_errors: Some(vec![]),
        v8_flags: svec!["--help", "--random-seed=1"],
        seed: Some(1),
        env_file: Some(vec![".example.env".to_owned()]),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn compile_target_aarch64_windows() {
    // denort for Windows arm64 is built and published by CI, so
    // `--target aarch64-pc-windows-msvc` must be accepted by the SUPPORTED_OS
    // value parser (and an unsupported triple must still be rejected).
    let r = flags_from_vec(svec![
      "deno",
      "compile",
      "--target",
      "aarch64-pc-windows-msvc",
      "main.ts"
    ]);
    let DenoSubcommand::Compile(c) = r.unwrap().subcommand else {
      unreachable!()
    };
    assert_eq!(c.target, Some("aarch64-pc-windows-msvc".to_string()));

    assert!(
      flags_from_vec(svec![
        "deno",
        "compile",
        "--target",
        "riscv64gc-unknown-linux-gnu",
        "main.ts"
      ])
      .is_err()
    );
  }

  #[test]
  fn coverage() {
    let r = flags_from_vec(svec!["deno", "coverage", "foo.json"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Coverage(CoverageFlags {
          files: FileFlags {
            include: vec!["foo.json".to_string()],
            ignore: vec![],
          },
          include: vec![r"^file:".to_string()],
          exclude: vec![r"test\.(js|mjs|ts|jsx|tsx)$".to_string()],
          ..CoverageFlags::default()
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn coverage_with_threshold() {
    let r =
      flags_from_vec(svec!["deno", "coverage", "--threshold=80", "foo.json"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Coverage(CoverageFlags {
          files: FileFlags {
            include: vec!["foo.json".to_string()],
            ignore: vec![],
          },
          include: vec![r"^file:".to_string()],
          exclude: vec![r"test\.(js|mjs|ts|jsx|tsx)$".to_string()],
          threshold: Some(80),
          ..CoverageFlags::default()
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn coverage_threshold_out_of_range() {
    // Percentages above 100 are rejected by the value parser.
    let r =
      flags_from_vec(svec!["deno", "coverage", "--threshold=150", "foo.json"]);
    assert!(r.is_err());
  }

  #[test]
  fn coverage_with_lcov_and_out_file() {
    let r = flags_from_vec(svec![
      "deno",
      "coverage",
      "--lcov",
      "--output=foo.lcov",
      "foo.json"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Coverage(CoverageFlags {
          files: FileFlags {
            include: vec!["foo.json".to_string()],
            ignore: vec![],
          },
          include: vec![r"^file:".to_string()],
          exclude: vec![r"test\.(js|mjs|ts|jsx|tsx)$".to_string()],
          r#type: CoverageType::Lcov,
          threshold: None,
          output: Some(String::from("foo.lcov")),
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn coverage_with_default_files() {
    let r = flags_from_vec(svec!["deno", "coverage",]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Coverage(CoverageFlags {
          files: FileFlags {
            include: vec!["coverage".to_string()],
            ignore: vec![],
          },
          include: vec![r"^file:".to_string()],
          exclude: vec![r"test\.(js|mjs|ts|jsx|tsx)$".to_string()],
          ..CoverageFlags::default()
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn location_with_bad_scheme() {
    #[rustfmt::skip]
    let r = flags_from_vec(svec!["deno", "run", "--location", "foo:", "mod.ts"]);
    assert!(r.is_err());
    assert!(
      r.unwrap_err()
        .to_string()
        .contains("Expected protocol \"http\" or \"https\"")
    );
  }

  #[test]
  fn test_config_path_args() {
    let flags = flags_from_vec(svec!["deno", "run", "foo.js"]).unwrap();
    let cwd = resolve_cwd(None).unwrap().into_owned();

    assert_eq!(flags.config_path_args(&cwd), Some(vec![cwd.clone()]));

    let flags = flags_from_vec(svec!["deno", "run", "sub_dir/foo.js"]).unwrap();
    let cwd = resolve_cwd(None).unwrap().into_owned();
    assert_eq!(
      flags.config_path_args(&cwd),
      Some(vec![cwd.join("sub_dir").clone()])
    );

    let flags =
      flags_from_vec(svec!["deno", "https://example.com/foo.js"]).unwrap();
    assert_eq!(flags.config_path_args(&cwd), None);

    let flags = flags_from_vec(svec!["deno", "run", "jsr:@scope/foo"]).unwrap();
    assert_eq!(flags.config_path_args(&cwd), Some(vec![cwd.clone()]));

    let flags = flags_from_vec(svec!["deno", "run", "npm:@scope/foo"]).unwrap();
    assert_eq!(flags.config_path_args(&cwd), Some(vec![cwd.clone()]));

    let flags =
      flags_from_vec(svec!["deno", "lint", "dir/a/a.js", "dir/b/b.js"])
        .unwrap();
    assert_eq!(
      flags.config_path_args(&cwd),
      Some(vec![cwd.join("dir/a/a.js"), cwd.join("dir/b/b.js")])
    );

    let flags = flags_from_vec(svec!["deno", "lint"]).unwrap();
    assert_eq!(flags.config_path_args(&cwd), Some(vec![cwd.clone()]));

    let flags = flags_from_vec(svec!["deno", "cache", "sub/test.js"]).unwrap();
    assert_eq!(flags.config_path_args(&cwd), Some(vec![cwd.join("sub")]));

    let flags = flags_from_vec(svec!["deno", "cache", "."]).unwrap();
    assert_eq!(flags.config_path_args(&cwd), Some(vec![cwd.clone()]));

    let flags =
      flags_from_vec(svec!["deno", "install", "-e", "sub/test.js"]).unwrap();
    assert_eq!(flags.config_path_args(&cwd), Some(vec![cwd.join("sub")]));

    let flags = flags_from_vec(svec![
      "deno",
      "fmt",
      "dir/a/a.js",
      "dir/a/a2.js",
      "dir/b.js"
    ])
    .unwrap();
    assert_eq!(
      flags.config_path_args(&cwd),
      Some(vec![
        cwd.join("dir/a/a.js"),
        cwd.join("dir/a/a2.js"),
        cwd.join("dir/b.js")
      ])
    );

    // `deno desktop .` (and bare `deno desktop`) runs framework detection on
    // the current directory, so config discovery must start in that directory
    // rather than its parent. See issue #35653.
    let flags = flags_from_vec(svec!["deno", "desktop", "."]).unwrap();
    assert_eq!(flags.config_path_args(&cwd), Some(vec![cwd.clone()]));

    let flags = flags_from_vec(svec!["deno", "desktop"]).unwrap();
    assert_eq!(flags.config_path_args(&cwd), Some(vec![cwd.clone()]));

    // An explicit file entrypoint resolves to its containing directory.
    let flags =
      flags_from_vec(svec!["deno", "desktop", "sub/main.ts"]).unwrap();
    assert_eq!(flags.config_path_args(&cwd), Some(vec![cwd.join("sub")]));
  }

  #[test]
  fn test_no_clear_watch_flag_without_watch_flag() {
    let r = flags_from_vec(svec!["deno", "run", "--no-clear-screen", "foo.js"]);
    assert!(r.is_err());
    let error_message = r.unwrap_err().to_string();
    assert!(
      &error_message
        .contains("error: the following required arguments were not provided:")
    );
    assert!(&error_message.contains("--watch[=<FILES>...]"));
  }

  #[test]
  fn task_subcommand() {
    let r = flags_from_vec(svec!["deno", "task", "build", "hello", "world",]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: Some("build".to_string()),
          is_run: false,
          recursive: false,
          members: false,
          filter: None,
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        argv: svec!["hello", "world"],
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "task", "build"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: Some("build".to_string()),
          is_run: false,
          recursive: false,
          members: false,
          filter: None,
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "task", "--cwd", "foo", "build"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: Some("foo".to_string()),
          task: Some("build".to_string()),
          is_run: false,
          recursive: false,
          members: false,
          filter: None,
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "task", "--filter", "*", "build"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: Some("build".to_string()),
          is_run: false,
          recursive: false,
          members: false,
          filter: Some("*".to_string()),
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "task", "--recursive", "build"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: Some("build".to_string()),
          is_run: false,
          recursive: true,
          members: false,
          filter: Some("*".to_string()),
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "task", "-r", "build"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: Some("build".to_string()),
          is_run: false,
          recursive: true,
          members: false,
          filter: Some("*".to_string()),
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "task", "--members", "build"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: Some("build".to_string()),
          is_run: false,
          recursive: false,
          members: true,
          filter: Some("*".to_string()),
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "task", "--members", "-r", "build"]);
    assert!(r.is_err());

    let r =
      flags_from_vec(svec!["deno", "task", "--members", "-f", "*", "build"]);
    assert!(r.is_err());

    let r = flags_from_vec(svec!["deno", "task", "--eval", "echo 1"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: Some("echo 1".to_string()),
          is_run: false,
          recursive: false,
          members: false,
          filter: None,
          eval: true,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "task", "--eval"]);
    assert!(r.is_err());
  }

  #[test]
  fn task_subcommand_jobs() {
    // `--jobs`, its `--concurrency` alias, and the `-j` short form all parse
    // to the same value.
    for args in [
      svec!["deno", "task", "--jobs", "1", "build"],
      svec!["deno", "task", "--concurrency", "1", "build"],
      svec!["deno", "task", "-j", "1", "build"],
    ] {
      let r = flags_from_vec(args.clone());
      assert_eq!(
        r.unwrap(),
        Flags {
          subcommand: DenoSubcommand::Task(TaskFlags {
            cwd: None,
            task: Some("build".to_string()),
            is_run: false,
            recursive: false,
            members: false,
            filter: None,
            eval: false,
            no_prefix: false,
            concurrency: Some(NonZeroUsize::new(1).unwrap()),
            if_present: false,
          }),
          ..Flags::default()
        },
        "unexpected parse for {args:?}"
      );
    }

    // Reject zero, negative, and non-numeric values.
    for invalid in ["0", "-1", "abc"] {
      let r = flags_from_vec(svec!["deno", "task", "--jobs", invalid, "build"]);
      assert!(r.is_err(), "expected error for value {invalid:?}");
    }
  }

  #[test]
  fn task_subcommand_double_hyphen() {
    let r = flags_from_vec(svec![
      "deno",
      "task",
      "-c",
      "deno.json",
      "build",
      "--",
      "hello",
      "world",
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: Some("build".to_string()),
          is_run: false,
          recursive: false,
          members: false,
          filter: None,
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        argv: svec!["--", "hello", "world"],
        config_flag: ConfigFlag::Path("deno.json".to_owned()),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno", "task", "--cwd", "foo", "build", "--", "hello", "world"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: Some("foo".to_string()),
          task: Some("build".to_string()),
          is_run: false,
          recursive: false,
          members: false,
          filter: None,
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        argv: svec!["--", "hello", "world"],
        ..Flags::default()
      }
    );
  }

  #[test]
  fn task_subcommand_double_hyphen_only() {
    // edge case, but it should forward
    let r = flags_from_vec(svec!["deno", "task", "build", "--"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: Some("build".to_string()),
          is_run: false,
          recursive: false,
          members: false,
          filter: None,
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        argv: svec!["--"],
        ..Flags::default()
      }
    );
  }

  #[test]
  fn task_following_arg() {
    let r = flags_from_vec(svec!["deno", "task", "build", "-1", "--test"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: Some("build".to_string()),
          is_run: false,
          recursive: false,
          members: false,
          filter: None,
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        argv: svec!["-1", "--test"],
        ..Flags::default()
      }
    );
  }

  #[test]
  fn task_following_double_hyphen_arg() {
    let r = flags_from_vec(svec!["deno", "task", "build", "--test"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: Some("build".to_string()),
          is_run: false,
          recursive: false,
          members: false,
          filter: None,
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        argv: svec!["--test"],
        ..Flags::default()
      }
    );
  }

  #[test]
  fn task_with_global_flags() {
    // can fail if the custom parser in task_parse() starts at the wrong index
    let r = flags_from_vec(svec!["deno", "--quiet", "task", "build"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: Some("build".to_string()),
          is_run: false,
          recursive: false,
          members: false,
          filter: None,
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        log_level: Some(log::Level::Error),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn task_subcommand_empty() {
    let r = flags_from_vec(svec!["deno", "task"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: None,
          is_run: false,
          recursive: false,
          members: false,
          filter: None,
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn task_subcommand_config() {
    let r = flags_from_vec(svec!["deno", "task", "--config", "deno.jsonc"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: None,
          is_run: false,
          recursive: false,
          members: false,
          filter: None,
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        config_flag: ConfigFlag::Path("deno.jsonc".to_string()),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn task_subcommand_config_short() {
    let r = flags_from_vec(svec!["deno", "task", "-c", "deno.jsonc"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: None,
          is_run: false,
          recursive: false,
          members: false,
          filter: None,
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        config_flag: ConfigFlag::Path("deno.jsonc".to_string()),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn task_subcommand_noconfig_invalid() {
    let r = flags_from_vec(svec!["deno", "task", "--no-config"]);
    assert_eq!(
      r.unwrap_err().kind(),
      clap::error::ErrorKind::UnknownArgument
    );
  }

  #[test]
  fn task_subcommand_env_file() {
    let r = flags_from_vec(svec!["deno", "task", "--env-file", "build"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: Some("build".to_string()),
          is_run: false,
          recursive: false,
          members: false,
          filter: None,
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        env_file: Some(vec![".env".to_owned()]),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "task",
      "--env-file=.env.dev",
      "--env-file=.env.local",
      "build"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: Some("build".to_string()),
          is_run: false,
          recursive: false,
          members: false,
          filter: None,
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: false,
        }),
        env_file: Some(vec![".env.dev".to_owned(), ".env.local".to_owned()]),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn task_subcommand_if_present() {
    let r = flags_from_vec(svec!["deno", "task", "--if-present", "build"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Task(TaskFlags {
          cwd: None,
          task: Some("build".to_string()),
          is_run: false,
          recursive: false,
          members: false,
          filter: None,
          eval: false,
          no_prefix: false,
          concurrency: None,
          if_present: true,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bench_with_flags() {
    let r = flags_from_vec(svec![
      "deno",
      "bench",
      "--json",
      "--no-npm",
      "--no-remote",
      "--no-run",
      "--filter",
      "- foo",
      "--location",
      "https:foo",
      "--allow-net",
      "dir1/",
      "dir2/",
      "--",
      "arg1",
      "arg2"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Bench(BenchFlags {
          filter: Some("- foo".to_string()),
          json: true,
          no_run: true,
          files: FileFlags {
            include: vec!["dir1/".to_string(), "dir2/".to_string()],
            ignore: vec![],
          },
          permit_no_files: false,
        }),
        no_npm: true,
        no_remote: true,
        type_check_mode: TypeCheckMode::Local,
        location: Some(Url::parse("https://foo/").unwrap()),
        permissions: PermissionFlags {
          allow_net: Some(vec![]),
          no_prompt: true,
          ..Default::default()
        },
        argv: svec!["arg1", "arg2"],
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bench_watch() {
    let r = flags_from_vec(svec!["deno", "bench", "--watch"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Bench(BenchFlags {
          filter: None,
          json: false,
          no_run: false,
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          permit_no_files: false
        }),
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        type_check_mode: TypeCheckMode::Local,
        watch: Some(Default::default()),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bench_watch_with_paths() {
    let r = flags_from_vec(svec!["deno", "bench", "--watch=foo,bar"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Bench(BenchFlags {
          ..Default::default()
        }),
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        type_check_mode: TypeCheckMode::Local,
        watch: Some(WatchFlagsWithPaths {
          hmr: false,
          paths: vec![String::from("foo"), String::from("bar")],
          no_clear_screen: false,
          exclude: vec![],
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bench_no_files() {
    let r = flags_from_vec(svec!["deno", "bench", "--permit-no-files"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Bench(BenchFlags {
          filter: None,
          json: false,
          no_run: false,
          files: FileFlags {
            include: vec![],
            ignore: vec![],
          },
          permit_no_files: true
        }),
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        type_check_mode: TypeCheckMode::Local,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_with_check() {
    let r = flags_from_vec(svec!["deno", "run", "--check", "script.ts",]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        type_check_mode: TypeCheckMode::Local,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "run", "--check=all", "script.ts",]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        type_check_mode: TypeCheckMode::All,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "--check=foo", "script.ts",]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: true,
          coverage_dir: None,
          print_task_list: false,
        }),
        type_check_mode: TypeCheckMode::None,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--no-check",
      "--check",
      "script.ts",
    ]);
    assert!(r.is_err());
  }

  #[test]
  fn no_config() {
    let r = flags_from_vec(svec!["deno", "run", "--no-config", "script.ts",]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags::new_default(
          "script.ts".to_string(),
        )),
        config_flag: ConfigFlag::Disabled,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--config",
      "deno.json",
      "--no-config",
      "script.ts",
    ]);
    assert!(r.is_err());
  }

  #[test]
  fn init() {
    let r = flags_from_vec(svec!["deno", "init"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: None,
          package_args: vec![],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "init", "foo"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: None,
          package_args: vec![],
          dir: Some(String::from("foo")),
          lib: false,
          serve: false,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "init", "--quiet"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: None,
          package_args: vec![],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: false,
        }),
        log_level: Some(Level::Error),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "init", "--lib"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: None,
          package_args: vec![],
          dir: None,
          lib: true,
          serve: false,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "init", "--serve"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: None,
          package_args: vec![],
          dir: None,
          lib: false,
          serve: true,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "init", "foo", "--lib"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: None,
          package_args: vec![],
          dir: Some(String::from("foo")),
          lib: true,
          serve: false,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "init", "--lib", "--npm", "vite"]);
    assert!(r.is_err());

    let r = flags_from_vec(svec!["deno", "init", "--serve", "--npm", "vite"]);
    assert!(r.is_err());

    let r = flags_from_vec(svec!["deno", "init", "--npm", "vite", "--lib"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("npm:vite".to_string()),
          package_args: svec!["--lib"],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "init", "--npm", "vite", "--serve"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("npm:vite".to_string()),
          package_args: svec!["--serve"],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "init", "--npm", "vite", "new_dir"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("npm:vite".to_string()),
          package_args: svec!["new_dir"],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "init", "--npm", "--yes", "npm:vite"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("npm:vite".to_string()),
          package_args: vec![],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: true,
        }),
        ..Flags::default()
      }
    );

    // --jsr basic
    let r = flags_from_vec(svec!["deno", "init", "--jsr", "@denotest/create"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("jsr:@denotest/create".to_string()),
          package_args: vec![],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    // --jsr with jsr: prefix already present
    let r = flags_from_vec(svec!["deno", "init", "--jsr", "jsr:@fresh/init"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("jsr:@fresh/init".to_string()),
          package_args: vec![],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    // --jsr with --yes
    let r = flags_from_vec(svec![
      "deno",
      "init",
      "--jsr",
      "--yes",
      "@denotest/create"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("jsr:@denotest/create".to_string()),
          package_args: vec![],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: true,
        }),
        ..Flags::default()
      }
    );

    // --jsr with extra args
    let r = flags_from_vec(svec![
      "deno",
      "init",
      "--jsr",
      "@denotest/create",
      "my-project"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("jsr:@denotest/create".to_string()),
          package_args: svec!["my-project"],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    // --jsr conflicts with --npm, --lib, --serve, --empty
    let r = flags_from_vec(svec!["deno", "init", "--jsr", "--npm", "@foo/bar"]);
    assert!(r.is_err());

    let r = flags_from_vec(svec!["deno", "init", "--jsr", "--lib", "@foo/bar"]);
    assert!(r.is_err());

    // --jsr without package name
    let r = flags_from_vec(svec!["deno", "init", "--jsr"]);
    assert!(r.is_err());
  }

  #[test]
  fn create() {
    let r = flags_from_vec(svec!["deno", "create"]);
    assert!(r.is_err());

    let r = flags_from_vec(svec!["deno", "create", "vite"]);
    assert!(r.is_err());

    let r = flags_from_vec(svec!["deno", "create", "npm:vite", "my-project"]);
    assert!(r.is_err());

    let r =
      flags_from_vec(svec!["deno", "create", "npm:vite", "--", "my-project"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("npm:vite".to_string()),
          package_args: svec!["my-project"],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "create", "--npm", "vite"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("npm:vite".to_string()),
          package_args: vec![],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    let r =
      flags_from_vec(svec!["deno", "create", "--npm", "vite", "my-project"]);
    assert!(r.is_err());

    let r = flags_from_vec(svec!["deno", "create", "--yes", "npm:vite"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("npm:vite".to_string()),
          package_args: vec![],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: true,
        }),
        ..Flags::default()
      }
    );

    let r =
      flags_from_vec(svec!["deno", "create", "jsr:@std/http/file-server"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("jsr:@std/http/file-server".to_string()),
          package_args: vec![],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "create", "jsr:@fresh/init"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("jsr:@fresh/init".to_string()),
          package_args: vec![],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "create", "--yes", "jsr:@fresh/init"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("jsr:@fresh/init".to_string()),
          package_args: vec![],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: true,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "create",
      "jsr:@fresh/init",
      "--",
      "my-project"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("jsr:@fresh/init".to_string()),
          package_args: svec!["my-project"],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    // empty jsr: prefix
    let r = flags_from_vec(svec!["deno", "create", "jsr:"]);
    assert!(r.is_err());

    // --jsr flag
    let r = flags_from_vec(svec!["deno", "create", "--jsr", "@fresh/init"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("jsr:@fresh/init".to_string()),
          package_args: vec![],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: false,
        }),
        ..Flags::default()
      }
    );

    // --jsr with --yes
    let r =
      flags_from_vec(svec!["deno", "create", "--jsr", "--yes", "@fresh/init"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Init(InitFlags {
          package: Some("jsr:@fresh/init".to_string()),
          package_args: vec![],
          dir: None,
          lib: false,
          serve: false,
          empty: false,
          yes: true,
        }),
        ..Flags::default()
      }
    );

    // --jsr with npm: specifier is contradictory
    let r = flags_from_vec(svec!["deno", "create", "--jsr", "npm:vite"]);
    assert!(r.is_err());

    // --jsr and --npm conflict
    let r = flags_from_vec(svec!["deno", "create", "--jsr", "--npm", "@foo"]);
    assert!(r.is_err());

    let r = flags_from_vec(svec!["deno", "create", "npm:"]);
    assert!(r.is_err());

    // --npm with jsr: is contradictory
    let r = flags_from_vec(svec!["deno", "create", "--npm", "jsr:@std/http"]);
    assert!(r.is_err());
  }

  #[test]
  fn jupyter() {
    let r = flags_from_vec(svec!["deno", "jupyter"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Jupyter(JupyterFlags {
          install: false,
          kernel: false,
          conn_file: None,
          name: None,
          display: None,
          force: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "jupyter", "--install"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Jupyter(JupyterFlags {
          install: true,
          kernel: false,
          conn_file: None,
          name: None,
          display: None,
          force: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "jupyter", "--install", "--force"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Jupyter(JupyterFlags {
          install: true,
          kernel: false,
          conn_file: None,
          name: None,
          display: None,
          force: true,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "jupyter",
      "--install",
      "--name",
      "debugdeno",
      "--display",
      "Deno (debug)"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Jupyter(JupyterFlags {
          install: true,
          kernel: false,
          conn_file: None,
          name: Some("debugdeno".to_string()),
          display: Some("Deno (debug)".to_string()),
          force: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec!["deno", "jupyter", "-n", "debugdeno",]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Jupyter(JupyterFlags {
          install: false,
          kernel: false,
          conn_file: None,
          name: Some("debugdeno".to_string()),
          display: None,
          force: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "jupyter",
      "--kernel",
      "--conn",
      "path/to/conn/file"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Jupyter(JupyterFlags {
          install: false,
          kernel: true,
          conn_file: Some(String::from("path/to/conn/file")),
          name: None,
          display: None,
          force: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "jupyter",
      "--install",
      "--conn",
      "path/to/conn/file"
    ]);
    r.unwrap_err();
    let r = flags_from_vec(svec!["deno", "jupyter", "--kernel",]);
    r.unwrap_err();
    let r = flags_from_vec(svec!["deno", "jupyter", "--install", "--kernel",]);
    r.unwrap_err();
    let r = flags_from_vec(svec!["deno", "jupyter", "--display", "deno"]);
    r.unwrap_err();
    let r = flags_from_vec(svec!["deno", "jupyter", "--kernel", "--display"]);
    r.unwrap_err();
    let r = flags_from_vec(svec!["deno", "jupyter", "--force"]);
    r.unwrap_err();
  }

  #[test]
  fn publish_args() {
    let r = flags_from_vec(svec![
      "deno",
      "publish",
      "--no-provenance",
      "--dry-run",
      "--allow-slow-types",
      "--allow-dirty",
      "--token=asdf",
      "--set-version=1.0.1",
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Publish(PublishFlags {
          token: Some("asdf".to_string()),
          dry_run: true,
          allow_slow_types: true,
          allow_dirty: true,
          no_provenance: true,
          set_version: Some("1.0.1".to_string()),
        }),
        type_check_mode: TypeCheckMode::Local,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn add_or_install_subcommand() {
    let r = flags_from_vec(svec!["deno", "add"]);
    r.unwrap_err();
    for cmd in ["add", "install"] {
      let mk_flags = |flags: AddFlags| -> Flags {
        match cmd {
          "add" => Flags {
            subcommand: DenoSubcommand::Add(flags),
            ..Flags::default()
          },
          "install" => Flags {
            subcommand: DenoSubcommand::Install(InstallFlags::Local(
              InstallFlagsLocal::Add(flags),
              Default::default(),
            )),
            ..Flags::default()
          },
          _ => unreachable!(),
        }
      };

      {
        let r = flags_from_vec(svec!["deno", cmd, "@david/which"]);
        assert_eq!(
          r.unwrap(),
          mk_flags(AddFlags {
            packages: svec!["@david/which"],
            dev: false, // default is false
            optional: false,
            no_save: false,
            default_registry: Some(DefaultRegistry::Npm),
            lockfile_only: false,
            save_exact: false,
            package_json: false,
            unscoped: false,
          })
        );
      }
      {
        let r = flags_from_vec(svec![
          "deno",
          cmd,
          "--frozen",
          "--lockfile-only",
          "@david/which",
          "@luca/hello"
        ]);
        let mut expected_flags = mk_flags(AddFlags {
          packages: svec!["@david/which", "@luca/hello"],
          dev: false,
          optional: false,
          no_save: false,
          default_registry: Some(DefaultRegistry::Npm),
          lockfile_only: true,
          save_exact: false,
          package_json: false,
          unscoped: false,
        });
        expected_flags.frozen_lockfile = Some(true);
        assert_eq!(r.unwrap(), expected_flags);
      }
      {
        let r = flags_from_vec(svec!["deno", cmd, "--dev", "npm:chalk"]);
        assert_eq!(
          r.unwrap(),
          mk_flags(AddFlags {
            packages: svec!["npm:chalk"],
            dev: true,
            optional: false,
            no_save: false,
            default_registry: Some(DefaultRegistry::Npm),
            lockfile_only: false,
            save_exact: false,
            package_json: false,
            unscoped: false,
          }),
        );
      }
      {
        let r = flags_from_vec(svec!["deno", cmd, "--npm", "chalk"]);
        assert_eq!(
          r.unwrap(),
          mk_flags(AddFlags {
            packages: svec!["chalk"],
            dev: false,
            optional: false,
            no_save: false,
            default_registry: Some(DefaultRegistry::Npm),
            lockfile_only: false,
            save_exact: false,
            package_json: false,
            unscoped: false,
          }),
        );
      }
      {
        let r = flags_from_vec(svec!["deno", cmd, "--jsr", "@std/fs"]);
        assert_eq!(
          r.unwrap(),
          mk_flags(AddFlags {
            packages: svec!["@std/fs"],
            dev: false,
            optional: false,
            no_save: false,
            default_registry: Some(DefaultRegistry::Jsr),
            lockfile_only: false,
            save_exact: false,
            package_json: false,
            unscoped: false,
          }),
        );
      }
      for arg in ["--save-optional", "-O"] {
        let r = flags_from_vec(svec!["deno", cmd, arg, "npm:chalk"]);
        assert_eq!(
          r.unwrap(),
          mk_flags(AddFlags {
            packages: svec!["npm:chalk"],
            dev: false,
            optional: true,
            no_save: false,
            default_registry: Some(DefaultRegistry::Npm),
            lockfile_only: false,
            save_exact: false,
            package_json: false,
            unscoped: false,
          }),
        );
      }
      {
        let r = flags_from_vec(svec!["deno", cmd, "--no-save", "npm:chalk"]);
        assert_eq!(
          r.unwrap(),
          mk_flags(AddFlags {
            packages: svec!["npm:chalk"],
            dev: false,
            optional: false,
            no_save: true,
            default_registry: Some(DefaultRegistry::Npm),
            lockfile_only: false,
            save_exact: false,
            package_json: false,
            unscoped: false,
          }),
        );
      }
      {
        let r =
          flags_from_vec(svec!["deno", cmd, "--unscoped", "jsr:@david/which"]);
        assert_eq!(
          r.unwrap(),
          mk_flags(AddFlags {
            packages: svec!["jsr:@david/which"],
            dev: false,
            optional: false,
            no_save: false,
            default_registry: Some(DefaultRegistry::Npm),
            lockfile_only: false,
            save_exact: false,
            package_json: false,
            unscoped: true,
          }),
        );
      }
      // --save-optional conflicts with --dev
      assert!(
        flags_from_vec(svec![
          "deno",
          cmd,
          "--save-optional",
          "--dev",
          "npm:chalk"
        ])
        .is_err()
      );
      // --no-save conflicts with --dev and --save-optional
      assert!(
        flags_from_vec(svec!["deno", cmd, "--no-save", "--dev", "npm:chalk"])
          .is_err()
      );
      assert!(
        flags_from_vec(svec![
          "deno",
          cmd,
          "--no-save",
          "--save-optional",
          "npm:chalk"
        ])
        .is_err()
      );
    }

    {
      let r = flags_from_vec(svec![
        "deno",
        "add",
        "--allow-import=example.com",
        "@david/which"
      ]);
      assert_eq!(
        r.unwrap(),
        Flags {
          subcommand: DenoSubcommand::Add(AddFlags {
            packages: svec!["@david/which"],
            dev: false,
            optional: false,
            no_save: false,
            default_registry: Some(DefaultRegistry::Npm),
            lockfile_only: false,
            save_exact: false,
            package_json: false,
            unscoped: false,
          }),
          permissions: PermissionFlags {
            allow_import: Some(svec!["example.com"]),
            ..Default::default()
          },
          ..Flags::default()
        }
      );
    }
  }

  #[test]
  fn remove_subcommand() {
    let r = flags_from_vec(svec!["deno", "remove"]);
    r.unwrap_err();

    let r = flags_from_vec(svec!["deno", "remove", "@david/which"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Remove(RemoveFlags {
          packages: svec!["@david/which"],
          lockfile_only: false,
          package_json: false,
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "remove",
      "--frozen",
      "--lockfile-only",
      "@david/which",
      "@luca/hello"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Remove(RemoveFlags {
          packages: svec!["@david/which", "@luca/hello"],
          lockfile_only: true,
          package_json: false,
        }),
        frozen_lockfile: Some(true),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn remove_global_alias_for_uninstall() {
    // `deno remove --global <name>` is an alias for `deno uninstall --global
    // <name>` and produces the exact same subcommand.
    for global_flag in ["--global", "-g"] {
      let r =
        flags_from_vec(svec!["deno", "remove", global_flag, "file_server"]);
      assert_eq!(
        r.unwrap(),
        Flags {
          subcommand: DenoSubcommand::Uninstall(UninstallFlags {
            kind: UninstallKind::Global(UninstallFlagsGlobal {
              packages: svec!["file_server"],
              root: None,
            }),
          }),
          ..Flags::default()
        }
      );
    }

    // `--root` is honored, just like `deno uninstall --global --root`.
    let r = flags_from_vec(svec![
      "deno",
      "remove",
      "-g",
      "--root",
      "/user/foo/bar",
      "file_server"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Uninstall(UninstallFlags {
          kind: UninstallKind::Global(UninstallFlagsGlobal {
            packages: svec!["file_server"],
            root: Some("/user/foo/bar".to_string()),
          }),
        }),
        ..Flags::default()
      }
    );

    // A global removal accepts multiple executables, like `deno uninstall
    // --global`.
    let r =
      flags_from_vec(svec!["deno", "remove", "-g", "file_server", "chalk"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Uninstall(UninstallFlags {
          kind: UninstallKind::Global(UninstallFlagsGlobal {
            packages: svec!["file_server", "chalk"],
            root: None,
          }),
        }),
        ..Flags::default()
      }
    );

    // `--root` requires `--global`.
    let r =
      flags_from_vec(svec!["deno", "remove", "--root", "/tmp", "@std/path"]);
    assert!(r.is_err());

    // Without `--global`, removal stays a config-file dependency removal.
    let r = flags_from_vec(svec!["deno", "remove", "@david/which"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Remove(RemoveFlags {
          packages: svec!["@david/which"],
          lockfile_only: false,
          package_json: false,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn run_with_frozen_lockfile() {
    let cases = [
      (Some("--frozen"), Some(true)),
      (Some("--frozen=true"), Some(true)),
      (Some("--frozen=false"), Some(false)),
      (None, None),
    ];
    for (flag, frozen) in cases {
      let mut args = svec!["deno", "run"];
      if let Some(f) = flag {
        args.push(f.into());
      }
      args.push("script.ts".into());
      let r = flags_from_vec(args);
      assert_eq!(
        r.unwrap(),
        Flags {
          subcommand: DenoSubcommand::Run(RunFlags::new_default(
            "script.ts".to_string(),
          )),
          frozen_lockfile: frozen,
          code_cache_enabled: true,
          ..Flags::default()
        }
      );
    }
  }

  #[test]
  fn allow_scripts() {
    let cases = [
      (Some("--allow-scripts"), Ok(PackagesAllowedScripts::All)),
      (None, Ok(PackagesAllowedScripts::None)),
      (
        Some("--allow-scripts=npm:foo"),
        Ok(PackagesAllowedScripts::Some(vec![
          PackageReq::from_str("foo").unwrap(),
        ])),
      ),
      (
        Some("--allow-scripts=npm:foo,npm:bar@2"),
        Ok(PackagesAllowedScripts::Some(vec![
          PackageReq::from_str("foo").unwrap(),
          PackageReq::from_str("bar@2").unwrap(),
        ])),
      ),
      (Some("--allow-scripts=foo"), Err("Invalid package")),
      (
        Some("--allow-scripts=npm:foo@next"),
        Err("Tags are not supported in --allow-scripts: npm:foo@next"),
      ),
      (
        Some("--allow-scripts=jsr:@foo/bar"),
        Err("An 'npm:' specifier is required"),
      ),
    ];
    for (flag, value) in cases {
      let mut args = svec!["deno", "cache"];
      if let Some(flag) = flag {
        args.push(flag.into());
      }
      args.push("script.ts".into());
      let r = flags_from_vec(args);
      match value {
        Ok(value) => {
          assert_eq!(
            r.unwrap(),
            Flags {
              subcommand: DenoSubcommand::Cache(CacheFlags {
                files: svec!["script.ts"],
              }),
              allow_scripts: value,
              ..Flags::default()
            }
          );
        }
        Err(e) => {
          let err = r.unwrap_err();
          assert!(
            err.to_string().contains(e),
            "expected to contain '{e}' got '{err}'"
          );
        }
      }
    }
  }

  #[test]
  fn x_ignore_scripts() {
    let flags = flags_from_vec(svec![
      "deno",
      "x",
      "--ignore-scripts",
      "-y",
      "npm:foo",
      "--bar"
    ])
    .unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::X(XFlags {
          kind: XFlagsKind::Command(XCommandFlags {
            yes: true,
            command: "npm:foo".to_string(),
            ignore_scripts: PackagesAllowedScripts::All,
            package: None,
          }),
        }),
        argv: svec!["--bar"],
        permissions: PermissionFlags {
          allow_all: true,
          ..Default::default()
        },
        ..Flags::default()
      }
    );

    let err = flags_from_vec(svec![
      "deno",
      "x",
      "--ignore-scripts",
      "--allow-scripts",
      "npm:foo"
    ])
    .unwrap_err();
    assert!(err.to_string().contains("cannot be used with"));

    let flags = flags_from_vec(svec![
      "deno",
      "x",
      "--ignore-scripts=foo,npm:bar@2",
      "npm:foo"
    ])
    .unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::X(XFlags {
          kind: XFlagsKind::Command(XCommandFlags {
            yes: false,
            command: "npm:foo".to_string(),
            ignore_scripts: PackagesAllowedScripts::Some(vec![
              PackageReq::from_str("foo").unwrap(),
              PackageReq::from_str("bar@2").unwrap(),
            ]),
            package: None,
          }),
        }),
        permissions: PermissionFlags {
          allow_all: true,
          ..Default::default()
        },
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bare_run() {
    let r = flags_from_vec(svec!["deno", "--no-config", "script.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          bare: true,
          coverage_dir: None,
          print_task_list: false,
        }),
        config_flag: ConfigFlag::Disabled,
        code_cache_enabled: true,
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bare_global() {
    let r = flags_from_vec(svec!["deno", "--log-level=debug"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Repl(ReplFlags {
          eval_files: None,
          eval: None,
          is_default_command: true,
          json: false,
        }),
        log_level: Some(Level::Debug),
        permissions: PermissionFlags {
          allow_all: true,
          ..Default::default()
        },
        ..Flags::default()
      }
    );
  }

  #[test]
  fn repl_user_args() {
    let r = flags_from_vec(svec!["deno", "repl", "foo"]);
    assert!(r.is_err());
    let r = flags_from_vec(svec!["deno", "repl", "--", "foo"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Repl(ReplFlags {
          eval_files: None,
          eval: None,
          is_default_command: false,
          json: false,
        }),
        argv: svec!["foo"],
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bare_with_flag_no_file() {
    let r = flags_from_vec(svec!["deno", "--no-config"]);

    let err = r.unwrap_err();
    assert!(err.to_string().contains("error: [SCRIPT_ARG] may only be omitted with --v8-flags=--help, else to use the repl with arguments, please use the `deno repl` subcommand"));
    assert!(
      err
        .to_string()
        .contains("Usage: deno [OPTIONS] [COMMAND] [SCRIPT_ARG]...")
    );
  }

  #[test]
  fn subcommands_recognized_by_node_shim() {
    // When `deno <subcommand>` is spawned via `node:child_process`, the args
    // are passed through unchanged only if node_shim recognizes the
    // subcommand. Otherwise it is misinterpreted as a script for `deno run`
    // (see #35591). This test fails if a subcommand is added to the CLI
    // without also adding it to `DENO_SUBCOMMANDS` in `libs/node_shim/lib.rs`.
    //
    // Most subcommands are registered lazily via clap's `.defer(...)`, so the
    // command must be built before they show up in `get_subcommands()`.
    let mut root = clap_root();
    root.build();
    for command in root.get_subcommands() {
      let name = command.get_name();
      assert!(
        node_shim::is_deno_subcommand(name),
        "subcommand `{name}` is missing from node_shim's DENO_SUBCOMMANDS list; \
         add it to `libs/node_shim/lib.rs` so it is passed through when spawned \
         via node:child_process",
      );
      for alias in command.get_visible_aliases() {
        assert!(
          node_shim::is_deno_subcommand(alias),
          "visible alias `{alias}` of subcommand `{name}` is missing from \
           node_shim's DENO_SUBCOMMANDS list; add it to `libs/node_shim/lib.rs`",
        );
      }
    }
  }

  #[test]
  fn equal_help_output() {
    for command in clap_root().get_subcommands() {
      if command.get_name() == "help" {
        continue;
      }

      let long_flag =
        match flags_from_vec(svec!["deno", command.get_name(), "--help"])
          .unwrap()
          .subcommand
        {
          DenoSubcommand::Help(help) => help.help.to_string(),
          _ => {
            unreachable!()
          }
        };
      let short_flag =
        match flags_from_vec(svec!["deno", command.get_name(), "-h"])
          .unwrap()
          .subcommand
        {
          DenoSubcommand::Help(help) => help.help.to_string(),
          _ => {
            unreachable!()
          }
        };
      let subcommand =
        match flags_from_vec(svec!["deno", "help", command.get_name()])
          .unwrap()
          .subcommand
        {
          DenoSubcommand::Help(help) => help.help.to_string(),
          _ => {
            unreachable!()
          }
        };
      assert_eq!(long_flag, short_flag, "{} subcommand", command.get_name());
      assert_eq!(long_flag, subcommand, "{} subcommand", command.get_name());
    }
  }

  #[test]
  fn ci_subcommand_defaults() {
    let r = flags_from_vec(svec!["deno", "ci"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Ci(CiFlags::default()),
        frozen_lockfile: Some(true),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn ci_subcommand_prod_skip_types() {
    let r = flags_from_vec(svec!["deno", "ci", "--prod", "--skip-types"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Ci(CiFlags {
          production: true,
          skip_types: true,
        }),
        frozen_lockfile: Some(true),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn install_permissions_non_global() {
    let r =
      flags_from_vec(svec!["deno", "install", "--allow-net", "jsr:@std/fs"]);

    assert!(
      r.unwrap_err().to_string().contains(
        "Note: Permission flags can only be used in a global setting"
      )
    );
  }

  #[test]
  fn install_os_arch_flags() {
    let r = flags_from_vec(svec![
      "deno", "install", "--os", "linux", "--arch", "arm64"
    ]);
    let flags = r.unwrap();
    assert_eq!(
      flags.subcommand,
      DenoSubcommand::Install(InstallFlags::Local(
        InstallFlagsLocal::TopLevel(InstallTopLevelFlags {
          lockfile_only: false,
          production: false,
          skip_types: false,
        }),
        NpmInstallTargetFlags {
          os: Some("linux".to_string()),
          arch: Some("arm64".to_string()),
        },
      ))
    );
    assert_eq!(
      flags.subcommand.npm_system_info(),
      NpmSystemInfo {
        os: "linux".into(),
        cpu: "arm64".into(),
      }
    );
  }

  #[test]
  fn install_os_only_flag() {
    let r = flags_from_vec(svec!["deno", "install", "--os", "win32"]);
    let flags = r.unwrap();
    assert_eq!(
      flags.subcommand,
      DenoSubcommand::Install(InstallFlags::Local(
        InstallFlagsLocal::TopLevel(InstallTopLevelFlags {
          lockfile_only: false,
          production: false,
          skip_types: false,
        }),
        NpmInstallTargetFlags {
          os: Some("win32".to_string()),
          arch: None,
        },
      ))
    );
    let sys_info = flags.subcommand.npm_system_info();
    assert_eq!(sys_info.os.as_str(), "win32");
  }

  #[test]
  fn install_os_arch_conflicts_with_global() {
    let r =
      flags_from_vec(svec!["deno", "install", "-g", "--os", "linux", "mod.ts"]);
    assert!(r.is_err());
  }

  #[test]
  fn install_production() {
    let r = flags_from_vec(svec!["deno", "install", "--prod"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Install(InstallFlags::Local(
          InstallFlagsLocal::TopLevel(InstallTopLevelFlags {
            lockfile_only: false,
            production: true,
            skip_types: false,
          }),
          NpmInstallTargetFlags::default(),
        )),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn install_production_with_entrypoint() {
    let r = flags_from_vec(svec![
      "deno",
      "install",
      "--prod",
      "--entrypoint",
      "main.ts"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Install(InstallFlags::Local(
          InstallFlagsLocal::Entrypoints(InstallEntrypointsFlags {
            entrypoints: svec!["main.ts"],
            lockfile_only: false,
            production: true,
            skip_types: false,
          }),
          NpmInstallTargetFlags::default(),
        )),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn install_production_with_skip_types() {
    let r = flags_from_vec(svec!["deno", "install", "--prod", "--skip-types"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Install(InstallFlags::Local(
          InstallFlagsLocal::TopLevel(InstallTopLevelFlags {
            lockfile_only: false,
            production: true,
            skip_types: true,
          }),
          NpmInstallTargetFlags::default(),
        )),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn install_skip_types_requires_prod() {
    let r = flags_from_vec(svec!["deno", "install", "--skip-types"]);
    assert!(r.is_err());
  }

  #[test]
  fn install_production_conflicts_with_global() {
    let r = flags_from_vec(svec![
      "deno",
      "install",
      "--prod",
      "--global",
      "jsr:@std/http/file-server"
    ]);
    assert!(r.is_err());
  }

  #[test]
  fn install_production_conflicts_with_dev() {
    let r =
      flags_from_vec(svec!["deno", "install", "--prod", "--dev", "npm:chalk"]);
    assert!(r.is_err());
  }

  #[test]
  fn jupyter_unstable_flags() {
    let r = flags_from_vec(svec![
      "deno",
      "jupyter",
      "--unstable-ffi",
      "--unstable-bare-node-builtins",
      "--unstable-worker-options"
    ]);

    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Jupyter(JupyterFlags {
          install: false,
          kernel: false,
          conn_file: None,
          name: None,
          display: None,
          force: false,
        }),
        unstable_config: UnstableConfig {
          sloppy_imports: false,
          features: svec!["bare-node-builtins", "ffi", "worker-options"],
          ..Default::default()
        },
        ..Flags::default()
      }
    );
  }

  #[test]
  fn serve_with_allow_all() {
    let r = flags_from_vec(svec!["deno", "serve", "--allow-all", "./main.ts"]);
    let flags = r.unwrap();
    assert_eq!(
      &flags,
      &Flags {
        subcommand: DenoSubcommand::Serve(ServeFlags::new_default(
          "./main.ts".into(),
          8000,
          "0.0.0.0"
        )),
        permissions: PermissionFlags {
          allow_all: true,
          allow_net: None,
          ..Default::default()
        },
        code_cache_enabled: true,
        ..Default::default()
      }
    );
  }

  #[test]
  fn escape_and_split_commas_test() {
    assert_eq!(escape_and_split_commas("foo".to_string()).unwrap(), ["foo"]);
    assert!(escape_and_split_commas("foo,".to_string()).is_err());
    assert_eq!(
      escape_and_split_commas("foo,,".to_string()).unwrap(),
      ["foo,"]
    );
    assert!(escape_and_split_commas("foo,,,".to_string()).is_err());
    assert_eq!(
      escape_and_split_commas("foo,,,,".to_string()).unwrap(),
      ["foo,,"]
    );
    assert_eq!(
      escape_and_split_commas("foo,bar".to_string()).unwrap(),
      ["foo", "bar"]
    );
    assert_eq!(
      escape_and_split_commas("foo,,bar".to_string()).unwrap(),
      ["foo,bar"]
    );
    assert_eq!(
      escape_and_split_commas("foo,,,bar".to_string()).unwrap(),
      ["foo,", "bar"]
    );
  }

  #[test]
  fn net_flag_with_url() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--allow-net=https://example.com",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap_err().to_string(),
      "error: invalid value 'https://example.com': URLs are not supported, only domains and IPs"
    );
  }

  #[test]
  fn node_modules_dir_default() {
    let r =
      flags_from_vec(svec!["deno", "run", "--node-modules-dir", "./foo.ts"]);
    let flags = r.unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "./foo.ts".into(),
          ..Default::default()
        }),
        node_modules_dir: Some(NodeModulesDirMode::Auto),
        code_cache_enabled: true,
        ..Default::default()
      }
    )
  }

  #[test]
  fn flag_before_subcommand() {
    let r = flags_from_vec(svec!["deno", "--allow-net", "repl"]);
    assert_eq!(
      r.unwrap_err().to_string(),
      "error: unexpected argument '--allow-net' found

  tip: 'repl --allow-net' exists

Usage: deno repl [OPTIONS] [-- [ARGS]...]\n"
    )
  }

  #[test]
  fn flag_before_subcommand_not_supported() {
    // `lint` does not accept `--allow-all`, so the error must not claim that
    // `lint --allow-all` exists (see issue #27336).
    let r = flags_from_vec(svec!["deno", "--allow-all", "lint"]);
    assert_eq!(
      r.unwrap_err().to_string(),
      "error: unexpected argument '--allow-all' found

Usage: deno lint [OPTIONS] [files]...\n"
    )
  }

  #[test]
  fn allow_all_conflicts_allow_perms() {
    let flags = [
      "--allow-read",
      "--allow-write",
      "--allow-net",
      "--allow-env",
      "--allow-run",
      "--allow-sys",
      "--allow-ffi",
      "--allow-import",
    ];
    for flag in flags {
      let r =
        flags_from_vec(svec!["deno", "run", "--allow-all", flag, "foo.ts"]);
      assert!(r.is_err());
    }
  }

  #[test]
  fn allow_import_with_url() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--allow-import=https://example.com",
      "script.ts"
    ]);
    assert_eq!(
      r.unwrap_err().to_string(),
      "error: invalid value 'https://example.com': URLs are not supported, only domains and IPs"
    );
  }

  #[test]
  fn deny_import_with_url() {
    let r = flags_from_vec(svec![
      "deno",
      "run",
      "--deny-import=https://example.com",
      "script.ts",
    ]);
    assert_eq!(
      r.unwrap_err().to_string(),
      "error: invalid value 'https://example.com': URLs are not supported, only domains and IPs"
    );
  }

  #[test]
  fn outdated_subcommand() {
    let cases = [
      (
        svec![],
        OutdatedFlags {
          filters: vec![],
          kind: OutdatedKind::PrintOutdated { compatible: false },
          recursive: false,
        },
      ),
      (
        svec!["--recursive"],
        OutdatedFlags {
          filters: vec![],
          kind: OutdatedKind::PrintOutdated { compatible: false },
          recursive: true,
        },
      ),
      (
        svec!["--recursive", "--compatible"],
        OutdatedFlags {
          filters: vec![],
          kind: OutdatedKind::PrintOutdated { compatible: true },
          recursive: true,
        },
      ),
      (
        svec!["--update"],
        OutdatedFlags {
          filters: vec![],
          kind: OutdatedKind::Update {
            latest: false,
            interactive: false,
            lockfile_only: false,
          },
          recursive: false,
        },
      ),
      (
        svec!["--update", "--latest"],
        OutdatedFlags {
          filters: vec![],
          kind: OutdatedKind::Update {
            latest: true,
            interactive: false,
            lockfile_only: false,
          },
          recursive: false,
        },
      ),
      (
        svec!["--update", "--recursive"],
        OutdatedFlags {
          filters: vec![],
          kind: OutdatedKind::Update {
            latest: false,
            interactive: false,
            lockfile_only: false,
          },
          recursive: true,
        },
      ),
      (
        svec!["--update", "--lockfile-only"],
        OutdatedFlags {
          filters: vec![],
          kind: OutdatedKind::Update {
            latest: false,
            interactive: false,
            lockfile_only: true,
          },
          recursive: false,
        },
      ),
      (
        svec!["--update", "@foo/bar"],
        OutdatedFlags {
          filters: svec!["@foo/bar"],
          kind: OutdatedKind::Update {
            latest: false,
            interactive: false,
            lockfile_only: false,
          },
          recursive: false,
        },
      ),
      (
        svec!["--latest"],
        OutdatedFlags {
          filters: svec![],
          kind: OutdatedKind::PrintOutdated { compatible: false },
          recursive: false,
        },
      ),
      (
        svec!["--update", "--latest", "--interactive"],
        OutdatedFlags {
          filters: svec![],
          kind: OutdatedKind::Update {
            latest: true,
            interactive: true,
            lockfile_only: false,
          },
          recursive: false,
        },
      ),
    ];
    for (input, expected) in cases {
      let mut args = svec!["deno", "outdated"];
      args.extend(input);
      let r = flags_from_vec(args.clone()).unwrap();
      assert_eq!(
        r.subcommand,
        DenoSubcommand::Outdated(expected),
        "incorrect result for args: {:?}",
        args
      );
    }
  }

  #[test]
  fn list_subcommand() {
    let cases = [
      (svec![], ListFlags::default()),
      (
        svec!["--recursive"],
        ListFlags {
          recursive: true,
          ..Default::default()
        },
      ),
      (
        svec!["--depth", "3"],
        ListFlags {
          depth: 3,
          ..Default::default()
        },
      ),
      (
        svec!["--prod"],
        ListFlags {
          prod: true,
          ..Default::default()
        },
      ),
      (
        svec!["--dev", "@foo/bar", "react*"],
        ListFlags {
          dev: true,
          filters: svec!["@foo/bar", "react*"],
          ..Default::default()
        },
      ),
    ];
    for (input, expected) in cases {
      let mut args = svec!["deno", "list"];
      args.extend(input);
      let r = flags_from_vec(args.clone()).unwrap();
      assert_eq!(
        r.subcommand,
        DenoSubcommand::List(expected),
        "incorrect result for args: {:?}",
        args
      );
    }

    // --prod and --dev are mutually exclusive
    assert!(flags_from_vec(svec!["deno", "list", "--prod", "--dev"]).is_err());
  }

  #[test]
  fn update_subcommand() {
    let cases = [
      (
        svec![],
        OutdatedFlags {
          filters: vec![],
          kind: OutdatedKind::Update {
            latest: false,
            interactive: false,
            lockfile_only: false,
          },
          recursive: false,
        },
      ),
      (
        svec!["--latest"],
        OutdatedFlags {
          filters: vec![],
          kind: OutdatedKind::Update {
            latest: true,
            interactive: false,
            lockfile_only: false,
          },
          recursive: false,
        },
      ),
      (
        svec!["--recursive"],
        OutdatedFlags {
          filters: vec![],
          kind: OutdatedKind::Update {
            latest: false,
            interactive: false,
            lockfile_only: false,
          },
          recursive: true,
        },
      ),
      (
        svec!["--lockfile-only"],
        OutdatedFlags {
          filters: vec![],
          kind: OutdatedKind::Update {
            latest: false,
            interactive: false,
            lockfile_only: true,
          },
          recursive: false,
        },
      ),
      (
        svec!["@foo/bar"],
        OutdatedFlags {
          filters: svec!["@foo/bar"],
          kind: OutdatedKind::Update {
            latest: false,
            interactive: false,
            lockfile_only: false,
          },
          recursive: false,
        },
      ),
      (
        svec!["--latest", "--interactive"],
        OutdatedFlags {
          filters: svec![],
          kind: OutdatedKind::Update {
            latest: true,
            interactive: true,
            lockfile_only: false,
          },
          recursive: false,
        },
      ),
    ];
    for (input, expected) in cases {
      let mut args = svec!["deno", "update"];
      args.extend(input);
      let r = flags_from_vec(args.clone()).unwrap();
      assert_eq!(
        r.subcommand,
        DenoSubcommand::Outdated(expected),
        "incorrect result for args: {:?}",
        args
      );
    }
  }

  #[test]
  fn update_subcommand_frozen_flag() {
    let r = flags_from_vec(svec!["deno", "update", "--frozen=false"]).unwrap();
    assert_eq!(r.frozen_lockfile, Some(false));

    let r = flags_from_vec(svec!["deno", "update", "--frozen"]).unwrap();
    assert_eq!(r.frozen_lockfile, Some(true));
  }

  #[test]
  fn outdated_subcommand_frozen_flag() {
    let r =
      flags_from_vec(svec!["deno", "outdated", "--frozen=false"]).unwrap();
    assert_eq!(r.frozen_lockfile, Some(false));
  }

  #[test]
  fn approve_scripts_subcommand() {
    let cases = [
      (
        svec![],
        ApproveScriptsFlags {
          packages: vec![],
          lockfile_only: false,
        },
      ),
      (
        svec!["npm:pkg@1.0.0"],
        ApproveScriptsFlags {
          packages: vec!["npm:pkg@1.0.0".to_string()],
          lockfile_only: false,
        },
      ),
      (
        svec!["npm:pkg1@1.0.0", "npm:pkg2@2.0.0"],
        ApproveScriptsFlags {
          packages: vec![
            "npm:pkg1@1.0.0".to_string(),
            "npm:pkg2@2.0.0".to_string(),
          ],
          lockfile_only: false,
        },
      ),
      (
        svec!["npm:pkg1@1.0.0,npm:pkg2@2.0.0"],
        ApproveScriptsFlags {
          packages: vec![
            "npm:pkg1@1.0.0".to_string(),
            "npm:pkg2@2.0.0".to_string(),
          ],
          lockfile_only: false,
        },
      ),
      (
        svec!["--lockfile-only"],
        ApproveScriptsFlags {
          packages: vec![],
          lockfile_only: true,
        },
      ),
      (
        svec!["--lockfile-only", "npm:pkg@1.0.0"],
        ApproveScriptsFlags {
          packages: vec!["npm:pkg@1.0.0".to_string()],
          lockfile_only: true,
        },
      ),
      (
        svec!["npm:pkg@1.0.0", "--lockfile-only"],
        ApproveScriptsFlags {
          packages: vec!["npm:pkg@1.0.0".to_string()],
          lockfile_only: true,
        },
      ),
      (
        svec!["npm:pkg1@1.0.0", "npm:pkg2@2.0.0", "--lockfile-only"],
        ApproveScriptsFlags {
          packages: vec![
            "npm:pkg1@1.0.0".to_string(),
            "npm:pkg2@2.0.0".to_string(),
          ],
          lockfile_only: true,
        },
      ),
    ];
    for (input, expected) in cases {
      let mut args = svec!["deno", "approve-scripts"];
      args.extend(input);
      let r = flags_from_vec(args.clone()).unwrap();
      assert_eq!(
        r.subcommand,
        DenoSubcommand::ApproveScripts(expected),
        "incorrect result for args: {:?}",
        args
      );
    }
  }

  #[test]
  fn clean_subcommand() {
    let cases = [
      (
        svec![],
        CleanFlags {
          except_paths: vec![],
          dry_run: false,
        },
      ),
      (
        svec!["--except", "path1"],
        CleanFlags {
          except_paths: vec!["path1".to_string()],
          dry_run: false,
        },
      ),
      (
        svec!["--except", "path1", "path2"],
        CleanFlags {
          except_paths: vec!["path1".to_string(), "path2".to_string()],
          dry_run: false,
        },
      ),
      (
        svec!["--except", "path1", "--dry-run"],
        CleanFlags {
          except_paths: vec!["path1".to_string()],
          dry_run: true,
        },
      ),
      (
        svec!["--dry-run"],
        CleanFlags {
          except_paths: vec![],
          dry_run: true,
        },
      ),
    ];
    for (input, expected) in cases {
      // `--except` builds a module graph of the retained paths, so it parses
      // with `--cached-only`; other invocations (e.g. `--dry-run` alone) don't.
      let cached_only = input.iter().any(|arg| arg == "--except");
      let mut args = svec!["deno", "clean"];
      args.extend(input);
      let r = flags_from_vec(args.clone())
        .inspect_err(|e| {
          #[allow(clippy::print_stderr, reason = "actually want to output")]
          {
            eprintln!("error: {:?} on input: {:?}", e, args);
          }
        })
        .unwrap();
      assert_eq!(
        r,
        Flags {
          subcommand: DenoSubcommand::Clean(expected),
          cached_only,
          ..Flags::default()
        },
        "incorrect result for args: {:?}",
        args
      );
    }
  }

  #[test]
  fn conditions_test() {
    let flags = flags_from_vec(svec![
      "deno",
      "run",
      "--conditions",
      "development",
      "main.ts"
    ])
    .unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "main.ts".into(),
          ..Default::default()
        }),
        node_conditions: svec!["development"],
        code_cache_enabled: true,
        ..Default::default()
      }
    );

    let flags = flags_from_vec(svec![
      "deno",
      "run",
      "--conditions",
      "development,production",
      "main.ts"
    ])
    .unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "main.ts".into(),
          ..Default::default()
        }),
        node_conditions: svec!["development", "production"],
        code_cache_enabled: true,
        ..Default::default()
      }
    );

    let flags = flags_from_vec(svec![
      "deno",
      "run",
      "--conditions",
      "development",
      "--conditions",
      "production",
      "main.ts"
    ])
    .unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "main.ts".into(),
          ..Default::default()
        }),
        node_conditions: svec!["development", "production"],
        code_cache_enabled: true,
        ..Default::default()
      }
    );
  }

  #[test]
  fn preload_flag_test() {
    let flags = flags_from_vec(svec![
      "deno",
      "run",
      "--preload",
      "preload.js",
      "main.ts"
    ])
    .unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "main.ts".into(),
          ..Default::default()
        }),
        preload: svec!["preload.js"],
        code_cache_enabled: true,
        ..Default::default()
      }
    );

    let flags =
      flags_from_vec(svec!["deno", "run", "--preload", "data:,()", "main.ts"])
        .unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "main.ts".into(),
          ..Default::default()
        }),
        preload: svec!["data:,()"],
        code_cache_enabled: true,
        ..Default::default()
      }
    );

    let flags = flags_from_vec(svec![
      "deno",
      "compile",
      "--preload",
      "p1.js",
      "--preload",
      "./p2.js",
      "main.ts"
    ])
    .unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Compile(CompileFlags {
          source_file: "main.ts".into(),
          output: None,
          args: vec![],
          target: None,
          no_terminal: false,
          icon: None,
          include: Default::default(),
          exclude: Default::default(),
          eszip: false,
          self_extracting: false,
          bundle: false,
          app_name: None,
          minify: false,
          exclude_unused_npm: false,
          engine: Default::default(),
        }),
        type_check_mode: TypeCheckMode::Local,
        preload: svec!["p1.js", "./p2.js"],
        code_cache_enabled: true,
        ..Default::default()
      }
    );

    let flags = flags_from_vec(svec![
      "deno",
      "test",
      "--preload",
      "p1.js",
      "--import",
      "./p2.js",
    ])
    .unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Test(TestFlags::default()),
        preload: svec!["p1.js", "./p2.js"],
        type_check_mode: TypeCheckMode::Local,
        code_cache_enabled: false,
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        ..Default::default()
      }
    );

    let flags = flags_from_vec(svec![
      "deno",
      "bench",
      "--preload",
      "p1.js",
      "--import",
      "./p2.js",
    ])
    .unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Bench(BenchFlags::default()),
        preload: svec!["p1.js", "./p2.js"],
        type_check_mode: TypeCheckMode::Local,
        code_cache_enabled: false,
        permissions: PermissionFlags {
          no_prompt: true,
          ..Default::default()
        },
        ..Default::default()
      }
    );
  }

  #[test]
  fn require_flag_test() {
    let flags = flags_from_vec(svec![
      "deno",
      "run",
      "--require",
      "require.js",
      "main.ts"
    ])
    .unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "main.ts".into(),
          ..Default::default()
        }),
        require: svec!["require.js"],
        code_cache_enabled: true,
        ..Default::default()
      }
    );

    let flags = flags_from_vec(svec![
      "deno",
      "run",
      "--require",
      "r1.js",
      "--require",
      "./r2.js",
      "main.ts"
    ])
    .unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "main.ts".into(),
          ..Default::default()
        }),
        require: svec!["r1.js", "./r2.js"],
        code_cache_enabled: true,
        ..Default::default()
      }
    );
  }

  #[test]
  fn check_with_v8_flags() {
    let flags =
      flags_from_vec(svec!["deno", "check", "--v8-flags=--help", "script.ts",])
        .unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Check(CheckFlags {
          files: svec!["script.ts"],
          doc: false,
          doc_only: false,
          check_js: false,
        }),
        type_check_mode: TypeCheckMode::Local,
        code_cache_enabled: true,
        v8_flags: svec!["--help"],
        ..Flags::default()
      }
    );
  }

  #[test]
  fn multiple_allow_all() {
    let flags = flags_from_vec(svec![
      "deno",
      "run",
      "--allow-all",
      "--inspect",
      "-A",
      "script.ts",
    ])
    .unwrap();
    assert_eq!(
      flags,
      Flags {
        subcommand: DenoSubcommand::Run(RunFlags {
          script: "script.ts".to_string(),
          ..Default::default()
        }),
        inspect: Some("127.0.0.1:9229".parse().unwrap()),
        code_cache_enabled: true,
        permissions: PermissionFlags {
          allow_all: true,
          ..Default::default()
        },
        ..Flags::default()
      }
    );
  }

  #[test]
  fn inspect_flag_parsing() {
    use std::net::IpAddr;
    use std::net::Ipv4Addr;

    let cases = vec![
      (
        "127.0.0.1:9229",
        SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 9229),
      ),
      (
        "192.168.0.1",
        SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)), 9229),
      ),
      (
        "10000",
        SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 10000),
      ),
      (
        ":10000",
        SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 10000),
      ),
      (
        ":0",
        SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0),
      ),
      (
        "0",
        SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0),
      ),
    ];

    for case in cases {
      let flags = flags_from_vec(svec![
        "deno",
        "run",
        &format!("--inspect={}", case.0),
        "script.ts",
      ])
      .unwrap();
      assert_eq!(
        flags,
        Flags {
          subcommand: DenoSubcommand::Run(RunFlags {
            script: "script.ts".to_string(),
            ..Default::default()
          }),
          inspect: Some(case.1),
          code_cache_enabled: true,
          ..Flags::default()
        }
      );
    }
  }

  #[test]
  fn inspect_value_parser_resolves_hostnames() {
    // Node accepts `--inspect=localhost:0` (and similar hostname forms);
    // the parser should resolve them via DNS rather than rejecting.
    let cases = [
      ("localhost:0", 0),
      ("localhost:1234", 1234),
      ("localhost", 9229),
    ];
    for (input, expected_port) in cases {
      let addr = inspect_value_parser(input)
        .unwrap_or_else(|e| panic!("failed to parse {input:?}: {e}"));
      assert_eq!(addr.port(), expected_port, "port for {input:?}");
      assert!(
        addr.ip().is_loopback(),
        "expected loopback for {input:?}, got {}",
        addr.ip()
      );
    }
  }

  #[test]
  fn inspect_publish_uid_flag_parsing() {
    // Test with both stderr and http
    let flags = flags_from_vec(svec![
      "deno",
      "run",
      "--inspect",
      "--inspect-publish-uid=stderr,http",
      "script.ts",
    ])
    .unwrap();
    assert_eq!(
      flags.inspect_publish_uid,
      Some(InspectPublishUid {
        console: true,
        http: true,
      })
    );

    // Test with only stderr
    let flags = flags_from_vec(svec![
      "deno",
      "run",
      "--inspect",
      "--inspect-publish-uid=stderr",
      "script.ts",
    ])
    .unwrap();
    assert_eq!(
      flags.inspect_publish_uid,
      Some(InspectPublishUid {
        console: true,
        http: false,
      })
    );

    // Test with only http
    let flags = flags_from_vec(svec![
      "deno",
      "run",
      "--inspect",
      "--inspect-publish-uid=http",
      "script.ts",
    ])
    .unwrap();
    assert_eq!(
      flags.inspect_publish_uid,
      Some(InspectPublishUid {
        console: false,
        http: true,
      })
    );

    // Test without the flag (should be None)
    let flags =
      flags_from_vec(svec!["deno", "run", "--inspect", "script.ts",]).unwrap();
    assert_eq!(flags.inspect_publish_uid, None);
  }

  fn set_test_node_options(value: Option<&str>) {
    TEST_NODE_OPTIONS.with(|opt| {
      *opt.borrow_mut() = value.map(|s| s.to_string());
    });
  }

  #[test]
  fn node_options_require() {
    // Test NODE_OPTIONS --require when no CLI --require is passed
    set_test_node_options(Some("--require only.js"));
    let flags = flags_from_vec(svec!["deno", "run", "script.ts",]).unwrap();
    set_test_node_options(None);
    assert_eq!(flags.require, vec!["only.js"]);
  }

  #[test]
  fn node_options_require_prepend_to_cli() {
    // Test NODE_OPTIONS --require is prepended to CLI --require values
    set_test_node_options(Some("--require foo.js --require bar.js"));
    let flags =
      flags_from_vec(svec!["deno", "run", "--require", "cli.js", "script.ts",])
        .unwrap();
    set_test_node_options(None);
    assert_eq!(flags.require, vec!["foo.js", "bar.js", "cli.js"]);
  }

  #[test]
  fn node_options_inspect_publish_uid() {
    set_test_node_options(Some("--inspect-publish-uid=http"));
    let flags = flags_from_vec(svec!["deno", "run", "script.ts",]).unwrap();
    set_test_node_options(None);
    assert_eq!(
      flags.inspect_publish_uid,
      Some(InspectPublishUid {
        console: false,
        http: true,
      })
    );
  }

  #[test]
  fn node_options_inspect_publish_uid_cli_precedence() {
    set_test_node_options(Some("--inspect-publish-uid=http"));
    let flags = flags_from_vec(svec![
      "deno",
      "run",
      "--inspect-publish-uid=stderr",
      "script.ts",
    ])
    .unwrap();
    set_test_node_options(None);
    assert_eq!(
      flags.inspect_publish_uid,
      Some(InspectPublishUid {
        console: true,
        http: false,
      })
    );
  }

  #[test]
  fn node_options_inspect() {
    set_test_node_options(Some("--inspect=127.0.0.1:9333"));
    let flags = flags_from_vec(svec!["deno", "run", "script.ts",]).unwrap();
    set_test_node_options(None);
    assert_eq!(flags.inspect, Some("127.0.0.1:9333".parse().unwrap()));
  }

  #[test]
  fn node_options_inspect_default_address() {
    set_test_node_options(Some("--inspect"));
    let flags = flags_from_vec(svec!["deno", "run", "script.ts",]).unwrap();
    set_test_node_options(None);
    assert_eq!(flags.inspect, Some("127.0.0.1:9229".parse().unwrap()));
  }

  #[test]
  fn node_options_inspect_brk_and_wait() {
    set_test_node_options(Some("--inspect-brk=127.0.0.1:9334"));
    let flags = flags_from_vec(svec!["deno", "run", "script.ts",]).unwrap();
    set_test_node_options(None);
    assert_eq!(flags.inspect_brk, Some("127.0.0.1:9334".parse().unwrap()));

    set_test_node_options(Some("--inspect-wait=127.0.0.1:9335"));
    let flags = flags_from_vec(svec!["deno", "run", "script.ts",]).unwrap();
    set_test_node_options(None);
    assert_eq!(flags.inspect_wait, Some("127.0.0.1:9335".parse().unwrap()));
  }

  #[test]
  fn node_options_inspect_cli_precedence() {
    // An explicit CLI inspector flag suppresses the entire NODE_OPTIONS
    // inspector family, since --inspect/-brk/-wait are mutually exclusive.
    set_test_node_options(Some("--inspect=127.0.0.1:9333"));
    let flags = flags_from_vec(svec![
      "deno",
      "run",
      "--inspect-brk=127.0.0.1:9444",
      "script.ts",
    ])
    .unwrap();
    set_test_node_options(None);
    assert_eq!(flags.inspect, None);
    assert_eq!(flags.inspect_brk, Some("127.0.0.1:9444".parse().unwrap()));
  }

  #[test]
  fn node_options_combined() {
    // Test NODE_OPTIONS with both --require and --inspect-publish-uid
    set_test_node_options(Some(
      "--require foo.js --inspect-publish-uid=stderr,http",
    ));
    let flags = flags_from_vec(svec!["deno", "run", "script.ts",]).unwrap();
    set_test_node_options(None);
    assert_eq!(flags.require, vec!["foo.js"]);
    assert_eq!(
      flags.inspect_publish_uid,
      Some(InspectPublishUid {
        console: true,
        http: true,
      })
    );
  }

  #[test]
  fn node_options_empty() {
    set_test_node_options(Some(""));
    let flags = flags_from_vec(svec!["deno", "run", "script.ts",]).unwrap();
    set_test_node_options(None);
    assert!(flags.require.is_empty());
    assert_eq!(flags.inspect_publish_uid, None);
  }

  #[test]
  fn node_options_ignores_unknown_flags() {
    set_test_node_options(Some(
      "--require known.js --unknown-flag --another-unknown",
    ));
    let flags = flags_from_vec(svec!["deno", "run", "script.ts",]).unwrap();
    set_test_node_options(None);
    assert_eq!(flags.require, vec!["known.js"]);
  }

  #[test]
  fn bump_version_patch() {
    let r = flags_from_vec(svec!["deno", "bump-version", "patch"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::BumpVersion(VersionFlags {
          increment: Some(VersionIncrement::Patch),
          ..Default::default()
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bump_version_minor() {
    let r = flags_from_vec(svec!["deno", "bump-version", "minor"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::BumpVersion(VersionFlags {
          increment: Some(VersionIncrement::Minor),
          ..Default::default()
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bump_version_major() {
    let r = flags_from_vec(svec!["deno", "bump-version", "major"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::BumpVersion(VersionFlags {
          increment: Some(VersionIncrement::Major),
          ..Default::default()
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bump_version_prerelease() {
    let r = flags_from_vec(svec!["deno", "bump-version", "prerelease"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::BumpVersion(VersionFlags {
          increment: Some(VersionIncrement::Prerelease),
          ..Default::default()
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bump_version_premajor() {
    let r = flags_from_vec(svec!["deno", "bump-version", "premajor"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::BumpVersion(VersionFlags {
          increment: Some(VersionIncrement::Premajor),
          ..Default::default()
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bump_version_preminor() {
    let r = flags_from_vec(svec!["deno", "bump-version", "preminor"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::BumpVersion(VersionFlags {
          increment: Some(VersionIncrement::Preminor),
          ..Default::default()
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bump_version_prepatch() {
    let r = flags_from_vec(svec!["deno", "bump-version", "prepatch"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::BumpVersion(VersionFlags {
          increment: Some(VersionIncrement::Prepatch),
          ..Default::default()
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bump_version_no_args() {
    let r = flags_from_vec(svec!["deno", "bump-version"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::BumpVersion(VersionFlags::default()),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bump_version_invalid_increment() {
    let r = flags_from_vec(svec!["deno", "bump-version", "invalid"]);
    assert!(r.is_err());
  }

  #[test]
  fn bump_version_workspace_flags() {
    let r = flags_from_vec(svec![
      "deno",
      "bump-version",
      "--workspace",
      "--dry-run",
      "--start",
      "v1.0.0",
      "--base",
      "main",
      "--import-map",
      "import_map.json",
      "--release-notes",
      "Releases.md",
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::BumpVersion(VersionFlags {
          increment: None,
          workspace: Some(true),
          dry_run: true,
          start: Some("v1.0.0".to_string()),
          base: Some("main".to_string()),
          import_map: Some("import_map.json".to_string()),
          release_notes: Some("Releases.md".to_string()),
          config: None,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bump_version_config() {
    let r = flags_from_vec(svec![
      "deno",
      "bump-version",
      "patch",
      "--config",
      "package.json",
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::BumpVersion(VersionFlags {
          increment: Some(VersionIncrement::Patch),
          config: Some("package.json".to_string()),
          ..Default::default()
        }),
        ..Flags::default()
      }
    );

    let r = flags_from_vec(svec![
      "deno",
      "bump-version",
      "-c",
      "package.json",
      "patch",
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::BumpVersion(VersionFlags {
          increment: Some(VersionIncrement::Patch),
          config: Some("package.json".to_string()),
          ..Default::default()
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn bump_version_no_workspace() {
    let r =
      flags_from_vec(svec!["deno", "bump-version", "patch", "--no-workspace"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::BumpVersion(VersionFlags {
          increment: Some(VersionIncrement::Patch),
          workspace: Some(false),
          ..Default::default()
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn why_package() {
    let r = flags_from_vec(svec!["deno", "why", "express"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Why(WhyFlags {
          package: "express".to_string(),
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn why_package_with_version() {
    let r = flags_from_vec(svec!["deno", "why", "express@4.18.2"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Why(WhyFlags {
          package: "express@4.18.2".to_string(),
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn why_scoped_package() {
    let r = flags_from_vec(svec!["deno", "why", "@scope/pkg"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Why(WhyFlags {
          package: "@scope/pkg".to_string(),
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn why_scoped_package_with_version() {
    let r = flags_from_vec(svec!["deno", "why", "@scope/pkg@1.0.0"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Why(WhyFlags {
          package: "@scope/pkg@1.0.0".to_string(),
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn why_missing_package() {
    let r = flags_from_vec(svec!["deno", "why"]);
    assert!(r.is_err());
  }

  #[test]
  fn audit_basic() {
    let r = flags_from_vec(svec!["deno", "audit"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Audit(AuditFlags {
          severity: "low".to_string(),
          dev: true,
          prod: true,
          optional: true,
          ..Default::default()
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn audit_fix_flag() {
    let r = flags_from_vec(svec!["deno", "audit", "--fix"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Audit(AuditFlags {
          severity: "low".to_string(),
          dev: true,
          prod: true,
          optional: true,
          fix: true,
          ..Default::default()
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn audit_fix_positional() {
    let r = flags_from_vec(svec!["deno", "audit", "fix"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Audit(AuditFlags {
          severity: "low".to_string(),
          dev: true,
          prod: true,
          optional: true,
          fix: true,
          ..Default::default()
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn audit_invalid_positional() {
    let r = flags_from_vec(svec!["deno", "audit", "bogus"]);
    assert!(r.is_err());
  }

  #[test]
  fn transpile_single_file() {
    let r = flags_from_vec(svec!["deno", "transpile", "main.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Transpile(TranspileFlags {
          files: svec!["main.ts"],
          output: None,
          output_dir: None,
          declaration: false,
          source_map: SourceMapMode::None,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn transpile_multiple_files() {
    let r = flags_from_vec(svec!["deno", "transpile", "main.ts", "helpers.ts"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Transpile(TranspileFlags {
          files: svec!["main.ts", "helpers.ts"],
          output: None,
          output_dir: None,
          declaration: false,
          source_map: SourceMapMode::None,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn transpile_with_output() {
    let r =
      flags_from_vec(svec!["deno", "transpile", "main.ts", "-o", "out.js"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Transpile(TranspileFlags {
          files: svec!["main.ts"],
          output: Some("out.js".to_string()),
          output_dir: None,
          declaration: false,
          source_map: SourceMapMode::None,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn transpile_with_outdir() {
    let r =
      flags_from_vec(svec!["deno", "transpile", "main.ts", "--outdir", "dist"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Transpile(TranspileFlags {
          files: svec!["main.ts"],
          output: None,
          output_dir: Some("dist".to_string()),
          declaration: false,
          source_map: SourceMapMode::None,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn transpile_with_source_map_inline() {
    let r = flags_from_vec(svec![
      "deno",
      "transpile",
      "main.ts",
      "--source-map",
      "inline"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Transpile(TranspileFlags {
          files: svec!["main.ts"],
          output: None,
          output_dir: None,
          declaration: false,
          source_map: SourceMapMode::Inline,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn transpile_with_source_map_separate() {
    let r = flags_from_vec(svec![
      "deno",
      "transpile",
      "main.ts",
      "--source-map",
      "separate"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Transpile(TranspileFlags {
          files: svec!["main.ts"],
          output: None,
          output_dir: None,
          declaration: false,
          source_map: SourceMapMode::Separate,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn transpile_with_declaration() {
    let r =
      flags_from_vec(svec!["deno", "transpile", "main.ts", "--declaration"]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Transpile(TranspileFlags {
          files: svec!["main.ts"],
          output: None,
          output_dir: None,
          declaration: true,
          source_map: SourceMapMode::None,
        }),
        ..Flags::default()
      }
    );
  }

  #[test]
  fn transpile_all_flags() {
    let r = flags_from_vec(svec![
      "deno",
      "transpile",
      "main.ts",
      "-o",
      "out.js",
      "--source-map",
      "separate",
      "--declaration"
    ]);
    assert_eq!(
      r.unwrap(),
      Flags {
        subcommand: DenoSubcommand::Transpile(TranspileFlags {
          files: svec!["main.ts"],
          output: Some("out.js".to_string()),
          output_dir: None,
          declaration: true,
          source_map: SourceMapMode::Separate,
        }),
        ..Flags::default()
      }
    );
  }
}
