use std::borrow::Cow;
use std::collections::HashSet;
use std::env;
use std::ffi::OsString;
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
use color_print::cstr;
use deno_cli_parser::CliError;
pub use deno_cli_parser::flags::*;
use deno_config::glob::FilePatterns;
use deno_config::glob::PathOrPatternSet;
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_graph::GraphKind;
use deno_lib::version::DENO_VERSION_INFO;
use deno_npm::NpmSystemInfo;
use deno_path_util::normalize_path;
use deno_path_util::resolve_url_or_path;
use deno_path_util::url_to_file_path;
pub use deno_runtime::deno_inspector_server::InspectPublishUid;
use deno_telemetry::OtelConfig;
use deno_telemetry::OtelConsoleConfig;
use deno_telemetry::OtelPropagators;
use crate::util::env::resolve_cwd;
use crate::util::fs::canonicalize_path;
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 join_paths(allowlist: &[String], d: &str) -> String {
allowlist
.iter()
.map(|path| path.to_string())
.collect::<Vec<String>>()
.join(d)
}
pub fn resolve_file_patterns(
files: &FileFlags,
base: &Path,
) -> Result<FilePatterns, AnyError> {
Ok(FilePatterns {
include: if files.include.is_empty() {
None
} else {
Some(PathOrPatternSet::from_include_relative_path_or_patterns(
base,
&files.include,
)?)
},
exclude: PathOrPatternSet::from_exclude_relative_path_or_patterns(
base,
&files.ignore,
)?,
base: base.to_path_buf(),
})
}
pub fn resolve_compile_target(flags: &CompileFlags) -> String {
flags
.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 fn npm_system_info(subcommand: &DenoSubcommand) -> NpmSystemInfo {
match subcommand {
DenoSubcommand::Compile(CompileFlags {
target: Some(target),
..
})
| DenoSubcommand::Desktop(DesktopFlags {
target: Some(target),
..
}) => {
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 fn graph_kind(mode: TypeCheckMode) -> GraphKind {
match mode.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 resolve_watch_exclude_set(&self) -> Result<PathOrPatternSet, AnyError>;
}
impl FlagsExt for Flags {
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(),
}
}
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 {
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| {
if p.is_dir() {
Some(p)
} else {
p.parent().map(|parent| parent.to_path_buf())
}
})
.map(|p| vec![p]),
Task(TaskFlags {
cwd: Some(path), ..
}) => {
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()]),
}
}
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()),
}
}
}
pub fn flags_from_vec(args: Vec<OsString>) -> Result<Flags, CliError> {
flags_from_vec_with_initial_cwd(args, None)
}
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();
unsafe { OsString::from_encoded_bytes_unchecked(stripped) }
} else {
arg
}
}
fn bare_run_fast_path(args: &[OsString]) -> Option<Flags> {
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,
code_cache_enabled: true,
..Default::default()
};
deno_cli_parser::convert::apply_node_options(&mut flags);
Some(flags)
} else {
None
}
}
pub fn flags_from_vec_with_initial_cwd(
args: Vec<OsString>,
initial_cwd: Option<PathBuf>,
) -> Result<Flags, CliError> {
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"));
new_args.extend(args.into_iter().skip(1));
new_args
} else {
args
};
if let Some(mut flags) = bare_run_fast_path(&args) {
flags.initial_cwd = initial_cwd;
return Ok(flags);
}
let string_args: Vec<String> = args
.iter()
.map(|a| a.to_string_lossy().into_owned())
.collect();
match deno_cli_parser::convert::flags_from_vec(string_args) {
Ok(mut flags) => {
flags.initial_cwd = match &flags.subcommand {
DenoSubcommand::Compile(_) | DenoSubcommand::Desktop(_) => {
initial_cwd.map(|cwd| canonicalize_path(&cwd).ok().unwrap_or(cwd))
}
_ => initial_cwd,
};
Ok(flags)
}
Err(e) => Err(e),
}
}
pub fn render_version(long: bool) -> String {
if long {
debug_assert_eq!(DENO_VERSION_INFO.typescript, deno_snapshots::TS_VERSION);
format!(
"deno {} ({}, {}, {})\nv8 {}\ntypescript {}\n",
DENO_VERSION_INFO.deno,
DENO_VERSION_INFO.release_channel.name(),
env!("PROFILE"),
env!("TARGET"),
deno_core::v8::VERSION_STRING,
DENO_VERSION_INFO.typescript,
)
} else {
format!("deno {}\n", DENO_VERSION_INFO.deno)
}
}
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()
.map(|pv| (strsim::jaro(v, pv.as_ref()), pv.as_ref().to_owned()))
.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()
}
pub fn handle_shell_completion(_cwd: &Path) -> Result<(), AnyError> {
let shell = std::env::var("COMPLETE").unwrap_or_default();
let words = shell_completion_words();
deno_cli_parser::completions::try_complete(
&deno_cli_parser::defs::DENO_ROOT,
&words,
&shell,
&dynamic_task_completer,
);
Ok(())
}
pub fn handle_dynamic_shell_completion(shell: &str) -> Result<(), AnyError> {
let completer = std::env::args_os()
.next()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_else(|| "deno".to_string());
let completer = shlex::try_quote(&completer)
.map(|c| c.into_owned())
.unwrap_or(completer);
let script =
deno_cli_parser::completions::generate_dynamic(shell, "deno", &completer);
deno_print::drop_write_stdout(&script);
Ok(())
}
fn shell_completion_words() -> Vec<String> {
let mut words = Vec::new();
let mut seen_sep = false;
for a in std::env::args_os() {
if seen_sep {
words.push(a.to_string_lossy().into_owned());
} else if a == "--" {
seen_sep = true;
}
}
words
}
fn dynamic_task_completer(
active_cmd: &deno_cli_parser::CommandDef,
_current: &str,
) -> Vec<(String, Option<String>)> {
if active_cmd.name != "task" {
return Vec::new();
}
let Some(flags) = task_flags_for_completion() else {
return Vec::new();
};
match crate::tools::task::get_available_tasks_for_completion(
std::sync::Arc::new(flags),
) {
Ok(tasks) => tasks
.into_iter()
.map(|t| (t.name, t.task.description))
.collect(),
Err(e) => {
log::debug!("Error during available tasks completion: {e}");
Vec::new()
}
}
}
fn task_flags_for_completion() -> Option<Flags> {
let words = shell_completion_words();
let flags = deno_cli_parser::convert::flags_from_vec(words.clone())
.or_else(|_| {
let mut w = words;
w.pop();
deno_cli_parser::convert::flags_from_vec(w)
})
.ok()?;
matches!(flags.subcommand, DenoSubcommand::Task(_)).then_some(flags)
}
#[cfg(test)]
mod tests {
use deno_cli_parser::defs::DENO_ROOT;
use pretty_assertions::assert_eq;
use super::*;
macro_rules! svec {
($($x:expr),* $(,)?) => (vec![$($x.to_string().into()),*]);
}
fn flags_from_vec(args: Vec<OsString>) -> Result<Flags, CliError> {
flags_from_vec_with_initial_cwd(args, None)
}
#[test]
fn fast_path_matches_full_parse() {
let fires: &[&[&str]] = &[
&["deno", "run", "script.ts"],
&["deno", "run", "./path/to/mod.ts"],
&["deno", "run", "script.ts", "a", "b"],
&["deno", "run", "script.ts", "--", "--foo", "-x"],
&["deno", "run", "https://example.com/mod.ts", "arg"],
];
for case in fires {
let os_args: Vec<OsString> = case.iter().map(OsString::from).collect();
let fast = bare_run_fast_path(&os_args).unwrap_or_else(|| {
panic!("fast path should fire for {case:?}");
});
let string_args: Vec<String> =
case.iter().map(|s| s.to_string()).collect();
let full = deno_cli_parser::convert::flags_from_vec(string_args)
.unwrap_or_else(|e| panic!("full parse failed for {case:?}: {e:?}"));
assert_eq!(
fast, full,
"fast path diverged from full parse for {case:?}"
);
}
let declines: &[&[&str]] = &[
&["deno", "run"], &["deno", "run", "-"], &["deno", "run", "-A", "script.ts"], &["deno", "run", "--watch", "s.ts"], &["deno", "test", "script.ts"], ];
for case in declines {
let os_args: Vec<OsString> = case.iter().map(OsString::from).collect();
assert!(
bare_run_fast_path(&os_args).is_none(),
"fast path should decline for {case:?}"
);
}
}
#[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!(
npm_system_info(&flags.subcommand),
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 = npm_system_info(&flags.subcommand);
assert_eq!(sys_info.os.as_str(), "win32");
}
#[test]
fn minimum_dependency_age_pm_subcommands() {
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 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")
])
);
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()]));
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 equal_help_output() {
for command in DENO_ROOT.subcommands {
if command.name == "help" || command.passthrough {
continue;
}
let render =
|args: Vec<OsString>| match flags_from_vec(args).unwrap().subcommand {
DenoSubcommand::Help(help) => help.help.to_string(),
_ => unreachable!("{} did not render help", command.name),
};
let long_flag = render(svec!["deno", command.name, "--help"]);
let short_flag = render(svec!["deno", command.name, "-h"]);
let subcommand = render(svec!["deno", "help", command.name]);
assert_eq!(long_flag, short_flag, "{} subcommand", command.name);
assert_eq!(long_flag, subcommand, "{} subcommand", command.name);
}
}
#[test]
fn subcommands_recognized_by_node_shim() {
for command in DENO_ROOT.subcommands {
let name = command.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.aliases {
assert!(
node_shim::is_deno_subcommand(alias),
"alias `{alias}` of subcommand `{name}` is missing from node_shim's \
DENO_SUBCOMMANDS list; add it to `libs/node_shim/lib.rs`",
);
}
}
}
#[test]
fn no_colon_in_value_name() {
fn check(cmd: &'static deno_cli_parser::CommandDef) {
for arg in cmd.all_args() {
if let Some(value_name) = arg.value_name {
assert!(
!value_name.contains(':'),
"`{}` arg `{}` has a value_name containing ':' ({value_name}), \
which breaks zsh completions",
cmd.name,
arg.name,
);
}
}
for sub in cmd.subcommands {
check(sub);
}
}
check(&DENO_ROOT);
}
}