summaryrefslogtreecommitdiff
path: root/cli/args
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2023-02-22 22:45:35 -0500
committerGitHub <noreply@github.com>2023-02-22 22:45:35 -0500
commitb15f9e60a040e2e450e7ca9971a5fc07dbf8b94c (patch)
tree4290744b0c0a8f8f5d063322a650fdabf2d3150c /cli/args
parentcc8e4a00aaf4c4fe959944c7400f2e259f7faae8 (diff)
feat(task): support scripts in package.json (#17887)
This is a super basic initial implementation. We don't create a `node_modules/.bin` folder at the moment and add it to the PATH like we should which is necessary to make command name resolution in the subprocess work properly (ex. you run a script that launches another script that then tries to launch an "npx command"... this won't work atm). Closes #17492
Diffstat (limited to 'cli/args')
-rw-r--r--cli/args/config_file.rs39
-rw-r--r--cli/args/flags.rs54
-rw-r--r--cli/args/mod.rs31
3 files changed, 63 insertions, 61 deletions
diff --git a/cli/args/config_file.rs b/cli/args/config_file.rs
index 154692376..f7fd85579 100644
--- a/cli/args/config_file.rs
+++ b/cli/args/config_file.rs
@@ -18,6 +18,7 @@ use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::serde_json::Value;
use deno_core::ModuleSpecifier;
+use indexmap::IndexMap;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::collections::HashMap;
@@ -760,9 +761,9 @@ impl ConfigFile {
pub fn to_tasks_config(
&self,
- ) -> Result<Option<BTreeMap<String, String>>, AnyError> {
+ ) -> Result<Option<IndexMap<String, String>>, AnyError> {
if let Some(config) = self.json.tasks.clone() {
- let tasks_config: BTreeMap<String, String> =
+ let tasks_config: IndexMap<String, String> =
serde_json::from_value(config)
.context("Failed to parse \"tasks\" configuration")?;
Ok(Some(tasks_config))
@@ -815,25 +816,22 @@ impl ConfigFile {
pub fn resolve_tasks_config(
&self,
- ) -> Result<BTreeMap<String, String>, AnyError> {
+ ) -> Result<IndexMap<String, String>, AnyError> {
let maybe_tasks_config = self.to_tasks_config()?;
- if let Some(tasks_config) = maybe_tasks_config {
- for key in tasks_config.keys() {
- if key.is_empty() {
- bail!("Configuration file task names cannot be empty");
- } else if !key
- .chars()
- .all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | ':'))
- {
- bail!("Configuration file task names must only contain alpha-numeric characters, colons (:), underscores (_), or dashes (-). Task: {}", key);
- } else if !key.chars().next().unwrap().is_ascii_alphabetic() {
- bail!("Configuration file task names must start with an alphabetic character. Task: {}", key);
- }
+ let tasks_config = maybe_tasks_config.unwrap_or_default();
+ for key in tasks_config.keys() {
+ if key.is_empty() {
+ bail!("Configuration file task names cannot be empty");
+ } else if !key
+ .chars()
+ .all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | ':'))
+ {
+ bail!("Configuration file task names must only contain alpha-numeric characters, colons (:), underscores (_), or dashes (-). Task: {}", key);
+ } else if !key.chars().next().unwrap().is_ascii_alphabetic() {
+ bail!("Configuration file task names must start with an alphabetic character. Task: {}", key);
}
- Ok(tasks_config)
- } else {
- bail!("No tasks found in configuration file")
}
+ Ok(tasks_config)
}
pub fn to_lock_config(&self) -> Result<Option<LockConfig>, AnyError> {
@@ -1238,11 +1236,6 @@ mod tests {
}
#[test]
- fn tasks_no_tasks() {
- run_task_error_test(r#"{}"#, "No tasks found in configuration file");
- }
-
- #[test]
fn task_name_invalid_chars() {
run_task_error_test(
r#"{
diff --git a/cli/args/flags.rs b/cli/args/flags.rs
index 2c9f4c09d..825bf96d0 100644
--- a/cli/args/flags.rs
+++ b/cli/args/flags.rs
@@ -193,7 +193,7 @@ impl RunFlags {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TaskFlags {
pub cwd: Option<String>,
- pub task: String,
+ pub task: Option<String>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
@@ -508,26 +508,34 @@ impl Flags {
/// from the `path` dir.
/// If it returns None, the `package.json` file shouldn't be discovered at
/// all.
- pub fn package_json_arg(&self) -> Option<PathBuf> {
+ pub fn package_json_search_dir(&self) -> Option<PathBuf> {
use DenoSubcommand::*;
- if let Run(RunFlags { script }) = &self.subcommand {
- if let Ok(module_specifier) = deno_core::resolve_url_or_path(script) {
+ match &self.subcommand {
+ Run(RunFlags { script }) => {
+ let module_specifier = deno_core::resolve_url_or_path(script).ok()?;
if module_specifier.scheme() == "file" {
let p = module_specifier
.to_file_path()
.unwrap()
.parent()?
.to_owned();
- return Some(p);
+ Some(p)
} else if module_specifier.scheme() == "npm" {
- let p = std::env::current_dir().unwrap();
- return Some(p);
+ Some(std::env::current_dir().unwrap())
+ } else {
+ None
}
}
+ Task(TaskFlags { cwd: Some(cwd), .. }) => {
+ deno_core::resolve_url_or_path(cwd)
+ .ok()?
+ .to_file_path()
+ .ok()
+ }
+ Task(TaskFlags { cwd: None, .. }) => std::env::current_dir().ok(),
+ _ => None,
}
-
- None
}
pub fn has_permission(&self) -> bool {
@@ -2795,7 +2803,7 @@ fn task_parse(
let mut task_flags = TaskFlags {
cwd: None,
- task: String::new(),
+ task: None,
};
if let Some(cwd) = matches.value_of("cwd") {
@@ -2830,7 +2838,7 @@ fn task_parse(
}
if index < raw_args.len() {
- task_flags.task = raw_args[index].to_string();
+ task_flags.task = Some(raw_args[index].to_string());
index += 1;
if index < raw_args.len() {
@@ -6394,7 +6402,7 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Task(TaskFlags {
cwd: None,
- task: "build".to_string(),
+ task: Some("build".to_string()),
}),
argv: svec!["hello", "world"],
..Flags::default()
@@ -6407,7 +6415,7 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Task(TaskFlags {
cwd: None,
- task: "build".to_string(),
+ task: Some("build".to_string()),
}),
..Flags::default()
}
@@ -6419,7 +6427,7 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Task(TaskFlags {
cwd: Some("foo".to_string()),
- task: "build".to_string(),
+ task: Some("build".to_string()),
}),
..Flags::default()
}
@@ -6443,7 +6451,7 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Task(TaskFlags {
cwd: None,
- task: "build".to_string(),
+ task: Some("build".to_string()),
}),
argv: svec!["--", "hello", "world"],
config_flag: ConfigFlag::Path("deno.json".to_owned()),
@@ -6459,7 +6467,7 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Task(TaskFlags {
cwd: Some("foo".to_string()),
- task: "build".to_string(),
+ task: Some("build".to_string()),
}),
argv: svec!["--", "hello", "world"],
..Flags::default()
@@ -6476,7 +6484,7 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Task(TaskFlags {
cwd: None,
- task: "build".to_string(),
+ task: Some("build".to_string()),
}),
argv: svec!["--"],
..Flags::default()
@@ -6492,7 +6500,7 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Task(TaskFlags {
cwd: None,
- task: "build".to_string(),
+ task: Some("build".to_string()),
}),
argv: svec!["-1", "--test"],
..Flags::default()
@@ -6508,7 +6516,7 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Task(TaskFlags {
cwd: None,
- task: "build".to_string(),
+ task: Some("build".to_string()),
}),
argv: svec!["--test"],
..Flags::default()
@@ -6526,7 +6534,7 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Task(TaskFlags {
cwd: None,
- task: "build".to_string(),
+ task: Some("build".to_string()),
}),
unstable: true,
log_level: Some(log::Level::Error),
@@ -6543,7 +6551,7 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Task(TaskFlags {
cwd: None,
- task: "".to_string(),
+ task: None,
}),
..Flags::default()
}
@@ -6558,7 +6566,7 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Task(TaskFlags {
cwd: None,
- task: "".to_string(),
+ task: None,
}),
config_flag: ConfigFlag::Path("deno.jsonc".to_string()),
..Flags::default()
@@ -6574,7 +6582,7 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Task(TaskFlags {
cwd: None,
- task: "".to_string(),
+ task: None,
}),
config_flag: ConfigFlag::Path("deno.jsonc".to_string()),
..Flags::default()
diff --git a/cli/args/mod.rs b/cli/args/mod.rs
index 3d8a29fe7..aa1781bd8 100644
--- a/cli/args/mod.rs
+++ b/cli/args/mod.rs
@@ -9,9 +9,9 @@ pub mod package_json;
pub use self::import_map::resolve_import_map_from_specifier;
use ::import_map::ImportMap;
+use indexmap::IndexMap;
use crate::npm::NpmResolutionSnapshot;
-use crate::util::fs::canonicalize_path;
pub use config_file::BenchConfig;
pub use config_file::CompilerOptions;
pub use config_file::ConfigFile;
@@ -49,7 +49,6 @@ 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::BTreeMap;
use std::collections::HashMap;
use std::env;
use std::io::BufReader;
@@ -398,6 +397,7 @@ fn discover_package_json(
) -> Result<Option<PackageJson>, AnyError> {
const PACKAGE_JSON_NAME: &str = "package.json";
+ // note: ancestors() includes the `start` path
for ancestor in start.ancestors() {
let path = ancestor.join(PACKAGE_JSON_NAME);
@@ -430,17 +430,10 @@ fn discover_package_json(
// TODO(bartlomieju): discover for all subcommands, but print warnings that
// `package.json` is ignored in bundle/compile/etc.
- if let crate::args::DenoSubcommand::Task(TaskFlags {
- cwd: Some(path), ..
- }) = &flags.subcommand
- {
- // attempt to resolve the config file from the task subcommand's
- // `--cwd` when specified
- let task_cwd = canonicalize_path(&PathBuf::from(path))?;
- return discover_from(&task_cwd, None);
- } else if let Some(package_json_arg) = flags.package_json_arg() {
- let package_json_arg = canonicalize_path(&package_json_arg)?;
- return discover_from(&package_json_arg, maybe_stop_at);
+ if let Some(package_json_dir) = flags.package_json_search_dir() {
+ let package_json_dir =
+ canonicalize_path_maybe_not_exists(&package_json_dir)?;
+ return discover_from(&package_json_dir, maybe_stop_at);
}
log::debug!("No package.json file found");
@@ -802,9 +795,11 @@ impl CliOptions {
pub fn resolve_tasks_config(
&self,
- ) -> Result<BTreeMap<String, String>, AnyError> {
+ ) -> Result<IndexMap<String, String>, AnyError> {
if let Some(config_file) = &self.maybe_config_file {
config_file.resolve_tasks_config()
+ } else if self.maybe_package_json.is_some() {
+ Ok(Default::default())
} else {
bail!("No config file found")
}
@@ -841,7 +836,13 @@ impl CliOptions {
pub fn maybe_package_json_deps(
&self,
) -> Result<Option<HashMap<String, NpmPackageReq>>, AnyError> {
- if let Some(package_json) = self.maybe_package_json() {
+ if matches!(
+ self.flags.subcommand,
+ DenoSubcommand::Task(TaskFlags { task: None, .. })
+ ) {
+ // don't have any package json dependencies for deno task with no args
+ Ok(None)
+ } else if let Some(package_json) = self.maybe_package_json() {
package_json::get_local_package_json_version_reqs(package_json).map(Some)
} else {
Ok(None)