1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

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

@ -27,6 +27,7 @@
"cli/tsc/dts/lib.es*.d.ts",
"cli/tsc/dts/typescript.d.ts",
"cli/tests/node_compat/test",
"cli/tests/testdata/file_extensions/ts_with_js_extension.js",
"cli/tests/testdata/fmt/badly_formatted.json",
"cli/tests/testdata/fmt/badly_formatted.md",
"cli/tests/testdata/byte_order_mark.ts",

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()

16
cli/cache/mod.rs vendored
View file

@ -11,6 +11,7 @@ use deno_graph::source::LoadFuture;
use deno_graph::source::LoadResponse;
use deno_graph::source::Loader;
use deno_runtime::permissions::PermissionsContainer;
use std::collections::HashMap;
use std::sync::Arc;
mod check;
@ -43,6 +44,7 @@ pub struct FetchCacher {
emit_cache: EmitCache,
dynamic_permissions: PermissionsContainer,
file_fetcher: Arc<FileFetcher>,
file_header_overrides: HashMap<ModuleSpecifier, HashMap<String, String>>,
root_permissions: PermissionsContainer,
cache_info_enabled: bool,
maybe_local_node_modules_url: Option<ModuleSpecifier>,
@ -52,6 +54,7 @@ impl FetchCacher {
pub fn new(
emit_cache: EmitCache,
file_fetcher: Arc<FileFetcher>,
file_header_overrides: HashMap<ModuleSpecifier, HashMap<String, String>>,
root_permissions: PermissionsContainer,
dynamic_permissions: PermissionsContainer,
maybe_local_node_modules_url: Option<ModuleSpecifier>,
@ -60,6 +63,7 @@ impl FetchCacher {
emit_cache,
dynamic_permissions,
file_fetcher,
file_header_overrides,
root_permissions,
cache_info_enabled: false,
maybe_local_node_modules_url,
@ -123,6 +127,7 @@ impl Loader for FetchCacher {
self.root_permissions.clone()
};
let file_fetcher = self.file_fetcher.clone();
let file_header_overrides = self.file_header_overrides.clone();
let specifier = specifier.clone();
async move {
@ -130,9 +135,18 @@ impl Loader for FetchCacher {
.fetch(&specifier, permissions)
.await
.map(|file| {
let maybe_headers =
match (file.maybe_headers, file_header_overrides.get(&specifier)) {
(Some(headers), Some(overrides)) => {
Some(headers.into_iter().chain(overrides.clone()).collect())
}
(Some(headers), None) => Some(headers),
(None, Some(overrides)) => Some(overrides.clone()),
(None, None) => None,
};
Ok(Some(LoadResponse::Module {
specifier: file.specifier,
maybe_headers: file.maybe_headers,
maybe_headers,
content: file.source,
}))
})

View file

@ -162,6 +162,7 @@ pub async fn create_graph_and_maybe_check(
let mut cache = cache::FetchCacher::new(
ps.emit_cache.clone(),
ps.file_fetcher.clone(),
ps.options.resolve_file_header_overrides(),
PermissionsContainer::allow_all(),
PermissionsContainer::allow_all(),
ps.options.node_modules_dir_specifier(),

View file

@ -88,7 +88,7 @@ async fn run_subcommand(flags: Flags) -> Result<i32, AnyError> {
Ok(0)
}
DenoSubcommand::Fmt(fmt_flags) => {
let cli_options = CliOptions::from_flags(flags)?;
let cli_options = CliOptions::from_flags(flags.clone())?;
let fmt_options = cli_options.resolve_fmt_options(fmt_flags)?;
tools::fmt::format(cli_options, fmt_options).await?;
Ok(0)
@ -130,7 +130,7 @@ async fn run_subcommand(flags: Flags) -> Result<i32, AnyError> {
if run_flags.is_stdin() {
tools::run::run_from_stdin(flags).await
} else {
tools::run::run_script(flags, run_flags).await
tools::run::run_script(flags).await
}
}
DenoSubcommand::Task(task_flags) => {

View file

@ -346,6 +346,7 @@ impl ProcState {
let mut cache = cache::FetchCacher::new(
self.emit_cache.clone(),
self.file_fetcher.clone(),
self.options.resolve_file_header_overrides(),
root_permissions,
dynamic_permissions,
self.options.node_modules_dir_specifier(),
@ -639,6 +640,7 @@ impl ProcState {
cache::FetchCacher::new(
self.emit_cache.clone(),
self.file_fetcher.clone(),
self.options.resolve_file_header_overrides(),
PermissionsContainer::allow_all(),
PermissionsContainer::allow_all(),
self.options.node_modules_dir_specifier(),

View file

@ -466,6 +466,16 @@ itest!(check_local_by_default_type_error {
exit_code: 1,
});
itest!(ts_without_extension {
args: "bundle --ext ts file_extensions/ts_without_extension",
output: "bundle/file_extensions/ts_without_extension.out",
});
itest!(js_without_extension {
args: "bundle --ext js file_extensions/js_without_extension",
output: "bundle/file_extensions/js_without_extension.out",
});
itest!(bundle_shebang_file {
args: "bundle subdir/shebang_file.js",
output: "bundle/shebang_file.bundle.out",

View file

@ -411,6 +411,82 @@ fn standalone_runtime_flags() {
.contains("PermissionDenied: Requires write access"));
}
#[test]
fn standalone_ext_flag_ts() {
let dir = TempDir::new();
let exe = if cfg!(windows) {
dir.path().join("ext_flag_ts.exe")
} else {
dir.path().join("ext_flag_ts")
};
let output = util::deno_cmd()
.current_dir(util::testdata_path())
.arg("compile")
.arg("--unstable")
.arg("--ext")
.arg("ts")
.arg("--output")
.arg(&exe)
.arg("./file_extensions/ts_without_extension")
.stdout(std::process::Stdio::piped())
.spawn()
.unwrap()
.wait_with_output()
.unwrap();
assert!(output.status.success());
let output = Command::new(exe)
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.unwrap()
.wait_with_output()
.unwrap();
assert!(output.status.success());
let stdout_str = String::from_utf8(output.stdout).unwrap();
assert_eq!(
util::strip_ansi_codes(&stdout_str),
"executing typescript with no extension\n"
);
}
#[test]
fn standalone_ext_flag_js() {
let dir = TempDir::new();
let exe = if cfg!(windows) {
dir.path().join("ext_flag_js.exe")
} else {
dir.path().join("ext_flag_js")
};
let output = util::deno_cmd()
.current_dir(util::testdata_path())
.arg("compile")
.arg("--unstable")
.arg("--ext")
.arg("js")
.arg("--output")
.arg(&exe)
.arg("./file_extensions/js_without_extension")
.stdout(std::process::Stdio::piped())
.spawn()
.unwrap()
.wait_with_output()
.unwrap();
assert!(output.status.success());
let output = Command::new(exe)
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.unwrap()
.wait_with_output()
.unwrap();
assert!(output.status.success());
let stdout_str = String::from_utf8(output.stdout).unwrap();
assert_eq!(
util::strip_ansi_codes(&stdout_str),
"executing javascript with no extension\n"
);
}
#[test]
fn standalone_import_map() {
let dir = TempDir::new();

View file

@ -3823,6 +3823,30 @@ itest!(error_cause_recursive {
exit_code: 1,
});
itest!(default_file_extension_is_js {
args: "run --check file_extensions/js_without_extension",
output: "file_extensions/js_without_extension.out",
exit_code: 0,
});
itest!(js_without_extension {
args: "run --ext js --check file_extensions/js_without_extension",
output: "file_extensions/js_without_extension.out",
exit_code: 0,
});
itest!(ts_without_extension {
args: "run --ext ts file_extensions/ts_without_extension",
output: "file_extensions/ts_without_extension.out",
exit_code: 0,
});
itest!(ext_flag_takes_precedence_over_extension {
args: "run --ext ts file_extensions/ts_with_js_extension.js",
output: "file_extensions/ts_with_extension.out",
exit_code: 0,
});
#[test]
fn websocket() {
let _g = util::http_server();

View file

@ -58,6 +58,24 @@ fn wait_contains(s: &str, lines: &mut impl Iterator<Item = String>) {
wait_for(|msg| msg.contains(s), lines)
}
/// Before test cases touch files, they need to wait for the watcher to be
/// ready. Waiting for subcommand output is insufficient.
/// The file watcher takes a moment to start watching files due to
/// asynchronicity. It is possible for the watched subcommand to finish before
/// any files are being watched.
/// deno must be running with --log-level=debug
/// file_name should be the file name and, optionally, extension. file_name
/// may not be a full path, as it is not portable.
fn wait_for_watcher(
file_name: &str,
stderr_lines: &mut impl Iterator<Item = String>,
) {
wait_for(
|m| m.contains("Watching paths") && m.contains(file_name),
stderr_lines,
);
}
fn read_line(s: &str, lines: &mut impl Iterator<Item = String>) -> String {
lines.find(|m| m.contains(s)).unwrap()
}
@ -508,20 +526,14 @@ fn run_watch_no_dynamic() {
let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child);
wait_contains("Hello world", &mut stdout_lines);
wait_for(
|m| m.contains("Watching paths") && m.contains("file_to_watch.js"),
&mut stderr_lines,
);
wait_for_watcher("file_to_watch.js", &mut stderr_lines);
// Change content of the file
write(&file_to_watch, "console.log('Hello world2');").unwrap();
wait_contains("Restarting", &mut stderr_lines);
wait_contains("Hello world2", &mut stdout_lines);
wait_for(
|m| m.contains("Watching paths") && m.contains("file_to_watch.js"),
&mut stderr_lines,
);
wait_for_watcher("file_to_watch.js", &mut stderr_lines);
// Add dependency
let another_file = t.path().join("another_file.js");
@ -534,30 +546,21 @@ fn run_watch_no_dynamic() {
wait_contains("Restarting", &mut stderr_lines);
wait_contains("0", &mut stdout_lines);
wait_for(
|m| m.contains("Watching paths") && m.contains("another_file.js"),
&mut stderr_lines,
);
wait_for_watcher("another_file.js", &mut stderr_lines);
// Confirm that restarting occurs when a new file is updated
write(&another_file, "export const foo = 42;").unwrap();
wait_contains("Restarting", &mut stderr_lines);
wait_contains("42", &mut stdout_lines);
wait_for(
|m| m.contains("Watching paths") && m.contains("file_to_watch.js"),
&mut stderr_lines,
);
wait_for_watcher("file_to_watch.js", &mut stderr_lines);
// Confirm that the watcher keeps on working even if the file is updated and has invalid syntax
write(&file_to_watch, "syntax error ^^").unwrap();
wait_contains("Restarting", &mut stderr_lines);
wait_contains("error:", &mut stderr_lines);
wait_for(
|m| m.contains("Watching paths") && m.contains("file_to_watch.js"),
&mut stderr_lines,
);
wait_for_watcher("file_to_watch.js", &mut stderr_lines);
// Then restore the file
write(
@ -568,20 +571,14 @@ fn run_watch_no_dynamic() {
wait_contains("Restarting", &mut stderr_lines);
wait_contains("42", &mut stdout_lines);
wait_for(
|m| m.contains("Watching paths") && m.contains("another_file.js"),
&mut stderr_lines,
);
wait_for_watcher("another_file.js", &mut stderr_lines);
// Update the content of the imported file with invalid syntax
write(&another_file, "syntax error ^^").unwrap();
wait_contains("Restarting", &mut stderr_lines);
wait_contains("error:", &mut stderr_lines);
wait_for(
|m| m.contains("Watching paths") && m.contains("another_file.js"),
&mut stderr_lines,
);
wait_for_watcher("another_file.js", &mut stderr_lines);
// Modify the imported file and make sure that restarting occurs
write(&another_file, "export const foo = 'modified!';").unwrap();
@ -629,12 +626,7 @@ fn run_watch_external_watch_files() {
let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child);
wait_contains("Process started", &mut stderr_lines);
wait_contains("Hello world", &mut stdout_lines);
wait_for(
|m| {
m.contains("Watching paths") && m.contains("external_file_to_watch.txt")
},
&mut stderr_lines,
);
wait_for_watcher("external_file_to_watch.txt", &mut stderr_lines);
// Change content of the external file
write(&external_file_to_watch, "Hello world2").unwrap();
@ -685,10 +677,7 @@ fn run_watch_load_unload_events() {
// Wait for the first load event to fire
wait_contains("load", &mut stdout_lines);
wait_for(
|m| m.contains("Watching paths") && m.contains("file_to_watch.js"),
&mut stderr_lines,
);
wait_for_watcher("file_to_watch.js", &mut stderr_lines);
// Change content of the file, this time without an interval to keep it alive.
write(
@ -743,10 +732,7 @@ fn run_watch_not_exit() {
wait_contains("Process started", &mut stderr_lines);
wait_contains("error:", &mut stderr_lines);
wait_for(
|m| m.contains("Watching paths") && m.contains("file_to_watch.js"),
&mut stderr_lines,
);
wait_for_watcher("file_to_watch.js", &mut stderr_lines);
// Make sure the watcher actually restarts and works fine with the proper syntax
write(&file_to_watch, "console.log(42);").unwrap();
@ -807,6 +793,47 @@ fn run_watch_with_import_map_and_relative_paths() {
check_alive_then_kill(child);
}
#[test]
fn run_watch_with_ext_flag() {
let t = TempDir::new();
let file_to_watch = t.path().join("file_to_watch");
write(&file_to_watch, "interface I{}; console.log(42);").unwrap();
let mut child = util::deno_cmd()
.current_dir(util::testdata_path())
.arg("run")
.arg("--watch")
.arg("--log-level")
.arg("debug")
.arg("--ext")
.arg("ts")
.arg(&file_to_watch)
.env("NO_COLOR", "1")
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.unwrap();
let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child);
wait_contains("42", &mut stdout_lines);
// Make sure the watcher actually restarts and works fine with the proper language
wait_for_watcher("file_to_watch", &mut stderr_lines);
wait_contains("Process finished", &mut stderr_lines);
write(
&file_to_watch,
"type Bear = 'polar' | 'grizzly'; console.log(123);",
)
.unwrap();
wait_contains("Restarting!", &mut stderr_lines);
wait_contains("123", &mut stdout_lines);
wait_contains("Process finished", &mut stderr_lines);
check_alive_then_kill(child);
}
#[test]
fn run_watch_error_messages() {
let t = TempDir::new();
@ -1193,10 +1220,7 @@ fn run_watch_dynamic_imports() {
&mut stdout_lines,
);
wait_for(
|m| m.contains("Watching paths") && m.contains("imported2.js"),
&mut stderr_lines,
);
wait_for_watcher("imported2.js", &mut stderr_lines);
wait_contains("finished", &mut stderr_lines);
write(

View file

@ -0,0 +1,8 @@
[WILDCARD]
// deno-fmt-ignore-file
// deno-lint-ignore-file
// This code was bundled using `deno bundle` and it's not recommended to edit it manually
"hello";
console.log("executing javascript with no extension");

View file

@ -0,0 +1,7 @@
[WILDCARD]
// deno-fmt-ignore-file
// deno-lint-ignore-file
// This code was bundled using `deno bundle` and it's not recommended to edit it manually
console.log("executing typescript with no extension");

View file

@ -0,0 +1,3 @@
let i = 123;
i = "hello"
console.log("executing javascript with no extension");

View file

@ -0,0 +1 @@
executing javascript with no extension

View file

@ -0,0 +1 @@
executing typescript with extension

View file

@ -0,0 +1,5 @@
interface Lollipop {
_: number;
}
console.log("executing typescript with extension");

View file

@ -0,0 +1,5 @@
interface Lollipop {
_: number;
}
console.log("executing typescript with extension");

View file

@ -0,0 +1,3 @@
interface Lollipop {}
console.log("executing typescript with no extension");

View file

@ -0,0 +1 @@
executing typescript with no extension

View file

@ -658,6 +658,6 @@ Deno.test(
p.close();
p.stdout.close();
assertStrictEquals(code, 1);
assertStringIncludes(stderr, "No such file or directory");
assertStringIncludes(stderr, "Failed getting cwd.");
},
);

View file

@ -5,7 +5,6 @@ use std::sync::Arc;
use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
use deno_core::resolve_url_or_path;
use deno_graph::Module;
use deno_runtime::colors;
@ -35,8 +34,7 @@ pub async fn bundle(
"Use alternative bundlers like \"deno_emit\", \"esbuild\" or \"rollup\" instead."
);
let module_specifier =
resolve_url_or_path(&bundle_flags.source_file, cli_options.initial_cwd())?;
let module_specifier = cli_options.resolve_main_module()?;
let resolver = |_| {
let cli_options = cli_options.clone();

View file

@ -49,7 +49,14 @@ pub async fn format(
fmt_options: FmtOptions,
) -> Result<(), AnyError> {
if fmt_options.is_stdin {
return format_stdin(fmt_options);
return format_stdin(
fmt_options,
cli_options
.ext_flag()
.as_ref()
.map(|s| s.as_str())
.unwrap_or("ts"),
);
}
let files = fmt_options.files;
@ -456,14 +463,14 @@ fn format_ensure_stable(
}
/// Format stdin and write result to stdout.
/// Treats input as TypeScript or as set by `--ext` flag.
/// Treats input as set by `--ext` flag.
/// Compatible with `--check` flag.
fn format_stdin(fmt_options: FmtOptions) -> Result<(), AnyError> {
fn format_stdin(fmt_options: FmtOptions, ext: &str) -> Result<(), AnyError> {
let mut source = String::new();
if stdin().read_to_string(&mut source).is_err() {
bail!("Failed to read from stdin");
}
let file_path = PathBuf::from(format!("_stdin.{}", fmt_options.ext));
let file_path = PathBuf::from(format!("_stdin.{ext}"));
let formatted_text = format_file(&file_path, &source, &fmt_options.options)?;
if fmt_options.check {
if formatted_text.is_some() {

View file

@ -6,7 +6,6 @@ use crate::colors;
use crate::proc_state::ProcState;
use crate::worker::create_main_worker;
use deno_core::error::AnyError;
use deno_core::resolve_path;
use deno_runtime::permissions::Permissions;
use deno_runtime::permissions::PermissionsContainer;
use rustyline::error::ReadlineError;
@ -82,8 +81,7 @@ async fn read_eval_file(
pub async fn run(flags: Flags, repl_flags: ReplFlags) -> Result<i32, AnyError> {
let ps = ProcState::build(flags).await?;
let main_module =
resolve_path("./$deno$repl.ts", ps.options.initial_cwd()).unwrap();
let main_module = ps.options.resolve_main_module()?;
let mut worker = create_main_worker(
&ps,
main_module,

View file

@ -5,26 +5,18 @@ use std::sync::Arc;
use deno_ast::MediaType;
use deno_ast::ModuleSpecifier;
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_core::resolve_path;
use deno_core::resolve_url_or_path;
use deno_graph::npm::NpmPackageReqReference;
use deno_runtime::permissions::Permissions;
use deno_runtime::permissions::PermissionsContainer;
use crate::args::EvalFlags;
use crate::args::Flags;
use crate::args::RunFlags;
use crate::file_fetcher::File;
use crate::proc_state::ProcState;
use crate::util;
use crate::worker::create_main_worker;
pub async fn run_script(
flags: Flags,
run_flags: RunFlags,
) -> Result<i32, AnyError> {
pub async fn run_script(flags: Flags) -> Result<i32, AnyError> {
if !flags.has_permission() && flags.has_permission_in_argv() {
log::warn!(
"{}",
@ -37,7 +29,7 @@ To grant permissions, set them before the script argument. For example:
}
if flags.watch.is_some() {
return run_with_watch(flags, run_flags.script).await;
return run_with_watch(flags).await;
}
// TODO(bartlomieju): actually I think it will also fail if there's an import
@ -52,12 +44,8 @@ To grant permissions, set them before the script argument. For example:
ps.dir.upgrade_check_file_path(),
);
let main_module =
if NpmPackageReqReference::from_str(&run_flags.script).is_ok() {
ModuleSpecifier::parse(&run_flags.script)?
} else {
resolve_url_or_path(&run_flags.script, ps.options.initial_cwd())?
};
let main_module = ps.options.resolve_main_module()?;
let permissions = PermissionsContainer::new(Permissions::from_options(
&ps.options.permissions_options(),
)?);
@ -69,8 +57,8 @@ To grant permissions, set them before the script argument. For example:
pub async fn run_from_stdin(flags: Flags) -> Result<i32, AnyError> {
let ps = ProcState::build(flags).await?;
let cwd = std::env::current_dir().context("Unable to get CWD")?;
let main_module = resolve_path("./$deno$stdin.ts", &cwd).unwrap();
let main_module = ps.options.resolve_main_module()?;
let mut worker = create_main_worker(
&ps,
main_module.clone(),
@ -101,12 +89,12 @@ pub async fn run_from_stdin(flags: Flags) -> Result<i32, AnyError> {
// TODO(bartlomieju): this function is not handling `exit_code` set by the runtime
// code properly.
async fn run_with_watch(flags: Flags, script: String) -> Result<i32, AnyError> {
async fn run_with_watch(flags: Flags) -> Result<i32, AnyError> {
let flags = Arc::new(flags);
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
let mut ps =
ProcState::build_for_file_watcher((*flags).clone(), sender.clone()).await?;
let main_module = resolve_url_or_path(&script, ps.options.initial_cwd())?;
let main_module = ps.options.resolve_main_module()?;
let operation = |main_module: ModuleSpecifier| {
ps.reset_for_file_watcher();
@ -140,13 +128,8 @@ pub async fn eval_command(
flags: Flags,
eval_flags: EvalFlags,
) -> Result<i32, AnyError> {
// deno_graph works off of extensions for local files to determine the media
// type, and so our "fake" specifier needs to have the proper extension.
let ps = ProcState::build(flags).await?;
let main_module = resolve_path(
&format!("./$deno$eval.{}", eval_flags.ext),
ps.options.initial_cwd(),
)?;
let main_module = ps.options.resolve_main_module()?;
let permissions = PermissionsContainer::new(Permissions::from_options(
&ps.options.permissions_options(),
)?);

View file

@ -39,8 +39,7 @@ pub async fn compile(
compile_flags: CompileFlags,
) -> Result<(), AnyError> {
let ps = ProcState::build(flags).await?;
let module_specifier =
resolve_url_or_path(&compile_flags.source_file, ps.options.initial_cwd())?;
let module_specifier = ps.options.resolve_main_module()?;
let module_roots = {
let mut vec = Vec::with_capacity(compile_flags.include.len() + 1);
vec.push(module_specifier.clone());

View file

@ -50,6 +50,7 @@ async function dlint() {
":!:cli/tsc/dts/**",
":!:cli/tests/testdata/encoding/**",
":!:cli/tests/testdata/error_syntax.js",
":!:cli/tests/testdata/file_extensions/ts_with_js_extension.js",
":!:cli/tests/testdata/fmt/**",
":!:cli/tests/testdata/npm/**",
":!:cli/tests/testdata/lint/**",