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
This commit is contained in:
David Sherret 2023-02-22 22:45:35 -05:00 committed by GitHub
parent cc8e4a00aa
commit b15f9e60a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 561 additions and 197 deletions

View file

@ -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> {
@ -1237,11 +1235,6 @@ mod tests {
assert!(err.to_string().contains("Unable to parse config file"));
}
#[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(