mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -05:00
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:
parent
cc8e4a00aa
commit
b15f9e60a0
45 changed files with 561 additions and 197 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -1194,6 +1194,7 @@ dependencies = [
|
|||
"deno_core",
|
||||
"digest 0.10.6",
|
||||
"idna 0.3.0",
|
||||
"indexmap",
|
||||
"md-5",
|
||||
"md4",
|
||||
"once_cell",
|
||||
|
@ -1279,9 +1280,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "deno_task_shell"
|
||||
version = "0.8.2"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "532b383a071a05144c712614d62f08a2f9fad48dd62d6d457ed3884b049357da"
|
||||
checksum = "a7068bd49521a7b22dc6df8937097a7ac285ea320cbd78582b4155d31f0d5049"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
|
|
|
@ -87,6 +87,7 @@ flate2 = "=1.0.24"
|
|||
futures = "0.3.21"
|
||||
http = "=0.2.8"
|
||||
hyper = "0.14.18"
|
||||
indexmap = "1.9.2"
|
||||
libc = "0.2.126"
|
||||
log = "=0.4.17"
|
||||
lzzzz = "1.0"
|
||||
|
|
|
@ -50,7 +50,7 @@ deno_graph = "0.44.0"
|
|||
deno_lint = { version = "0.40.0", features = ["docs"] }
|
||||
deno_lockfile.workspace = true
|
||||
deno_runtime = { workspace = true, features = ["dont_create_runtime_snapshot", "include_js_files_for_snapshotting"] }
|
||||
deno_task_shell = "0.8.1"
|
||||
deno_task_shell = "0.10.0"
|
||||
napi_sym.workspace = true
|
||||
|
||||
async-trait.workspace = true
|
||||
|
@ -75,7 +75,7 @@ fancy-regex = "=0.10.0"
|
|||
flate2.workspace = true
|
||||
http.workspace = true
|
||||
import_map = "=0.15.0"
|
||||
indexmap = "=1.9.2"
|
||||
indexmap.workspace = true
|
||||
jsonc-parser = { version = "=0.21.0", features = ["serde"] }
|
||||
libc.workspace = true
|
||||
log = { workspace = true, features = ["serde"] }
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -285,17 +285,41 @@ pub fn node_resolve_npm_reference(
|
|||
Ok(Some(resolve_response))
|
||||
}
|
||||
|
||||
pub fn node_resolve_binary_commands(
|
||||
pkg_nv: &NpmPackageNv,
|
||||
npm_resolver: &NpmPackageResolver,
|
||||
) -> Result<Vec<String>, AnyError> {
|
||||
let package_folder =
|
||||
npm_resolver.resolve_package_folder_from_deno_module(pkg_nv)?;
|
||||
let package_json_path = package_folder.join("package.json");
|
||||
let package_json = PackageJson::load(
|
||||
npm_resolver,
|
||||
&mut PermissionsContainer::allow_all(),
|
||||
package_json_path,
|
||||
)?;
|
||||
|
||||
Ok(match package_json.bin {
|
||||
Some(Value::String(_)) => vec![pkg_nv.name.to_string()],
|
||||
Some(Value::Object(o)) => {
|
||||
o.into_iter().map(|(key, _)| key).collect::<Vec<_>>()
|
||||
}
|
||||
_ => Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn node_resolve_binary_export(
|
||||
pkg_nv: &NpmPackageNv,
|
||||
bin_name: Option<&str>,
|
||||
npm_resolver: &NpmPackageResolver,
|
||||
permissions: &mut dyn NodePermissions,
|
||||
) -> Result<NodeResolution, AnyError> {
|
||||
let package_folder =
|
||||
npm_resolver.resolve_package_folder_from_deno_module(pkg_nv)?;
|
||||
let package_json_path = package_folder.join("package.json");
|
||||
let package_json =
|
||||
PackageJson::load(npm_resolver, permissions, package_json_path)?;
|
||||
let package_json = PackageJson::load(
|
||||
npm_resolver,
|
||||
&mut PermissionsContainer::allow_all(),
|
||||
package_json_path,
|
||||
)?;
|
||||
let bin = match &package_json.bin {
|
||||
Some(bin) => bin,
|
||||
None => bail!(
|
||||
|
|
|
@ -150,25 +150,23 @@ impl NpmPackageResolver {
|
|||
/// Resolves an npm package folder path from a Deno module.
|
||||
pub fn resolve_package_folder_from_deno_module(
|
||||
&self,
|
||||
package_id: &NpmPackageNv,
|
||||
pkg_nv: &NpmPackageNv,
|
||||
) -> Result<PathBuf, AnyError> {
|
||||
let node_id = self
|
||||
.resolution
|
||||
.resolve_pkg_id_from_deno_module(package_id)?;
|
||||
self.resolve_pkg_folder_from_deno_module_at_node_id(&node_id)
|
||||
let pkg_id = self.resolution.resolve_pkg_id_from_deno_module(pkg_nv)?;
|
||||
self.resolve_pkg_folder_from_deno_module_at_pkg_id(&pkg_id)
|
||||
}
|
||||
|
||||
fn resolve_pkg_folder_from_deno_module_at_node_id(
|
||||
fn resolve_pkg_folder_from_deno_module_at_pkg_id(
|
||||
&self,
|
||||
package_id: &NpmPackageId,
|
||||
pkg_id: &NpmPackageId,
|
||||
) -> Result<PathBuf, AnyError> {
|
||||
let path = self
|
||||
.fs_resolver
|
||||
.resolve_package_folder_from_deno_module(package_id)?;
|
||||
.resolve_package_folder_from_deno_module(pkg_id)?;
|
||||
let path = canonicalize_path_maybe_not_exists(&path)?;
|
||||
log::debug!(
|
||||
"Resolved package folder of {} to {}",
|
||||
package_id.as_serialized(),
|
||||
pkg_id.as_serialized(),
|
||||
path.display()
|
||||
);
|
||||
Ok(path)
|
||||
|
|
|
@ -2752,7 +2752,7 @@ itest!(config_not_auto_discovered_for_remote_script {
|
|||
itest!(package_json_auto_discovered_for_local_script_log {
|
||||
args: "run -L debug -A no_deno_json/main.ts",
|
||||
output: "run/with_package_json/no_deno_json/main.out",
|
||||
maybe_cwd: Some("run/with_package_json/"),
|
||||
cwd: Some("run/with_package_json/"),
|
||||
// prevent creating a node_modules dir in the code directory
|
||||
copy_temp_dir: Some("run/with_package_json/"),
|
||||
envs: env_vars_for_npm_tests_no_sync_download(),
|
||||
|
@ -2765,7 +2765,7 @@ itest!(
|
|||
package_json_auto_discovered_for_local_script_log_with_stop {
|
||||
args: "run -L debug with_stop/some/nested/dir/main.ts",
|
||||
output: "run/with_package_json/with_stop/main.out",
|
||||
maybe_cwd: Some("run/with_package_json/"),
|
||||
cwd: Some("run/with_package_json/"),
|
||||
copy_temp_dir: Some("run/with_package_json/"),
|
||||
envs: env_vars_for_npm_tests_no_sync_download(),
|
||||
http_server: true,
|
||||
|
@ -2777,7 +2777,7 @@ itest!(
|
|||
package_json_auto_discovered_node_modules_relative_package_json {
|
||||
args: "run -A main.js",
|
||||
output: "run/with_package_json/no_deno_json/sub_dir/main.out",
|
||||
maybe_cwd: Some("run/with_package_json/no_deno_json/sub_dir"),
|
||||
cwd: Some("run/with_package_json/no_deno_json/sub_dir"),
|
||||
copy_temp_dir: Some("run/with_package_json/"),
|
||||
envs: env_vars_for_npm_tests_no_sync_download(),
|
||||
http_server: true,
|
||||
|
@ -2787,7 +2787,7 @@ itest!(
|
|||
itest!(package_json_auto_discovered_for_npm_binary {
|
||||
args: "run -L debug -A npm:@denotest/bin/cli-esm this is a test",
|
||||
output: "run/with_package_json/npm_binary/main.out",
|
||||
maybe_cwd: Some("run/with_package_json/npm_binary/"),
|
||||
cwd: Some("run/with_package_json/npm_binary/"),
|
||||
copy_temp_dir: Some("run/with_package_json/"),
|
||||
envs: env_vars_for_npm_tests_no_sync_download(),
|
||||
http_server: true,
|
||||
|
|
|
@ -3,30 +3,32 @@
|
|||
// Most of the tests for this are in deno_task_shell.
|
||||
// These tests are intended to only test integration.
|
||||
|
||||
use test_util::env_vars_for_npm_tests;
|
||||
|
||||
itest!(task_no_args {
|
||||
args: "task -q --config task/deno.json",
|
||||
output: "task/task_no_args.out",
|
||||
args: "task -q --config task/deno_json/deno.json",
|
||||
output: "task/deno_json/task_no_args.out",
|
||||
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||
exit_code: 1,
|
||||
});
|
||||
|
||||
itest!(task_cwd {
|
||||
args: "task -q --config task/deno.json --cwd .. echo_cwd",
|
||||
output: "task/task_cwd.out",
|
||||
args: "task -q --config task/deno_json/deno.json --cwd .. echo_cwd",
|
||||
output: "task/deno_json/task_cwd.out",
|
||||
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||
exit_code: 0,
|
||||
});
|
||||
|
||||
itest!(task_init_cwd {
|
||||
args: "task -q --config task/deno.json --cwd .. echo_init_cwd",
|
||||
output: "task/task_init_cwd.out",
|
||||
args: "task -q --config task/deno_json/deno.json --cwd .. echo_init_cwd",
|
||||
output: "task/deno_json/task_init_cwd.out",
|
||||
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||
exit_code: 0,
|
||||
});
|
||||
|
||||
itest!(task_init_cwd_already_set {
|
||||
args: "task -q --config task/deno.json echo_init_cwd",
|
||||
output: "task/task_init_cwd_already_set.out",
|
||||
args: "task -q --config task/deno_json/deno.json echo_init_cwd",
|
||||
output: "task/deno_json/task_init_cwd_already_set.out",
|
||||
envs: vec![
|
||||
("NO_COLOR".to_string(), "1".to_string()),
|
||||
("INIT_CWD".to_string(), "HELLO".to_string())
|
||||
|
@ -35,15 +37,15 @@ itest!(task_init_cwd_already_set {
|
|||
});
|
||||
|
||||
itest!(task_cwd_resolves_config_from_specified_dir {
|
||||
args: "task -q --cwd task",
|
||||
output: "task/task_no_args.out",
|
||||
args: "task -q --cwd task/deno_json",
|
||||
output: "task/deno_json/task_no_args.out",
|
||||
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||
exit_code: 1,
|
||||
});
|
||||
|
||||
itest!(task_non_existent {
|
||||
args: "task --config task/deno.json non_existent",
|
||||
output: "task/task_non_existent.out",
|
||||
args: "task --config task/deno_json/deno.json non_existent",
|
||||
output: "task/deno_json/task_non_existent.out",
|
||||
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||
exit_code: 1,
|
||||
});
|
||||
|
@ -51,27 +53,27 @@ itest!(task_non_existent {
|
|||
#[test]
|
||||
fn task_emoji() {
|
||||
// this bug only appears when using a pty/tty
|
||||
let args = "task --config task/deno.json echo_emoji";
|
||||
let args = "task --config task/deno_json/deno.json echo_emoji";
|
||||
use test_util::PtyData::*;
|
||||
test_util::test_pty2(args, vec![Output("Task echo_emoji echo 🔥\r\n🔥")]);
|
||||
}
|
||||
|
||||
itest!(task_boolean_logic {
|
||||
args: "task -q --config task/deno.json boolean_logic",
|
||||
output: "task/task_boolean_logic.out",
|
||||
args: "task -q --config task/deno_json/deno.json boolean_logic",
|
||||
output: "task/deno_json/task_boolean_logic.out",
|
||||
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||
});
|
||||
|
||||
itest!(task_exit_code_5 {
|
||||
args: "task --config task/deno.json exit_code_5",
|
||||
output: "task/task_exit_code_5.out",
|
||||
args: "task --config task/deno_json/deno.json exit_code_5",
|
||||
output: "task/deno_json/task_exit_code_5.out",
|
||||
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||
exit_code: 5,
|
||||
});
|
||||
|
||||
itest!(task_additional_args {
|
||||
args: "task -q --config task/deno.json echo 2",
|
||||
output: "task/task_additional_args.out",
|
||||
args: "task -q --config task/deno_json/deno.json echo 2",
|
||||
output: "task/deno_json/task_additional_args.out",
|
||||
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||
});
|
||||
|
||||
|
@ -80,11 +82,11 @@ itest!(task_additional_args_no_shell_expansion {
|
|||
"task",
|
||||
"-q",
|
||||
"--config",
|
||||
"task/deno.json",
|
||||
"task/deno_json/deno.json",
|
||||
"echo",
|
||||
"$(echo 5)"
|
||||
],
|
||||
output: "task/task_additional_args_no_shell_expansion.out",
|
||||
output: "task/deno_json/task_additional_args_no_shell_expansion.out",
|
||||
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||
});
|
||||
|
||||
|
@ -93,11 +95,11 @@ itest!(task_additional_args_nested_strings {
|
|||
"task",
|
||||
"-q",
|
||||
"--config",
|
||||
"task/deno.json",
|
||||
"task/deno_json/deno.json",
|
||||
"echo",
|
||||
"string \"quoted string\""
|
||||
],
|
||||
output: "task/task_additional_args_nested_strings.out",
|
||||
output: "task/deno_json/task_additional_args_nested_strings.out",
|
||||
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||
});
|
||||
|
||||
|
@ -106,25 +108,124 @@ itest!(task_additional_args_no_logic {
|
|||
"task",
|
||||
"-q",
|
||||
"--config",
|
||||
"task/deno.json",
|
||||
"task/deno_json/deno.json",
|
||||
"echo",
|
||||
"||",
|
||||
"echo",
|
||||
"5"
|
||||
],
|
||||
output: "task/task_additional_args_no_logic.out",
|
||||
output: "task/deno_json/task_additional_args_no_logic.out",
|
||||
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||
});
|
||||
|
||||
itest!(task_deno_exe_no_env {
|
||||
args_vec: vec!["task", "-q", "--config", "task/deno.json", "deno_echo"],
|
||||
output: "task/task_deno_exe_no_env.out",
|
||||
args_vec: vec![
|
||||
"task",
|
||||
"-q",
|
||||
"--config",
|
||||
"task/deno_json/deno.json",
|
||||
"deno_echo"
|
||||
],
|
||||
output: "task/deno_json/task_deno_exe_no_env.out",
|
||||
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||
env_clear: true,
|
||||
});
|
||||
|
||||
itest!(task_piped_stdin {
|
||||
args_vec: vec!["task", "-q", "--config", "task/deno.json", "piped"],
|
||||
output: "task/task_piped_stdin.out",
|
||||
args_vec: vec![
|
||||
"task",
|
||||
"-q",
|
||||
"--config",
|
||||
"task/deno_json/deno.json",
|
||||
"piped"
|
||||
],
|
||||
output: "task/deno_json/task_piped_stdin.out",
|
||||
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||
});
|
||||
|
||||
itest!(task_package_json_no_arg {
|
||||
args: "task",
|
||||
cwd: Some("task/package_json/"),
|
||||
output: "task/package_json/no_args.out",
|
||||
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||
exit_code: 1,
|
||||
});
|
||||
|
||||
itest!(task_package_json_echo {
|
||||
args: "task --quiet echo",
|
||||
cwd: Some("task/package_json/"),
|
||||
output: "task/package_json/echo.out",
|
||||
// use a temp dir because the node_modules folder will be created
|
||||
copy_temp_dir: Some("task/package_json/"),
|
||||
envs: env_vars_for_npm_tests(),
|
||||
exit_code: 0,
|
||||
http_server: true,
|
||||
});
|
||||
|
||||
itest!(task_package_json_npm_bin {
|
||||
args: "task bin extra",
|
||||
cwd: Some("task/package_json/"),
|
||||
output: "task/package_json/bin.out",
|
||||
copy_temp_dir: Some("task/package_json/"),
|
||||
envs: env_vars_for_npm_tests(),
|
||||
exit_code: 0,
|
||||
http_server: true,
|
||||
});
|
||||
|
||||
itest!(task_both_no_arg {
|
||||
args: "task",
|
||||
cwd: Some("task/both/"),
|
||||
output: "task/both/no_args.out",
|
||||
envs: vec![("NO_COLOR".to_string(), "1".to_string())],
|
||||
exit_code: 1,
|
||||
});
|
||||
|
||||
itest!(task_both_deno_json_selected {
|
||||
args: "task other",
|
||||
cwd: Some("task/both/"),
|
||||
output: "task/both/deno_selected.out",
|
||||
copy_temp_dir: Some("task/both/"),
|
||||
envs: env_vars_for_npm_tests(),
|
||||
exit_code: 0,
|
||||
http_server: true,
|
||||
});
|
||||
|
||||
itest!(task_both_package_json_selected {
|
||||
args: "task bin asdf",
|
||||
cwd: Some("task/both/"),
|
||||
output: "task/both/package_json_selected.out",
|
||||
copy_temp_dir: Some("task/both/"),
|
||||
envs: env_vars_for_npm_tests(),
|
||||
exit_code: 0,
|
||||
http_server: true,
|
||||
});
|
||||
|
||||
itest!(task_both_prefers_deno {
|
||||
args: "task output some text",
|
||||
cwd: Some("task/both/"),
|
||||
output: "task/both/prefers_deno.out",
|
||||
copy_temp_dir: Some("task/both/"),
|
||||
envs: env_vars_for_npm_tests(),
|
||||
exit_code: 0,
|
||||
http_server: true,
|
||||
});
|
||||
|
||||
itest!(task_npx_non_existent {
|
||||
args: "task non-existent",
|
||||
cwd: Some("task/npx/"),
|
||||
output: "task/npx/non_existent.out",
|
||||
copy_temp_dir: Some("task/npx/"),
|
||||
envs: env_vars_for_npm_tests(),
|
||||
exit_code: 1,
|
||||
http_server: true,
|
||||
});
|
||||
|
||||
itest!(task_npx_on_own {
|
||||
args: "task on-own",
|
||||
cwd: Some("task/npx/"),
|
||||
output: "task/npx/on_own.out",
|
||||
copy_temp_dir: Some("task/npx/"),
|
||||
envs: env_vars_for_npm_tests(),
|
||||
exit_code: 1,
|
||||
http_server: true,
|
||||
});
|
||||
|
|
5
cli/tests/testdata/npm/registry/@denotest/bin/0.5.0/cli.mjs
vendored
Normal file
5
cli/tests/testdata/npm/registry/@denotest/bin/0.5.0/cli.mjs
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
import process from "node:process";
|
||||
|
||||
for (const arg of process.argv.slice(2)) {
|
||||
console.log(arg);
|
||||
}
|
5
cli/tests/testdata/npm/registry/@denotest/bin/0.5.0/package.json
vendored
Normal file
5
cli/tests/testdata/npm/registry/@denotest/bin/0.5.0/package.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "@deno/bin",
|
||||
"version": "0.5.0",
|
||||
"bin": "./cli.mjs"
|
||||
}
|
6
cli/tests/testdata/task/both/deno.json
vendored
Normal file
6
cli/tests/testdata/task/both/deno.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"tasks": {
|
||||
"output": "deno eval 'console.log(1)'",
|
||||
"other": "deno eval 'console.log(2)'"
|
||||
}
|
||||
}
|
4
cli/tests/testdata/task/both/deno_selected.out
vendored
Normal file
4
cli/tests/testdata/task/both/deno_selected.out
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
Download http://localhost:4545/npm/registry/@denotest/bin
|
||||
Download http://localhost:4545/npm/registry/@denotest/bin/1.0.0.tgz
|
||||
Task other deno eval 'console.log(2)'
|
||||
2
|
0
cli/tests/testdata/task/both/echo.out
vendored
Normal file
0
cli/tests/testdata/task/both/echo.out
vendored
Normal file
7
cli/tests/testdata/task/both/no_args.out
vendored
Normal file
7
cli/tests/testdata/task/both/no_args.out
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
Available tasks:
|
||||
- output
|
||||
deno eval 'console.log(1)'
|
||||
- other
|
||||
deno eval 'console.log(2)'
|
||||
- bin (package.json)
|
||||
cli-esm testing this out
|
9
cli/tests/testdata/task/both/package.json
vendored
Normal file
9
cli/tests/testdata/task/both/package.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"scripts": {
|
||||
"bin": "cli-esm testing this out",
|
||||
"output": "echo should never be called or shown"
|
||||
},
|
||||
"dependencies": {
|
||||
"other": "npm:@denotest/bin@1.0"
|
||||
}
|
||||
}
|
7
cli/tests/testdata/task/both/package_json_selected.out
vendored
Normal file
7
cli/tests/testdata/task/both/package_json_selected.out
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
Download http://localhost:4545/npm/registry/@denotest/bin
|
||||
Download http://localhost:4545/npm/registry/@denotest/bin/1.0.0.tgz
|
||||
Task bin cli-esm testing this out "asdf"
|
||||
testing
|
||||
this
|
||||
out
|
||||
asdf
|
4
cli/tests/testdata/task/both/prefers_deno.out
vendored
Normal file
4
cli/tests/testdata/task/both/prefers_deno.out
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
Download http://localhost:4545/npm/registry/@denotest/bin
|
||||
Download http://localhost:4545/npm/registry/@denotest/bin/1.0.0.tgz
|
||||
Task output deno eval 'console.log(1)' "some" "text"
|
||||
1
|
|
@ -1,19 +1,19 @@
|
|||
Available tasks:
|
||||
- boolean_logic
|
||||
sleep 0.1 && echo 3 && echo 4 & echo 1 && echo 2 || echo NOPE
|
||||
- deno_echo
|
||||
deno eval 'console.log(5)'
|
||||
- echo
|
||||
echo 1
|
||||
- echo_cwd
|
||||
echo $(pwd)
|
||||
- echo_emoji
|
||||
echo 🔥
|
||||
- echo_init_cwd
|
||||
echo $INIT_CWD
|
||||
- exit_code_5
|
||||
echo $(echo 10 ; exit 2) && exit 5
|
||||
- piped
|
||||
echo 12345 | (deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)' && deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)')
|
||||
- deno_echo
|
||||
deno eval 'console.log(5)'
|
||||
- strings
|
||||
deno run main.ts && deno eval "console.log(\"test\")"
|
||||
- piped
|
||||
echo 12345 | (deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)' && deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)')
|
||||
- exit_code_5
|
||||
echo $(echo 10 ; exit 2) && exit 5
|
||||
- echo_cwd
|
||||
echo $(pwd)
|
||||
- echo_init_cwd
|
||||
echo $INIT_CWD
|
||||
- echo_emoji
|
||||
echo 🔥
|
|
@ -2,19 +2,19 @@ Task not found: non_existent
|
|||
Available tasks:
|
||||
- boolean_logic
|
||||
sleep 0.1 && echo 3 && echo 4 & echo 1 && echo 2 || echo NOPE
|
||||
- deno_echo
|
||||
deno eval 'console.log(5)'
|
||||
- echo
|
||||
echo 1
|
||||
- echo_cwd
|
||||
echo $(pwd)
|
||||
- echo_emoji
|
||||
echo 🔥
|
||||
- echo_init_cwd
|
||||
echo $INIT_CWD
|
||||
- exit_code_5
|
||||
echo $(echo 10 ; exit 2) && exit 5
|
||||
- piped
|
||||
echo 12345 | (deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)' && deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)')
|
||||
- deno_echo
|
||||
deno eval 'console.log(5)'
|
||||
- strings
|
||||
deno run main.ts && deno eval "console.log(\"test\")"
|
||||
- piped
|
||||
echo 12345 | (deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)' && deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)')
|
||||
- exit_code_5
|
||||
echo $(echo 10 ; exit 2) && exit 5
|
||||
- echo_cwd
|
||||
echo $(pwd)
|
||||
- echo_init_cwd
|
||||
echo $INIT_CWD
|
||||
- echo_emoji
|
||||
echo 🔥
|
2
cli/tests/testdata/task/npx/non_existent.out
vendored
Normal file
2
cli/tests/testdata/task/npx/non_existent.out
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
Task non-existent npx this-command-should-not-exist-for-you
|
||||
npx: could not resolve command 'this-command-should-not-exist-for-you'
|
2
cli/tests/testdata/task/npx/on_own.out
vendored
Normal file
2
cli/tests/testdata/task/npx/on_own.out
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
Task on-own npx
|
||||
npx: missing command
|
6
cli/tests/testdata/task/npx/package.json
vendored
Normal file
6
cli/tests/testdata/task/npx/package.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"scripts": {
|
||||
"non-existent": "npx this-command-should-not-exist-for-you",
|
||||
"on-own": "npx"
|
||||
}
|
||||
}
|
10
cli/tests/testdata/task/package_json/bin.out
vendored
Normal file
10
cli/tests/testdata/task/package_json/bin.out
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
Download http://localhost:4545/npm/registry/@denotest/bin
|
||||
Download http://localhost:4545/npm/registry/@denotest/bin/0.5.0.tgz
|
||||
Download http://localhost:4545/npm/registry/@denotest/bin/1.0.0.tgz
|
||||
Task bin @denotest/bin hi && cli-esm testing this out && npx cli-cjs test "extra"
|
||||
hi
|
||||
testing
|
||||
this
|
||||
out
|
||||
test
|
||||
extra
|
1
cli/tests/testdata/task/package_json/echo.out
vendored
Normal file
1
cli/tests/testdata/task/package_json/echo.out
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
1
|
5
cli/tests/testdata/task/package_json/no_args.out
vendored
Normal file
5
cli/tests/testdata/task/package_json/no_args.out
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
Available tasks:
|
||||
- echo (package.json)
|
||||
deno eval 'console.log(1)'
|
||||
- bin (package.json)
|
||||
@denotest/bin hi && cli-esm testing this out && npx cli-cjs test
|
10
cli/tests/testdata/task/package_json/package.json
vendored
Normal file
10
cli/tests/testdata/task/package_json/package.json
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"scripts": {
|
||||
"echo": "deno eval 'console.log(1)'",
|
||||
"bin": "@denotest/bin hi && cli-esm testing this out && npx cli-cjs test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@denotest/bin": "0.5",
|
||||
"other": "npm:@denotest/bin@1.0"
|
||||
}
|
||||
}
|
|
@ -8,18 +8,16 @@ use crate::util::fs::canonicalize_path;
|
|||
use deno_core::anyhow::bail;
|
||||
use deno_core::anyhow::Context;
|
||||
use deno_core::error::AnyError;
|
||||
use std::collections::BTreeMap;
|
||||
use deno_core::futures;
|
||||
use deno_core::futures::future::LocalBoxFuture;
|
||||
use deno_graph::npm::NpmPackageNv;
|
||||
use deno_task_shell::ExecuteResult;
|
||||
use deno_task_shell::ShellCommand;
|
||||
use deno_task_shell::ShellCommandContext;
|
||||
use indexmap::IndexMap;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn print_available_tasks(tasks_config: BTreeMap<String, String>) {
|
||||
eprintln!("{}", colors::green("Available tasks:"));
|
||||
|
||||
for name in tasks_config.keys() {
|
||||
eprintln!("- {}", colors::cyan(name));
|
||||
eprintln!(" {}", tasks_config[name])
|
||||
}
|
||||
}
|
||||
use std::rc::Rc;
|
||||
|
||||
pub async fn execute_script(
|
||||
flags: Flags,
|
||||
|
@ -27,62 +25,209 @@ pub async fn execute_script(
|
|||
) -> Result<i32, AnyError> {
|
||||
let ps = ProcState::build(flags).await?;
|
||||
let tasks_config = ps.options.resolve_tasks_config()?;
|
||||
let config_file_url = ps.options.maybe_config_file_specifier().unwrap();
|
||||
let config_file_path = if config_file_url.scheme() == "file" {
|
||||
config_file_url.to_file_path().unwrap()
|
||||
} else {
|
||||
bail!("Only local configuration files are supported")
|
||||
};
|
||||
let maybe_package_json = ps.options.maybe_package_json();
|
||||
let package_json_scripts = maybe_package_json
|
||||
.as_ref()
|
||||
.and_then(|p| p.scripts.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
if task_flags.task.is_empty() {
|
||||
print_available_tasks(tasks_config);
|
||||
return Ok(1);
|
||||
}
|
||||
|
||||
let cwd = match task_flags.cwd {
|
||||
Some(path) => canonicalize_path(&PathBuf::from(path))?,
|
||||
None => config_file_path.parent().unwrap().to_owned(),
|
||||
};
|
||||
let task_name = task_flags.task;
|
||||
let maybe_script = tasks_config.get(&task_name);
|
||||
|
||||
if let Some(script) = maybe_script {
|
||||
let additional_args = ps
|
||||
.options
|
||||
.argv()
|
||||
.iter()
|
||||
// surround all the additional arguments in double quotes
|
||||
// and santize any command substition
|
||||
.map(|a| format!("\"{}\"", a.replace('"', "\\\"").replace('$', "\\$")))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let script = format!("{script} {additional_args}");
|
||||
let script = script.trim();
|
||||
log::info!(
|
||||
"{} {} {}",
|
||||
colors::green("Task"),
|
||||
colors::cyan(&task_name),
|
||||
script,
|
||||
);
|
||||
let seq_list = deno_task_shell::parser::parse(script)
|
||||
.with_context(|| format!("Error parsing script '{task_name}'."))?;
|
||||
|
||||
// get the starting env vars (the PWD env var will be set by deno_task_shell)
|
||||
let mut env_vars = std::env::vars().collect::<HashMap<String, String>>();
|
||||
const INIT_CWD_NAME: &str = "INIT_CWD";
|
||||
if !env_vars.contains_key(INIT_CWD_NAME) {
|
||||
if let Ok(cwd) = std::env::current_dir() {
|
||||
// if not set, set an INIT_CWD env var that has the cwd
|
||||
env_vars
|
||||
.insert(INIT_CWD_NAME.to_string(), cwd.to_string_lossy().to_string());
|
||||
}
|
||||
let task_name = match &task_flags.task {
|
||||
Some(task) => task,
|
||||
None => {
|
||||
print_available_tasks(&tasks_config, &package_json_scripts);
|
||||
return Ok(1);
|
||||
}
|
||||
};
|
||||
|
||||
let exit_code = deno_task_shell::execute(seq_list, env_vars, &cwd).await;
|
||||
if let Some(script) = tasks_config.get(task_name) {
|
||||
let config_file_url = ps.options.maybe_config_file_specifier().unwrap();
|
||||
let config_file_path = if config_file_url.scheme() == "file" {
|
||||
config_file_url.to_file_path().unwrap()
|
||||
} else {
|
||||
bail!("Only local configuration files are supported")
|
||||
};
|
||||
let cwd = match task_flags.cwd {
|
||||
Some(path) => canonicalize_path(&PathBuf::from(path))?,
|
||||
None => config_file_path.parent().unwrap().to_owned(),
|
||||
};
|
||||
let script = get_script_with_args(script, &ps);
|
||||
output_task(task_name, &script);
|
||||
let seq_list = deno_task_shell::parser::parse(&script)
|
||||
.with_context(|| format!("Error parsing script '{task_name}'."))?;
|
||||
let env_vars = collect_env_vars();
|
||||
let exit_code =
|
||||
deno_task_shell::execute(seq_list, env_vars, &cwd, Default::default())
|
||||
.await;
|
||||
Ok(exit_code)
|
||||
} else if let Some(script) = package_json_scripts.get(task_name) {
|
||||
let cwd = match task_flags.cwd {
|
||||
Some(path) => canonicalize_path(&PathBuf::from(path))?,
|
||||
None => maybe_package_json
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.to_owned(),
|
||||
};
|
||||
let script = get_script_with_args(script, &ps);
|
||||
output_task(task_name, &script);
|
||||
let seq_list = deno_task_shell::parser::parse(&script)
|
||||
.with_context(|| format!("Error parsing script '{task_name}'."))?;
|
||||
let npx_commands = resolve_npm_commands(&ps)?;
|
||||
let env_vars = collect_env_vars();
|
||||
let exit_code =
|
||||
deno_task_shell::execute(seq_list, env_vars, &cwd, npx_commands).await;
|
||||
Ok(exit_code)
|
||||
} else {
|
||||
eprintln!("Task not found: {task_name}");
|
||||
print_available_tasks(tasks_config);
|
||||
print_available_tasks(&tasks_config, &package_json_scripts);
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_script_with_args(script: &str, ps: &ProcState) -> String {
|
||||
let additional_args = ps
|
||||
.options
|
||||
.argv()
|
||||
.iter()
|
||||
// surround all the additional arguments in double quotes
|
||||
// and santize any command substition
|
||||
.map(|a| format!("\"{}\"", a.replace('"', "\\\"").replace('$', "\\$")))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let script = format!("{script} {additional_args}");
|
||||
script.trim().to_owned()
|
||||
}
|
||||
|
||||
fn output_task(task_name: &str, script: &str) {
|
||||
log::info!(
|
||||
"{} {} {}",
|
||||
colors::green("Task"),
|
||||
colors::cyan(&task_name),
|
||||
script,
|
||||
);
|
||||
}
|
||||
|
||||
fn collect_env_vars() -> HashMap<String, String> {
|
||||
// get the starting env vars (the PWD env var will be set by deno_task_shell)
|
||||
let mut env_vars = std::env::vars().collect::<HashMap<String, String>>();
|
||||
const INIT_CWD_NAME: &str = "INIT_CWD";
|
||||
if !env_vars.contains_key(INIT_CWD_NAME) {
|
||||
if let Ok(cwd) = std::env::current_dir() {
|
||||
// if not set, set an INIT_CWD env var that has the cwd
|
||||
env_vars
|
||||
.insert(INIT_CWD_NAME.to_string(), cwd.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
env_vars
|
||||
}
|
||||
|
||||
fn print_available_tasks(
|
||||
// order can be important, so these use an index map
|
||||
tasks_config: &IndexMap<String, String>,
|
||||
package_json_scripts: &IndexMap<String, String>,
|
||||
) {
|
||||
eprintln!("{}", colors::green("Available tasks:"));
|
||||
|
||||
let mut had_task = false;
|
||||
for (is_deno, (key, value)) in tasks_config.iter().map(|e| (true, e)).chain(
|
||||
package_json_scripts
|
||||
.iter()
|
||||
.filter(|(key, _)| !tasks_config.contains_key(*key))
|
||||
.map(|e| (false, e)),
|
||||
) {
|
||||
eprintln!(
|
||||
"- {}{}",
|
||||
colors::cyan(key),
|
||||
if is_deno {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!(" {}", colors::italic_gray("(package.json)"))
|
||||
}
|
||||
);
|
||||
eprintln!(" {value}");
|
||||
had_task = true;
|
||||
}
|
||||
if !had_task {
|
||||
eprintln!(" {}", colors::red("No tasks found in configuration file"));
|
||||
}
|
||||
}
|
||||
|
||||
struct NpxCommand;
|
||||
|
||||
impl ShellCommand for NpxCommand {
|
||||
fn execute(
|
||||
&self,
|
||||
mut context: ShellCommandContext,
|
||||
) -> LocalBoxFuture<'static, ExecuteResult> {
|
||||
if let Some(first_arg) = context.args.get(0).cloned() {
|
||||
if let Some(command) = context.state.resolve_command(&first_arg) {
|
||||
let context = ShellCommandContext {
|
||||
args: context.args.iter().skip(1).cloned().collect::<Vec<_>>(),
|
||||
..context
|
||||
};
|
||||
command.execute(context)
|
||||
} else {
|
||||
let _ = context
|
||||
.stderr
|
||||
.write_line(&format!("npx: could not resolve command '{first_arg}'"));
|
||||
Box::pin(futures::future::ready(ExecuteResult::from_exit_code(1)))
|
||||
}
|
||||
} else {
|
||||
let _ = context.stderr.write_line("npx: missing command");
|
||||
Box::pin(futures::future::ready(ExecuteResult::from_exit_code(1)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct NpmPackageBinCommand {
|
||||
name: String,
|
||||
npm_package: NpmPackageNv,
|
||||
}
|
||||
|
||||
impl ShellCommand for NpmPackageBinCommand {
|
||||
fn execute(
|
||||
&self,
|
||||
context: ShellCommandContext,
|
||||
) -> LocalBoxFuture<'static, ExecuteResult> {
|
||||
let mut args = vec![
|
||||
"run".to_string(),
|
||||
"-A".to_string(),
|
||||
if self.npm_package.name == self.name {
|
||||
format!("npm:{}", self.npm_package)
|
||||
} else {
|
||||
format!("npm:{}/{}", self.npm_package, self.name)
|
||||
},
|
||||
];
|
||||
args.extend(context.args);
|
||||
let executable_command =
|
||||
deno_task_shell::ExecutableCommand::new("deno".to_string());
|
||||
executable_command.execute(ShellCommandContext { args, ..context })
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_npm_commands(
|
||||
ps: &ProcState,
|
||||
) -> Result<HashMap<String, Rc<dyn ShellCommand>>, AnyError> {
|
||||
let mut result = HashMap::new();
|
||||
let snapshot = ps.npm_resolver.snapshot();
|
||||
for id in snapshot.top_level_packages() {
|
||||
let bin_commands =
|
||||
crate::node::node_resolve_binary_commands(&id.nv, &ps.npm_resolver)?;
|
||||
for bin_command in bin_commands {
|
||||
result.insert(
|
||||
bin_command.to_string(),
|
||||
Rc::new(NpmPackageBinCommand {
|
||||
name: bin_command,
|
||||
npm_package: id.nv.clone(),
|
||||
}) as Rc<dyn ShellCommand>,
|
||||
);
|
||||
}
|
||||
}
|
||||
if !result.contains_key("npx") {
|
||||
result.insert("npx".to_string(), Rc::new(NpxCommand));
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
|
|
@ -457,7 +457,6 @@ async fn create_main_worker_internal(
|
|||
&pkg_nv,
|
||||
package_ref.sub_path.as_deref(),
|
||||
&ps.npm_resolver,
|
||||
&mut PermissionsContainer::allow_all(),
|
||||
)?;
|
||||
let is_main_cjs =
|
||||
matches!(node_resolution, node::NodeResolution::CommonJs(_));
|
||||
|
|
|
@ -17,6 +17,7 @@ path = "lib.rs"
|
|||
deno_core.workspace = true
|
||||
digest = { version = "0.10.5", features = ["core-api", "std"] }
|
||||
idna = "0.3.0"
|
||||
indexmap.workspace = true
|
||||
md-5 = "0.10.5"
|
||||
md4 = "0.10.2"
|
||||
once_cell.workspace = true
|
||||
|
|
|
@ -4,12 +4,14 @@ use crate::NodeModuleKind;
|
|||
use crate::NodePermissions;
|
||||
|
||||
use super::RequireNpmResolver;
|
||||
|
||||
use deno_core::anyhow;
|
||||
use deno_core::anyhow::bail;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::serde_json;
|
||||
use deno_core::serde_json::Map;
|
||||
use deno_core::serde_json::Value;
|
||||
use indexmap::IndexMap;
|
||||
use serde::Serialize;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
@ -35,6 +37,7 @@ pub struct PackageJson {
|
|||
pub types: Option<String>,
|
||||
pub dependencies: Option<HashMap<String, String>>,
|
||||
pub dev_dependencies: Option<HashMap<String, String>>,
|
||||
pub scripts: Option<IndexMap<String, String>>,
|
||||
}
|
||||
|
||||
impl PackageJson {
|
||||
|
@ -53,6 +56,7 @@ impl PackageJson {
|
|||
types: None,
|
||||
dependencies: None,
|
||||
dev_dependencies: None,
|
||||
scripts: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,6 +148,10 @@ impl PackageJson {
|
|||
}
|
||||
});
|
||||
|
||||
let scripts: Option<IndexMap<String, String>> = package_json
|
||||
.get("scripts")
|
||||
.and_then(|d| serde_json::from_value(d.to_owned()).ok());
|
||||
|
||||
// Ignore unknown types for forwards compatibility
|
||||
let typ = if let Some(t) = type_val {
|
||||
if let Some(t) = t.as_str() {
|
||||
|
@ -179,6 +187,7 @@ impl PackageJson {
|
|||
bin,
|
||||
dependencies,
|
||||
dev_dependencies,
|
||||
scripts,
|
||||
};
|
||||
|
||||
CACHE.with(|cache| {
|
||||
|
|
|
@ -1929,7 +1929,7 @@ pub struct CheckOutputIntegrationTest<'a> {
|
|||
/// the test creates files in the testdata directory (ex. a node_modules folder)
|
||||
pub copy_temp_dir: Option<&'a str>,
|
||||
/// Relative to "testdata" directory
|
||||
pub maybe_cwd: Option<&'a str>,
|
||||
pub cwd: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> CheckOutputIntegrationTest<'a> {
|
||||
|
@ -1970,7 +1970,7 @@ impl<'a> CheckOutputIntegrationTest<'a> {
|
|||
let mut command = deno_cmd_with_deno_dir(&deno_dir);
|
||||
let cwd = if self.temp_cwd {
|
||||
deno_dir.path().to_owned()
|
||||
} else if let Some(cwd_) = &self.maybe_cwd {
|
||||
} else if let Some(cwd_) = &self.cwd {
|
||||
testdata_dir.join(cwd_)
|
||||
} else {
|
||||
testdata_dir.clone()
|
||||
|
|
Loading…
Add table
Reference in a new issue