feat(cli): --ext parameter for run, compile, and bundle (#17172)

Adds `--ext` to `deno run`, closes #5088

Additionally

- Adds `--ext` to `deno compile` and `deno bundle`
This commit is contained in:
Cre3per 2023-03-22 15:15:53 +01:00 committed by GitHub
parent 50b793c9ed
commit fd0658fb42
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 390 additions and 128 deletions

View file

@ -124,13 +124,11 @@ pub struct DocFlags {
pub struct EvalFlags {
pub print: bool,
pub code: String,
pub ext: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FmtFlags {
pub check: bool,
pub ext: String,
pub files: FileFlags,
pub use_tabs: Option<bool>,
pub line_width: Option<NonZeroU32>,
@ -335,6 +333,7 @@ pub struct Flags {
pub node_modules_dir: Option<bool>,
pub coverage_dir: Option<String>,
pub enable_testing_features: bool,
pub ext: Option<String>,
pub ignore: Vec<PathBuf>,
pub import_map_path: Option<String>,
pub inspect_brk: Option<SocketAddr>,
@ -837,6 +836,7 @@ fn bundle_subcommand<'a>() -> Command<'a> {
)
.arg(watch_arg(false))
.arg(no_clear_screen_arg())
.arg(executable_ext_arg())
.about("Bundle module and dependencies into single file")
.long_about(
"Output a single JavaScript file with all dependencies.
@ -943,6 +943,7 @@ fn compile_subcommand<'a>() -> Command<'a> {
"aarch64-apple-darwin",
]),
)
.arg(executable_ext_arg())
.about("UNSTABLE: Compile the script into a self contained executable")
.long_about(
"UNSTABLE: Compiles the given script into a self contained executable.
@ -1164,22 +1165,16 @@ This command has implicit access to all permissions (--allow-all).",
.arg(
// TODO(@satyarohith): remove this argument in 2.0.
Arg::new("ts")
.conflicts_with("ext")
.long("ts")
.short('T')
.help("Treat eval input as TypeScript")
.help("deprecated: Treat eval input as TypeScript")
.takes_value(false)
.multiple_occurrences(false)
.multiple_values(false)
.hide(true),
)
.arg(
Arg::new("ext")
.long("ext")
.help("Set standard input (stdin) content type")
.takes_value(true)
.default_value("js")
.possible_values(["ts", "tsx", "js", "jsx"]),
)
.arg(executable_ext_arg())
.arg(
Arg::new("print")
.long("print")
@ -1232,8 +1227,9 @@ Ignore formatting a file by adding an ignore comment at the top of the file:
.arg(
Arg::new("ext")
.long("ext")
.help("Set standard input (stdin) content type")
.help("Set content type of the supplied file")
.takes_value(true)
// prefer using ts for formatting instead of js because ts works in more scenarios
.default_value("ts")
.possible_values(["ts", "tsx", "js", "jsx", "md", "json", "jsonc"]),
)
@ -1615,6 +1611,7 @@ fn run_subcommand<'a>() -> Command<'a> {
.conflicts_with("inspect-brk"),
)
.arg(no_clear_screen_arg())
.arg(executable_ext_arg())
.trailing_var_arg(true)
.arg(script_arg().required(true))
.about("Run a JavaScript or TypeScript program")
@ -2168,6 +2165,18 @@ fn cached_only_arg<'a>() -> Arg<'a> {
.help("Require that remote dependencies are already cached")
}
/// 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<'a>() -> Arg<'a> {
Arg::new("ext")
.long("ext")
.help("Set content type of the supplied file")
.takes_value(true)
.possible_values(["ts", "tsx", "js", "jsx"])
}
fn location_arg<'a>() -> Arg<'a> {
Arg::new("location")
.long("location")
@ -2456,6 +2465,7 @@ fn bundle_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
};
watch_arg_parse(flags, matches, false);
ext_arg_parse(flags, matches);
flags.subcommand = DenoSubcommand::Bundle(BundleFlags {
source_file,
@ -2505,6 +2515,7 @@ fn compile_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
Some(f) => f.map(String::from).collect(),
None => vec![],
};
ext_arg_parse(flags, matches);
flags.subcommand = DenoSubcommand::Compile(CompileFlags {
source_file,
@ -2614,13 +2625,22 @@ fn eval_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
flags.allow_write = Some(vec![]);
flags.allow_ffi = Some(vec![]);
flags.allow_hrtime = true;
ext_arg_parse(flags, matches);
// TODO(@satyarohith): remove this flag in 2.0.
let as_typescript = matches.is_present("ts");
let ext = if as_typescript {
"ts".to_string()
} else {
matches.value_of("ext").unwrap().to_string()
};
if as_typescript {
eprintln!(
"{}",
crate::colors::yellow(
"Warning: --ts/-T flag is deprecated. Use --ext=ts instead."
),
);
flags.ext = Some("ts".to_string());
}
let print = matches.is_present("print");
let mut code: Vec<String> = matches
@ -2634,12 +2654,13 @@ fn eval_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
for v in code_args {
flags.argv.push(v);
}
flags.subcommand = DenoSubcommand::Eval(EvalFlags { print, code, ext });
flags.subcommand = DenoSubcommand::Eval(EvalFlags { print, code });
}
fn fmt_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
config_args_parse(flags, matches);
watch_arg_parse(flags, matches, false);
ext_arg_parse(flags, matches);
let include = match matches.values_of("files") {
Some(f) => f.map(PathBuf::from).collect(),
@ -2649,7 +2670,6 @@ fn fmt_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
Some(f) => f.map(PathBuf::from).collect(),
None => vec![],
};
let ext = matches.value_of("ext").unwrap().to_string();
let use_tabs = optional_bool_parse(matches, "use-tabs");
let line_width = if matches.is_present("line-width") {
@ -2674,7 +2694,6 @@ fn fmt_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
flags.subcommand = DenoSubcommand::Fmt(FmtFlags {
check: matches.is_present("check"),
ext,
files: FileFlags { include, ignore },
use_tabs,
line_width,
@ -2827,6 +2846,8 @@ fn run_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
flags.argv.push(v);
}
ext_arg_parse(flags, matches);
watch_arg_parse(flags, matches, true);
flags.subcommand = DenoSubcommand::Run(RunFlags { script });
}
@ -3228,6 +3249,10 @@ fn cached_only_arg_parse(flags: &mut Flags, matches: &ArgMatches) {
}
}
fn ext_arg_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
flags.ext = matches.value_of("ext").map(String::from);
}
fn location_arg_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
flags.location = matches
.value_of("location")
@ -3694,7 +3719,6 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Fmt(FmtFlags {
check: false,
ext: "ts".to_string(),
files: FileFlags {
include: vec![
PathBuf::from("script_1.ts"),
@ -3709,6 +3733,7 @@ mod tests {
prose_wrap: None,
no_semicolons: None,
}),
ext: Some("ts".to_string()),
..Flags::default()
}
);
@ -3719,7 +3744,6 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Fmt(FmtFlags {
check: true,
ext: "ts".to_string(),
files: FileFlags {
include: vec![],
ignore: vec![],
@ -3731,6 +3755,7 @@ mod tests {
prose_wrap: None,
no_semicolons: None,
}),
ext: Some("ts".to_string()),
..Flags::default()
}
);
@ -3741,7 +3766,6 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Fmt(FmtFlags {
check: false,
ext: "ts".to_string(),
files: FileFlags {
include: vec![],
ignore: vec![],
@ -3753,6 +3777,7 @@ mod tests {
prose_wrap: None,
no_semicolons: None,
}),
ext: Some("ts".to_string()),
..Flags::default()
}
);
@ -3763,7 +3788,6 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Fmt(FmtFlags {
check: false,
ext: "ts".to_string(),
files: FileFlags {
include: vec![],
ignore: vec![],
@ -3775,6 +3799,7 @@ mod tests {
prose_wrap: None,
no_semicolons: None,
}),
ext: Some("ts".to_string()),
watch: Some(vec![]),
..Flags::default()
}
@ -3787,7 +3812,6 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Fmt(FmtFlags {
check: false,
ext: "ts".to_string(),
files: FileFlags {
include: vec![],
ignore: vec![],
@ -3799,6 +3823,7 @@ mod tests {
prose_wrap: None,
no_semicolons: None,
}),
ext: Some("ts".to_string()),
watch: Some(vec![]),
no_clear_screen: true,
..Flags::default()
@ -3818,7 +3843,6 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Fmt(FmtFlags {
check: true,
ext: "ts".to_string(),
files: FileFlags {
include: vec![PathBuf::from("foo.ts")],
ignore: vec![PathBuf::from("bar.js")],
@ -3830,6 +3854,7 @@ mod tests {
prose_wrap: None,
no_semicolons: None,
}),
ext: Some("ts".to_string()),
watch: Some(vec![]),
..Flags::default()
}
@ -3841,7 +3866,6 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Fmt(FmtFlags {
check: false,
ext: "ts".to_string(),
files: FileFlags {
include: vec![],
ignore: vec![],
@ -3853,6 +3877,7 @@ mod tests {
prose_wrap: None,
no_semicolons: None,
}),
ext: Some("ts".to_string()),
config_flag: ConfigFlag::Path("deno.jsonc".to_string()),
..Flags::default()
}
@ -3871,7 +3896,6 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Fmt(FmtFlags {
check: false,
ext: "ts".to_string(),
files: FileFlags {
include: vec![PathBuf::from("foo.ts")],
ignore: vec![],
@ -3884,6 +3908,7 @@ mod tests {
no_semicolons: None,
}),
config_flag: ConfigFlag::Path("deno.jsonc".to_string()),
ext: Some("ts".to_string()),
watch: Some(vec![]),
..Flags::default()
}
@ -3907,7 +3932,6 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Fmt(FmtFlags {
check: false,
ext: "ts".to_string(),
files: FileFlags {
include: vec![],
ignore: vec![],
@ -3919,6 +3943,7 @@ mod tests {
prose_wrap: Some("never".to_string()),
no_semicolons: Some(true),
}),
ext: Some("ts".to_string()),
..Flags::default()
}
);
@ -3936,7 +3961,6 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Fmt(FmtFlags {
check: false,
ext: "ts".to_string(),
files: FileFlags {
include: vec![],
ignore: vec![],
@ -3948,6 +3972,7 @@ mod tests {
prose_wrap: None,
no_semicolons: Some(false),
}),
ext: Some("ts".to_string()),
..Flags::default()
}
);
@ -4362,7 +4387,6 @@ mod tests {
subcommand: DenoSubcommand::Eval(EvalFlags {
print: false,
code: "'console.log(\"hello\")'".to_string(),
ext: "js".to_string(),
}),
allow_net: Some(vec![]),
allow_env: Some(vec![]),
@ -4386,7 +4410,6 @@ mod tests {
subcommand: DenoSubcommand::Eval(EvalFlags {
print: true,
code: "1+2".to_string(),
ext: "js".to_string(),
}),
allow_net: Some(vec![]),
allow_env: Some(vec![]),
@ -4411,7 +4434,6 @@ mod tests {
subcommand: DenoSubcommand::Eval(EvalFlags {
print: false,
code: "'console.log(\"hello\")'".to_string(),
ext: "ts".to_string(),
}),
allow_net: Some(vec![]),
allow_env: Some(vec![]),
@ -4421,6 +4443,7 @@ mod tests {
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
ext: Some("ts".to_string()),
..Flags::default()
}
);
@ -4436,7 +4459,6 @@ mod tests {
subcommand: DenoSubcommand::Eval(EvalFlags {
print: false,
code: "42".to_string(),
ext: "js".to_string(),
}),
import_map_path: Some("import_map.json".to_string()),
no_remote: true,
@ -4479,7 +4501,6 @@ mod tests {
subcommand: DenoSubcommand::Eval(EvalFlags {
print: false,
code: "console.log(Deno.args)".to_string(),
ext: "js".to_string(),
}),
argv: svec!["arg1", "arg2"],
allow_net: Some(vec![]),

View file

@ -10,6 +10,8 @@ pub mod package_json;
pub use self::import_map::resolve_import_map_from_specifier;
use self::package_json::PackageJsonDeps;
use ::import_map::ImportMap;
use deno_core::resolve_url_or_path;
use deno_graph::npm::NpmPackageReqReference;
use indexmap::IndexMap;
use crate::npm::NpmRegistryApi;
@ -50,6 +52,7 @@ use deno_runtime::deno_tls::webpki_roots;
use deno_runtime::inspector_server::InspectorServer;
use deno_runtime::permissions::PermissionsOptions;
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::env;
use std::io::BufReader;
use std::io::Cursor;
@ -139,7 +142,6 @@ impl BenchOptions {
pub struct FmtOptions {
pub is_stdin: bool,
pub check: bool,
pub ext: String,
pub options: FmtOptionsConfig,
pub files: FilesConfig,
}
@ -166,10 +168,6 @@ impl FmtOptions {
Ok(Self {
is_stdin,
check: maybe_fmt_flags.as_ref().map(|f| f.check).unwrap_or(false),
ext: maybe_fmt_flags
.as_ref()
.map(|f| f.ext.to_string())
.unwrap_or_else(|| "ts".to_string()),
options: resolve_fmt_options(
maybe_fmt_flags.as_ref(),
maybe_config_options,
@ -675,6 +673,73 @@ impl CliOptions {
.map(Some)
}
pub fn resolve_main_module(&self) -> Result<ModuleSpecifier, AnyError> {
match &self.flags.subcommand {
DenoSubcommand::Bundle(bundle_flags) => {
resolve_url_or_path(&bundle_flags.source_file, self.initial_cwd())
.map_err(AnyError::from)
}
DenoSubcommand::Compile(compile_flags) => {
resolve_url_or_path(&compile_flags.source_file, self.initial_cwd())
.map_err(AnyError::from)
}
DenoSubcommand::Eval(_) => {
resolve_url_or_path("./$deno$eval", self.initial_cwd())
.map_err(AnyError::from)
}
DenoSubcommand::Repl(_) => {
resolve_url_or_path("./$deno$repl.ts", self.initial_cwd())
.map_err(AnyError::from)
}
DenoSubcommand::Run(run_flags) => {
if run_flags.is_stdin() {
std::env::current_dir()
.context("Unable to get CWD")
.and_then(|cwd| {
resolve_url_or_path("./$deno$stdin", &cwd).map_err(AnyError::from)
})
} else if self.flags.watch.is_some() {
resolve_url_or_path(&run_flags.script, self.initial_cwd())
.map_err(AnyError::from)
} else if NpmPackageReqReference::from_str(&run_flags.script).is_ok() {
ModuleSpecifier::parse(&run_flags.script).map_err(AnyError::from)
} else {
resolve_url_or_path(&run_flags.script, self.initial_cwd())
.map_err(AnyError::from)
}
}
_ => {
bail!("No main module.")
}
}
}
pub fn resolve_file_header_overrides(
&self,
) -> HashMap<ModuleSpecifier, HashMap<String, String>> {
let maybe_main_specifier = self.resolve_main_module().ok();
// TODO(Cre3per): This mapping moved to deno_ast with https://github.com/denoland/deno_ast/issues/133 and should be available in deno_ast >= 0.25.0 via `MediaType::from_path(...).as_media_type()`
let maybe_content_type =
self.flags.ext.as_ref().and_then(|el| match el.as_str() {
"ts" => Some("text/typescript"),
"tsx" => Some("text/tsx"),
"js" => Some("text/javascript"),
"jsx" => Some("text/jsx"),
_ => None,
});
if let (Some(main_specifier), Some(content_type)) =
(maybe_main_specifier, maybe_content_type)
{
HashMap::from([(
main_specifier,
HashMap::from([("content-type".to_string(), content_type.to_string())]),
)])
} else {
HashMap::default()
}
}
pub async fn resolve_npm_resolution_snapshot(
&self,
api: &NpmRegistryApi,
@ -936,6 +1001,10 @@ impl CliOptions {
self.flags.enable_testing_features
}
pub fn ext_flag(&self) -> &Option<String> {
&self.flags.ext
}
/// If the --inspect or --inspect-brk flags are used.
pub fn is_inspecting(&self) -> bool {
self.flags.inspect.is_some()