diff --git a/crates/cli/tests/run_node_test.rs b/crates/cli/tests/run_node_test.rs index 36139aad093..195ed318f70 100644 --- a/crates/cli/tests/run_node_test.rs +++ b/crates/cli/tests/run_node_test.rs @@ -1281,6 +1281,7 @@ mod affected_files { let output = assert.output(); assert!(predicate::str::contains("Args: .\n").eval(&output)); + assert!(predicate::str::contains("Env: .\n").eval(&output)); } #[test] @@ -1297,14 +1298,40 @@ mod affected_files { let output = assert.output(); if cfg!(windows) { - assert!(predicate::str::contains("Args: .\\input1.js .\\input2.js").eval(&output)); + assert!(predicate::str::contains("Args: .\\input1.js .\\input2.js\n").eval(&output)); + assert!(predicate::str::contains("Env: .\\input1.js,.\\input2.js\n").eval(&output)); } else { - assert!(predicate::str::contains("Args: ./input1.js ./input2.js").eval(&output)); + assert!(predicate::str::contains("Args: ./input1.js ./input2.js\n").eval(&output)); + assert!(predicate::str::contains("Env: ./input1.js,./input2.js\n").eval(&output)); } } #[test] - fn sets_env_var() { + fn sets_args_only() { + let sandbox = node_sandbox(); + + sandbox.create_file("base/input1.js", ""); + sandbox.create_file("base/input2.js", ""); + + let assert = sandbox.run_moon(|cmd| { + cmd.arg("run") + .arg("node:affectedFilesArgs") + .arg("--affected"); + }); + + let output = assert.output(); + + if cfg!(windows) { + assert!(predicate::str::contains("Args: .\\input1.js .\\input2.js\n").eval(&output)); + assert!(predicate::str::contains("Env: \n").eval(&output)); + } else { + assert!(predicate::str::contains("Args: ./input1.js ./input2.js\n").eval(&output)); + assert!(predicate::str::contains("Env: \n").eval(&output)); + } + } + + #[test] + fn sets_env_var_only() { let sandbox = node_sandbox(); sandbox.create_file("base/input1.js", ""); @@ -1318,8 +1345,12 @@ mod affected_files { let output = assert.output(); - assert!( - predicate::str::contains("MOON_AFFECTED_FILES=./input1.js,./input2.js").eval(&output) - ); + if cfg!(windows) { + assert!(predicate::str::contains("Args: \n").eval(&output)); + assert!(predicate::str::contains("Env: .\\input1.js,.\\input2.js\n").eval(&output)); + } else { + assert!(predicate::str::contains("Args: \n").eval(&output)); + assert!(predicate::str::contains("Env: ./input1.js,./input2.js\n").eval(&output)); + } } } diff --git a/crates/cli/tests/run_system_test.rs b/crates/cli/tests/run_system_test.rs index 16cb49ee7c2..a52ebedafc1 100644 --- a/crates/cli/tests/run_system_test.rs +++ b/crates/cli/tests/run_system_test.rs @@ -266,6 +266,7 @@ mod unix { let output = assert.output(); assert!(predicate::str::contains("Args: .\n").eval(&output)); + assert!(predicate::str::contains("Env: .\n").eval(&output)); } #[test] @@ -281,10 +282,29 @@ mod unix { let output = assert.output(); assert!(predicate::str::contains("Args: ./input1.txt ./input2.txt").eval(&output)); + assert!(predicate::str::contains("Env: ./input1.txt,./input2.txt").eval(&output)); } #[test] - fn sets_env_var() { + fn sets_args_only() { + let sandbox = system_sandbox(); + + sandbox.create_file("unix/input1.txt", ""); + sandbox.create_file("unix/input2.txt", ""); + + let assert = sandbox.run_moon(|cmd| { + cmd.arg("run") + .arg("unix:affectedFilesArgs") + .arg("--affected"); + }); + let output = assert.output(); + + assert!(predicate::str::contains("Args: ./input1.txt ./input2.txt\n").eval(&output)); + assert!(predicate::str::contains("Env: \n").eval(&output)); + } + + #[test] + fn sets_env_var_only() { let sandbox = system_sandbox(); sandbox.create_file("unix/input1.txt", ""); @@ -297,10 +317,8 @@ mod unix { }); let output = assert.output(); - assert!( - predicate::str::contains("MOON_AFFECTED_FILES=./input1.txt,./input2.txt") - .eval(&output) - ); + assert!(predicate::str::contains("Args: \n").eval(&output)); + assert!(predicate::str::contains("Env: ./input1.txt,./input2.txt\n").eval(&output)); } } } diff --git a/crates/cli/tests/snapshots/run_system_test__unix__caching__creates_run_state_cache.snap b/crates/cli/tests/snapshots/run_system_test__unix__caching__creates_run_state_cache.snap index 16c43e89ee3..82a9e1754fd 100644 --- a/crates/cli/tests/snapshots/run_system_test__unix__caching__creates_run_state_cache.snap +++ b/crates/cli/tests/snapshots/run_system_test__unix__caching__creates_run_state_cache.snap @@ -1,6 +1,6 @@ --- source: crates/cli/tests/run_system_test.rs -assertion_line: 236 +assertion_line: 233 expression: "fs::read_to_string(sandbox.path().join(format!(\".moon/cache/hashes/{}.json\",\n state.hash))).unwrap()" --- [ @@ -12,13 +12,14 @@ expression: "fs::read_to_string(sandbox.path().join(format!(\".moon/cache/hashes "deps": {}, "envVars": {}, "inputs": { + "unix/affectedFiles.sh": "4cc0d80d6a9d4dd55481467de96476618ef131ae", "unix/args.sh": "ac54a54b387de2281f14259a6e974688f6465c57", "unix/cwd.sh": "9a5ea47dd8809abadf1c61817ffe6be07883af0d", "unix/envVars.sh": "63cd12999af89a6823e908f32f2a08bf7f27182e", "unix/envVarsMoon.sh": "357c556827eaccb9fc7cc96fe882c073068b112f", "unix/exitNonZero.sh": "73cb4653d441d323178c51ea1ba9c4d82e169a91", "unix/exitZero.sh": "1ad12d878a9eac999c1bfd3b033158e22f147918", - "unix/moon.yml": "a0b293ab4cc21ae2241b36f3afaec39a89ce47c0", + "unix/moon.yml": "111d048aee3d1783f3d32cee5dc49f90b2b3ea12", "unix/outputs.sh": "9eb7ff5d58f831c346d39e41f98aac3f937a7e1d", "unix/standard.sh": "5d6a286c841c122f2dcc0df70cbf7cc17629d8c0" }, diff --git a/crates/cli/tests/snapshots/run_system_test__unix__handles_ls.snap b/crates/cli/tests/snapshots/run_system_test__unix__handles_ls.snap index 9f28060014a..1391fb77aae 100644 --- a/crates/cli/tests/snapshots/run_system_test__unix__handles_ls.snap +++ b/crates/cli/tests/snapshots/run_system_test__unix__handles_ls.snap @@ -1,9 +1,10 @@ --- source: crates/cli/tests/run_system_test.rs -assertion_line: 33 -expression: get_assert_output(&assert) +assertion_line: 51 +expression: assert.output() --- ▪▪▪▪ unix:ls +affectedFiles.sh args.sh cwd.sh envVars.sh diff --git a/crates/core/config/src/project/task_options.rs b/crates/core/config/src/project/task_options.rs index 7c3de7b5769..41812580b28 100644 --- a/crates/core/config/src/project/task_options.rs +++ b/crates/core/config/src/project/task_options.rs @@ -1,32 +1,53 @@ -use crate::validators::validate_child_relative_path; +use crate::{errors::create_validation_error, validators::validate_child_relative_path}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use validator::{Validate, ValidationError}; -fn validate_env_file(file: &TaskOptionEnvFile) -> Result<(), ValidationError> { - if let TaskOptionEnvFile::File(path) = file { - validate_child_relative_path("envFile", path)?; +fn validate_affected_files(file: &TaskOptionAffectedFilesConfig) -> Result<(), ValidationError> { + if let TaskOptionAffectedFilesConfig::Value(value) = file { + if value != "args" && value != "env" { + return Err(create_validation_error( + "invalid_value", + "options.affectedFiles", + "expected `args`, `env`, or a boolean", + )); + } + } + + Ok(()) +} + +fn validate_env_file(file: &TaskOptionEnvFileConfig) -> Result<(), ValidationError> { + if let TaskOptionEnvFileConfig::File(path) = file { + validate_child_relative_path("options.envFile", path)?; } Ok(()) } +#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] +#[serde(untagged, expecting = "expected `args`, `env`, or a boolean")] +pub enum TaskOptionAffectedFilesConfig { + Enabled(bool), + Value(String), +} + #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] #[serde( untagged, expecting = "expected a boolean or a relative file system path" )] -pub enum TaskOptionEnvFile { +pub enum TaskOptionEnvFileConfig { Enabled(bool), File(String), } -impl TaskOptionEnvFile { +impl TaskOptionEnvFileConfig { pub fn to_option(&self) -> Option { match self { - TaskOptionEnvFile::Enabled(true) => Some(".env".to_owned()), - TaskOptionEnvFile::Enabled(false) => None, - TaskOptionEnvFile::File(path) => Some(path.to_owned()), + TaskOptionEnvFileConfig::Enabled(true) => Some(".env".to_owned()), + TaskOptionEnvFileConfig::Enabled(false) => None, + TaskOptionEnvFileConfig::File(path) => Some(path.to_owned()), } } } @@ -54,12 +75,13 @@ pub enum TaskOutputStyle { #[schemars(default)] #[serde(default, rename_all = "camelCase")] pub struct TaskOptionsConfig { - pub affected_files: Option, + #[validate(custom = "validate_affected_files")] + pub affected_files: Option, pub cache: Option, #[validate(custom = "validate_env_file")] - pub env_file: Option, + pub env_file: Option, pub merge_args: Option, diff --git a/crates/core/config/tests/tasks_test.rs b/crates/core/config/tests/tasks_test.rs index bbca478549b..1cef854ae1a 100644 --- a/crates/core/config/tests/tasks_test.rs +++ b/crates/core/config/tests/tasks_test.rs @@ -587,6 +587,8 @@ platform: whatisthis } mod options { + use super::*; + #[test] #[should_panic( expected = "invalid type: found unsigned int `123`, expected struct TaskOptionsConfig for key \"default.options\"" @@ -690,4 +692,121 @@ options: // Ok(()) // }); // } + + mod affected_files { + + #[test] + #[should_panic( + expected = "expected `args`, `env`, or a boolean for key \"default.options.affectedFiles\"" + )] + fn invalid_type() { + figment::Jail::expect_with(|jail| { + jail.create_file( + super::CONFIG_FILENAME, + r#" +command: foo +options: + affectedFiles: 123 +"#, + )?; + + super::load_jailed_config()?; + + Ok(()) + }); + } + + #[test] + #[should_panic( + expected = "expected `args`, `env`, or a boolean for key \"default.options.affectedFiles\"" + )] + fn invalid_value() { + figment::Jail::expect_with(|jail| { + jail.create_file( + super::CONFIG_FILENAME, + r#" +command: foo +options: + affectedFiles: unknown +"#, + )?; + + super::load_jailed_config()?; + + Ok(()) + }); + } + + #[test] + fn supports_bool_true() { + figment::Jail::expect_with(|jail| { + jail.create_file( + super::CONFIG_FILENAME, + r#" +command: foo +options: + affectedFiles: true +"#, + )?; + + super::load_jailed_config()?; + + Ok(()) + }); + } + + #[test] + fn supports_bool_false() { + figment::Jail::expect_with(|jail| { + jail.create_file( + super::CONFIG_FILENAME, + r#" +command: foo +options: + affectedFiles: false +"#, + )?; + + super::load_jailed_config()?; + + Ok(()) + }); + } + + #[test] + fn supports_args() { + figment::Jail::expect_with(|jail| { + jail.create_file( + super::CONFIG_FILENAME, + r#" +command: foo +options: + affectedFiles: args +"#, + )?; + + super::load_jailed_config()?; + + Ok(()) + }); + } + + #[test] + fn supports_env() { + figment::Jail::expect_with(|jail| { + jail.create_file( + super::CONFIG_FILENAME, + r#" +command: foo +options: + affectedFiles: env +"#, + )?; + + super::load_jailed_config()?; + + Ok(()) + }); + } + } } diff --git a/crates/core/runner/src/actions/run_target.rs b/crates/core/runner/src/actions/run_target.rs index 17a3d426a0c..bd3192a7ed3 100644 --- a/crates/core/runner/src/actions/run_target.rs +++ b/crates/core/runner/src/actions/run_target.rs @@ -12,7 +12,9 @@ use moon_project::Project; use moon_project_graph::ProjectGraph; use moon_runner_context::RunnerContext; use moon_system_platform::actions as system_actions; -use moon_task::{Target, TargetError, TargetProjectScope, Task, TaskError}; +use moon_task::{ + Target, TargetError, TargetProjectScope, Task, TaskError, TaskOptionAffectedFiles, +}; use moon_terminal::label_checkpoint; use moon_terminal::Checkpoint; use moon_utils::{ @@ -263,30 +265,43 @@ impl<'a> TargetRunner<'a> { } // Affected files (must be last args) - if self.task.options.affected_files { - if context.affected_only { - let mut affected_files = self - .task - .get_affected_files(&context.touched_files, &self.project.root)?; + if let Some(check_affected) = &self.task.options.affected_files { + let mut affected_files = if context.affected_only { + self.task + .get_affected_files(&context.touched_files, &self.project.root)? + } else { + Vec::with_capacity(0) + }; - if affected_files.is_empty() { - command.arg_if_missing("."); - } else { - affected_files.sort(); + affected_files.sort(); - command.env( - "MOON_AFFECTED_FILES", + if matches!( + check_affected, + TaskOptionAffectedFiles::Env | TaskOptionAffectedFiles::Both + ) { + command.env( + "MOON_AFFECTED_FILES", + if affected_files.is_empty() { + ".".into() + } else { affected_files .iter() .map(|f| f.to_string_lossy()) .collect::>() - .join(","), - ); + .join(",") + }, + ); + } + if matches!( + check_affected, + TaskOptionAffectedFiles::Args | TaskOptionAffectedFiles::Both + ) { + if affected_files.is_empty() { + command.arg_if_missing("."); + } else { command.args(affected_files); } - } else { - command.arg_if_missing("."); } } diff --git a/crates/core/task/src/lib.rs b/crates/core/task/src/lib.rs index cc422fd746d..16395511c93 100644 --- a/crates/core/task/src/lib.rs +++ b/crates/core/task/src/lib.rs @@ -2,6 +2,7 @@ mod errors; mod file_group; mod target; mod task; +mod task_options; pub mod test; mod token; mod types; @@ -10,6 +11,7 @@ pub use errors::*; pub use file_group::FileGroup; pub use moon_config::{PlatformType, TargetID, TaskConfig, TaskID, TaskOptionsConfig}; pub use target::{Target, TargetProjectScope}; -pub use task::{Task, TaskOptions}; +pub use task::*; +pub use task_options::*; pub use token::{ResolverData, ResolverType, TokenResolver, TokenType}; pub use types::*; diff --git a/crates/core/task/src/task.rs b/crates/core/task/src/task.rs index 647c14312b9..73dae9ae40f 100644 --- a/crates/core/task/src/task.rs +++ b/crates/core/task/src/task.rs @@ -1,10 +1,11 @@ use crate::errors::{TargetError, TaskError}; use crate::target::{Target, TargetProjectScope}; +use crate::task_options::TaskOptions; use crate::token::{ResolverData, TokenResolver}; use crate::types::{EnvVars, TouchedFilePaths}; use moon_config::{ FileGlob, FilePath, InputValue, PlatformType, ProjectID, TaskCommandArgs, TaskConfig, - TaskMergeStrategy, TaskOptionEnvFile, TaskOptionsConfig, TaskOutputStyle, + TaskMergeStrategy, }; use moon_logger::{color, debug, trace, Logable}; use moon_utils::{glob, is_ci, path, regex::ENV_VAR, string_vec}; @@ -28,149 +29,6 @@ pub enum TaskType { Test, } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct TaskOptions { - pub affected_files: bool, - - pub cache: bool, - - pub env_file: Option, - - pub merge_args: TaskMergeStrategy, - - pub merge_deps: TaskMergeStrategy, - - pub merge_env: TaskMergeStrategy, - - pub merge_inputs: TaskMergeStrategy, - - pub merge_outputs: TaskMergeStrategy, - - pub output_style: Option, - - pub retry_count: u8, - - pub run_deps_in_parallel: bool, - - pub run_in_ci: bool, - - pub run_from_workspace_root: bool, -} - -impl Default for TaskOptions { - fn default() -> Self { - TaskOptions { - affected_files: false, - cache: true, - env_file: None, - merge_args: TaskMergeStrategy::Append, - merge_deps: TaskMergeStrategy::Append, - merge_env: TaskMergeStrategy::Append, - merge_inputs: TaskMergeStrategy::Append, - merge_outputs: TaskMergeStrategy::Append, - output_style: None, - retry_count: 0, - run_deps_in_parallel: true, - run_in_ci: true, - run_from_workspace_root: false, - } - } -} - -impl TaskOptions { - pub fn merge(&mut self, config: &TaskOptionsConfig) { - if let Some(affected_files) = &config.affected_files { - self.affected_files = *affected_files; - } - - if let Some(env_file) = &config.env_file { - self.env_file = env_file.to_option(); - } - - if let Some(merge_args) = &config.merge_args { - self.merge_args = merge_args.clone(); - } - - if let Some(merge_deps) = &config.merge_deps { - self.merge_deps = merge_deps.clone(); - } - - if let Some(merge_env) = &config.merge_env { - self.merge_env = merge_env.clone(); - } - - if let Some(merge_inputs) = &config.merge_inputs { - self.merge_inputs = merge_inputs.clone(); - } - - if let Some(merge_outputs) = &config.merge_outputs { - self.merge_outputs = merge_outputs.clone(); - } - - if let Some(output_style) = &config.output_style { - self.output_style = Some(output_style.clone()); - } - - if let Some(retry_count) = &config.retry_count { - self.retry_count = *retry_count; - } - - if let Some(run_deps_in_parallel) = &config.run_deps_in_parallel { - self.run_deps_in_parallel = *run_deps_in_parallel; - } - - if let Some(run_in_ci) = &config.run_in_ci { - self.run_in_ci = *run_in_ci; - } - - if let Some(run_from_workspace_root) = &config.run_from_workspace_root { - self.run_from_workspace_root = *run_from_workspace_root; - } - } - - pub fn to_config(&self) -> TaskOptionsConfig { - let default_options = TaskOptions::default(); - let mut config = TaskOptionsConfig::default(); - - // Skip merge options until we need them - - if self.affected_files != default_options.affected_files { - config.affected_files = Some(self.affected_files); - } - - if let Some(env_file) = &self.env_file { - config.env_file = Some(if env_file == ".env" { - TaskOptionEnvFile::Enabled(true) - } else { - TaskOptionEnvFile::File(env_file.clone()) - }); - } - - if let Some(output_style) = &self.output_style { - config.output_style = Some(output_style.clone()); - } - - if self.run_deps_in_parallel != default_options.run_deps_in_parallel { - config.run_deps_in_parallel = Some(self.run_deps_in_parallel); - } - - if self.retry_count != default_options.retry_count { - config.retry_count = Some(self.retry_count); - } - - if self.run_in_ci != default_options.run_in_ci { - config.run_in_ci = Some(self.run_in_ci); - } - - if self.run_from_workspace_root != default_options.run_from_workspace_root { - config.run_from_workspace_root = Some(self.run_from_workspace_root); - } - - config - } -} - #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Task { @@ -244,25 +102,7 @@ impl Task { input_globs: FxHashSet::default(), input_paths: FxHashSet::default(), log_target, - options: TaskOptions { - affected_files: cloned_options.affected_files.unwrap_or_default(), - cache: cloned_options.cache.unwrap_or(!is_local), - env_file: cloned_options - .env_file - .map(|env_file| env_file.to_option().unwrap()), - merge_args: cloned_options.merge_args.unwrap_or_default(), - merge_deps: cloned_options.merge_deps.unwrap_or_default(), - merge_env: cloned_options.merge_env.unwrap_or_default(), - merge_inputs: cloned_options.merge_inputs.unwrap_or_default(), - merge_outputs: cloned_options.merge_outputs.unwrap_or_default(), - output_style: cloned_options - .output_style - .or_else(|| is_local.then_some(TaskOutputStyle::Stream)), - retry_count: cloned_options.retry_count.unwrap_or_default(), - run_deps_in_parallel: cloned_options.run_deps_in_parallel.unwrap_or(true), - run_in_ci: cloned_options.run_in_ci.unwrap_or(!is_local), - run_from_workspace_root: cloned_options.run_from_workspace_root.unwrap_or_default(), - }, + options: TaskOptions::from_config(cloned_options, is_local), outputs: cloned_config.outputs.unwrap_or_default(), output_paths: FxHashSet::default(), platform: cloned_config.platform, diff --git a/crates/core/task/src/task_options.rs b/crates/core/task/src/task_options.rs new file mode 100644 index 00000000000..bb439d12aa6 --- /dev/null +++ b/crates/core/task/src/task_options.rs @@ -0,0 +1,204 @@ +use moon_config::{ + TaskMergeStrategy, TaskOptionAffectedFilesConfig, TaskOptionEnvFileConfig, TaskOptionsConfig, + TaskOutputStyle, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum TaskOptionAffectedFiles { + Args, + Env, + Both, +} + +impl TaskOptionAffectedFiles { + pub fn from_config(config: &TaskOptionAffectedFilesConfig) -> Option { + match config { + TaskOptionAffectedFilesConfig::Enabled(false) => None, + TaskOptionAffectedFilesConfig::Enabled(true) => Some(TaskOptionAffectedFiles::Both), + TaskOptionAffectedFilesConfig::Value(value) => { + if value == "args" { + Some(TaskOptionAffectedFiles::Args) + } else { + Some(TaskOptionAffectedFiles::Env) + } + } + } + } + + pub fn to_config(&self) -> TaskOptionAffectedFilesConfig { + match self { + TaskOptionAffectedFiles::Args => TaskOptionAffectedFilesConfig::Value("args".into()), + TaskOptionAffectedFiles::Env => TaskOptionAffectedFilesConfig::Value("env".into()), + TaskOptionAffectedFiles::Both => TaskOptionAffectedFilesConfig::Enabled(true), + } + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TaskOptions { + pub affected_files: Option, + + pub cache: bool, + + pub env_file: Option, + + pub merge_args: TaskMergeStrategy, + + pub merge_deps: TaskMergeStrategy, + + pub merge_env: TaskMergeStrategy, + + pub merge_inputs: TaskMergeStrategy, + + pub merge_outputs: TaskMergeStrategy, + + pub output_style: Option, + + pub retry_count: u8, + + pub run_deps_in_parallel: bool, + + pub run_in_ci: bool, + + pub run_from_workspace_root: bool, +} + +impl Default for TaskOptions { + fn default() -> Self { + TaskOptions { + affected_files: None, + cache: true, + env_file: None, + merge_args: TaskMergeStrategy::Append, + merge_deps: TaskMergeStrategy::Append, + merge_env: TaskMergeStrategy::Append, + merge_inputs: TaskMergeStrategy::Append, + merge_outputs: TaskMergeStrategy::Append, + output_style: None, + retry_count: 0, + run_deps_in_parallel: true, + run_in_ci: true, + run_from_workspace_root: false, + } + } +} + +impl TaskOptions { + pub fn merge(&mut self, config: &TaskOptionsConfig) { + if let Some(affected_files) = &config.affected_files { + self.affected_files = TaskOptionAffectedFiles::from_config(affected_files); + } + + if let Some(env_file) = &config.env_file { + self.env_file = env_file.to_option(); + } + + if let Some(merge_args) = &config.merge_args { + self.merge_args = merge_args.clone(); + } + + if let Some(merge_deps) = &config.merge_deps { + self.merge_deps = merge_deps.clone(); + } + + if let Some(merge_env) = &config.merge_env { + self.merge_env = merge_env.clone(); + } + + if let Some(merge_inputs) = &config.merge_inputs { + self.merge_inputs = merge_inputs.clone(); + } + + if let Some(merge_outputs) = &config.merge_outputs { + self.merge_outputs = merge_outputs.clone(); + } + + if let Some(output_style) = &config.output_style { + self.output_style = Some(output_style.clone()); + } + + if let Some(retry_count) = &config.retry_count { + self.retry_count = *retry_count; + } + + if let Some(run_deps_in_parallel) = &config.run_deps_in_parallel { + self.run_deps_in_parallel = *run_deps_in_parallel; + } + + if let Some(run_in_ci) = &config.run_in_ci { + self.run_in_ci = *run_in_ci; + } + + if let Some(run_from_workspace_root) = &config.run_from_workspace_root { + self.run_from_workspace_root = *run_from_workspace_root; + } + } + + pub fn from_config(config: TaskOptionsConfig, is_local: bool) -> TaskOptions { + TaskOptions { + affected_files: config + .affected_files + .map(|af| TaskOptionAffectedFiles::from_config(&af)) + .unwrap_or_default(), + cache: config.cache.unwrap_or(!is_local), + env_file: config + .env_file + .map(|env_file| env_file.to_option().unwrap()), + merge_args: config.merge_args.unwrap_or_default(), + merge_deps: config.merge_deps.unwrap_or_default(), + merge_env: config.merge_env.unwrap_or_default(), + merge_inputs: config.merge_inputs.unwrap_or_default(), + merge_outputs: config.merge_outputs.unwrap_or_default(), + output_style: config + .output_style + .or_else(|| is_local.then_some(TaskOutputStyle::Stream)), + retry_count: config.retry_count.unwrap_or_default(), + run_deps_in_parallel: config.run_deps_in_parallel.unwrap_or(true), + run_in_ci: config.run_in_ci.unwrap_or(!is_local), + run_from_workspace_root: config.run_from_workspace_root.unwrap_or_default(), + } + } + + pub fn to_config(&self) -> TaskOptionsConfig { + let default_options = TaskOptions::default(); + let mut config = TaskOptionsConfig::default(); + + // Skip merge options until we need them + + if let Some(affected_files) = &self.affected_files { + config.affected_files = Some(affected_files.to_config()); + } + + if let Some(env_file) = &self.env_file { + config.env_file = Some(if env_file == ".env" { + TaskOptionEnvFileConfig::Enabled(true) + } else { + TaskOptionEnvFileConfig::File(env_file.clone()) + }); + } + + if let Some(output_style) = &self.output_style { + config.output_style = Some(output_style.clone()); + } + + if self.run_deps_in_parallel != default_options.run_deps_in_parallel { + config.run_deps_in_parallel = Some(self.run_deps_in_parallel); + } + + if self.retry_count != default_options.retry_count { + config.retry_count = Some(self.retry_count); + } + + if self.run_in_ci != default_options.run_in_ci { + config.run_in_ci = Some(self.run_in_ci); + } + + if self.run_from_workspace_root != default_options.run_from_workspace_root { + config.run_from_workspace_root = Some(self.run_from_workspace_root); + } + + config + } +} diff --git a/crates/core/task/tests/task_test.rs b/crates/core/task/tests/task_test.rs index f51033bc4fa..ead2e3d5252 100644 --- a/crates/core/task/tests/task_test.rs +++ b/crates/core/task/tests/task_test.rs @@ -1,4 +1,4 @@ -use moon_config::{TaskCommandArgs, TaskConfig, TaskOptionEnvFile, TaskOptionsConfig}; +use moon_config::{TaskCommandArgs, TaskConfig, TaskOptionEnvFileConfig, TaskOptionsConfig}; use moon_task::test::create_expanded_task; use moon_task::{Target, Task, TaskOptions}; use moon_test_utils::{create_sandbox, get_fixtures_path}; @@ -38,7 +38,7 @@ mod from_config { assert_eq!( task.options, TaskOptions { - affected_files: false, + affected_files: None, cache: true, env_file: None, merge_args: TaskMergeStrategy::Append, @@ -131,7 +131,7 @@ mod from_config { Target::new("foo", "test").unwrap(), &TaskConfig { options: TaskOptionsConfig { - env_file: Some(TaskOptionEnvFile::Enabled(true)), + env_file: Some(TaskOptionEnvFileConfig::Enabled(true)), ..TaskOptionsConfig::default() }, ..TaskConfig::default() @@ -512,7 +512,7 @@ mod is_affected { &project_root, Some(TaskConfig { options: TaskOptionsConfig { - env_file: Some(TaskOptionEnvFile::Enabled(true)), + env_file: Some(TaskOptionEnvFileConfig::Enabled(true)), ..TaskOptionsConfig::default() }, ..TaskConfig::default() @@ -543,7 +543,7 @@ mod expand_env { &project_root, Some(TaskConfig { options: TaskOptionsConfig { - env_file: Some(TaskOptionEnvFile::Enabled(true)), + env_file: Some(TaskOptionEnvFileConfig::Enabled(true)), ..TaskOptionsConfig::default() }, ..TaskConfig::default() @@ -569,7 +569,7 @@ mod expand_env { &project_root, Some(TaskConfig { options: TaskOptionsConfig { - env_file: Some(TaskOptionEnvFile::Enabled(true)), + env_file: Some(TaskOptionEnvFileConfig::Enabled(true)), ..TaskOptionsConfig::default() }, ..TaskConfig::default() @@ -591,7 +591,7 @@ mod expand_env { &project_root, Some(TaskConfig { options: TaskOptionsConfig { - env_file: Some(TaskOptionEnvFile::Enabled(true)), + env_file: Some(TaskOptionEnvFileConfig::Enabled(true)), ..TaskOptionsConfig::default() }, ..TaskConfig::default() @@ -623,7 +623,7 @@ mod expand_env { &project_root, Some(TaskConfig { options: TaskOptionsConfig { - env_file: Some(TaskOptionEnvFile::File(".env.production".to_owned())), + env_file: Some(TaskOptionEnvFileConfig::File(".env.production".to_owned())), ..TaskOptionsConfig::default() }, ..TaskConfig::default() @@ -661,7 +661,7 @@ mod expand_env { "original".to_owned(), )])), options: TaskOptionsConfig { - env_file: Some(TaskOptionEnvFile::Enabled(true)), + env_file: Some(TaskOptionEnvFileConfig::Enabled(true)), ..TaskOptionsConfig::default() }, ..TaskConfig::default() diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index c107feeda9f..7191760ba0a 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -5,12 +5,16 @@ #### 🚀 Updates - We've rewritten our project graph to use eager-loading instead of lazy-loading to improve - performance, and to avoid mutating borrowed data across threads in Rust. We're no longer cloning - project information unnecessarily. + performance, and to avoid mutating borrowed data across threads in Rust. We're also no longer + cloning project information unnecessarily, which is a massive memory reduction boost. - We've also rewritten our dependency graph in a similar fashion, and are now able to efficiently reference data from the project graph while building the dependency chain. - You may now install the `@moonrepo/cli` package globally with pnpm and yarn. When running these - globals, the will attempt to use the binary found in the repo's node modules. + globals, moon will attempt to use the binary found in the repo's node modules. + +##### Runner + +- Added `args` and `env` as valid values for the `options.affectedFiles` task option. ## 0.20.3 diff --git a/tests/fixtures/node/base/affectedFiles.js b/tests/fixtures/node/base/affectedFiles.js new file mode 100644 index 00000000000..56600e0cd0a --- /dev/null +++ b/tests/fixtures/node/base/affectedFiles.js @@ -0,0 +1,2 @@ +console.log('Args:', process.argv.slice(2).join(' ')); +console.log('Env:', process.env.MOON_AFFECTED_FILES || ''); diff --git a/tests/fixtures/node/base/moon.yml b/tests/fixtures/node/base/moon.yml index 047ffbac424..4f05f144e38 100644 --- a/tests/fixtures/node/base/moon.yml +++ b/tests/fixtures/node/base/moon.yml @@ -65,10 +65,14 @@ tasks: command: node args: ./binExecArgs.js affectedFiles: - command: node ./args.js + command: node ./affectedFiles.js options: affectedFiles: true + affectedFilesArgs: + command: node ./affectedFiles.js + options: + affectedFiles: 'args' affectedFilesEnvVar: - command: node ./envVarsMoon.js + command: node ./affectedFiles.js options: - affectedFiles: true + affectedFiles: 'env' diff --git a/tests/fixtures/system/unix/affectedFiles.sh b/tests/fixtures/system/unix/affectedFiles.sh new file mode 100644 index 00000000000..4cc0d80d6a9 --- /dev/null +++ b/tests/fixtures/system/unix/affectedFiles.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -eo pipefail + +echo "Args: $@" +echo "Env: $MOON_AFFECTED_FILES" diff --git a/tests/fixtures/system/unix/moon.yml b/tests/fixtures/system/unix/moon.yml index a0b293ab4cc..111d048aee3 100644 --- a/tests/fixtures/system/unix/moon.yml +++ b/tests/fixtures/system/unix/moon.yml @@ -60,13 +60,17 @@ tasks: - 'file.txt' - 'folder' affectedFiles: - command: bash ./args.sh + command: bash ./affectedFiles.sh options: affectedFiles: true + affectedFilesArgs: + command: bash ./affectedFiles.sh + options: + affectedFiles: 'args' affectedFilesEnvVar: - command: bash ./envVarsMoon.sh + command: bash ./affectedFiles.sh options: - affectedFiles: true + affectedFiles: 'env' # Misc foo: diff --git a/website/docs/config/project.mdx b/website/docs/config/project.mdx index 07651b154be..e0e2bd67586 100644 --- a/website/docs/config/project.mdx +++ b/website/docs/config/project.mdx @@ -429,8 +429,9 @@ tasks: When enabled and the [`--affected` option](../run-task#running-based-on-affected-files-only) is provided, all affected files that match this task's [`inputs`](#inputs) will be passed as relative -paths via command line arguments. If there are no affected files, an argument of `.` (current -directory) will be passed instead. +file paths as command line arguments, and as a `MOON_AFFECTED_FILES` environment variable. If there +are no affected files, `.` (current directory) will be passed instead for arguments, and an empty +value for the environment variable. ```yaml title="moon.yml" {6} tasks: @@ -438,11 +439,12 @@ tasks: command: 'eslint' options: affectedFiles: true + # Only pass args + affectedFiles: 'args' + # Only set env var + affectedFiles: 'env' ``` -The list of files will also be passed as a comma separated list in the `MOON_AFFECTED_FILES` -environment variable. - :::caution When using this option, ensure that explicit files or `.` _are not present_ in the [`args`](#args)