diff --git a/.yarn/versions/2a5a0c21.yml b/.yarn/versions/2a5a0c21.yml index 28fac241db..0e53606a2e 100644 --- a/.yarn/versions/2a5a0c21.yml +++ b/.yarn/versions/2a5a0c21.yml @@ -1,9 +1,16 @@ releases: - "@moonrepo/cli": minor - "@moonrepo/core-linux-arm64-gnu": minor - "@moonrepo/core-linux-arm64-musl": minor - "@moonrepo/core-linux-x64-gnu": minor - "@moonrepo/core-linux-x64-musl": minor - "@moonrepo/core-macos-arm64": minor - "@moonrepo/core-macos-x64": minor - "@moonrepo/core-windows-x64-msvc": minor + '@moonrepo/cli': minor + '@moonrepo/core-linux-arm64-gnu': minor + '@moonrepo/core-linux-arm64-musl': minor + '@moonrepo/core-linux-x64-gnu': minor + '@moonrepo/core-linux-x64-musl': minor + '@moonrepo/core-macos-arm64': minor + '@moonrepo/core-macos-x64': minor + '@moonrepo/core-windows-x64-msvc': minor + '@moonrepo/types': minor + +declined: + - '@moonrepo/nx-compat' + - '@moonrepo/report' + - '@moonrepo/runtime' + - website diff --git a/CHANGELOG.md b/CHANGELOG.md index 528135eac3..0539bc0906 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Updated task option `runInCI` to support the values "always" (always run) and "affected" (only run + if affected, same as `true`). + ## 1.30.5 #### 🐞 Fixes diff --git a/crates/action-graph/src/action_graph_builder.rs b/crates/action-graph/src/action_graph_builder.rs index 7922314c58..2d0b2cea05 100644 --- a/crates/action-graph/src/action_graph_builder.rs +++ b/crates/action-graph/src/action_graph_builder.rs @@ -541,6 +541,10 @@ impl<'app> ActionGraphBuilder<'app> { let mut inserted_nodes = vec![]; let mut initial_targets = vec![]; + if let Some(affected) = &mut self.affected { + affected.set_ci_check(reqs.ci_check); + } + // Track the qualified as an initial target for locator in reqs.target_locators.clone() { initial_targets.push(match locator { diff --git a/crates/action-graph/tests/action_graph_test.rs b/crates/action-graph/tests/action_graph_test.rs index 46a38a87c6..144ead54f7 100644 --- a/crates/action-graph/tests/action_graph_test.rs +++ b/crates/action-graph/tests/action_graph_test.rs @@ -7,7 +7,7 @@ use moon_action_context::TargetState; use moon_action_graph::*; use moon_common::path::WorkspaceRelativePathBuf; use moon_common::Id; -use moon_config::{PlatformType, TaskArgs, TaskDependencyConfig}; +use moon_config::{PlatformType, TaskArgs, TaskDependencyConfig, TaskOptionRunInCI}; use moon_platform::*; use moon_task::{Target, TargetLocator, Task}; use moon_test_utils2::generate_workspace_graph; @@ -1027,7 +1027,7 @@ mod action_graph { let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); - task.options.run_in_ci = true; + task.options.run_in_ci = TaskOptionRunInCI::Enabled(true); builder .run_task( @@ -1121,7 +1121,7 @@ mod action_graph { let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); - task.options.run_in_ci = false; + task.options.run_in_ci = TaskOptionRunInCI::Enabled(false); builder .run_task( @@ -1158,7 +1158,7 @@ mod action_graph { let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); - task.options.run_in_ci = false; + task.options.run_in_ci = TaskOptionRunInCI::Enabled(false); builder .run_task( @@ -1188,7 +1188,7 @@ mod action_graph { let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); - task.options.run_in_ci = false; + task.options.run_in_ci = TaskOptionRunInCI::Enabled(false); builder .run_task( diff --git a/crates/affected/src/affected_tracker.rs b/crates/affected/src/affected_tracker.rs index b92ad161be..cdf2c696a6 100644 --- a/crates/affected/src/affected_tracker.rs +++ b/crates/affected/src/affected_tracker.rs @@ -2,7 +2,7 @@ use crate::affected::*; use moon_common::path::WorkspaceRelativePathBuf; use moon_common::{color, Id}; use moon_project::Project; -use moon_task::{Target, Task}; +use moon_task::{Target, Task, TaskOptionRunInCI}; use moon_workspace_graph::{GraphConnections, WorkspaceGraph}; use rustc_hash::{FxHashMap, FxHashSet}; use std::env; @@ -10,6 +10,8 @@ use std::fmt; use tracing::{debug, trace}; pub struct AffectedTracker<'app> { + ci: bool, + workspace_graph: &'app WorkspaceGraph, touched_files: &'app FxHashSet, @@ -38,6 +40,7 @@ impl<'app> AffectedTracker<'app> { tasks: FxHashMap::default(), task_downstream: DownstreamScope::None, task_upstream: UpstreamScope::Deep, + ci: false, } } @@ -82,6 +85,11 @@ impl<'app> AffectedTracker<'app> { affected } + pub fn set_ci_check(&mut self, ci: bool) -> &mut Self { + self.ci = ci; + self + } + pub fn with_project_scopes( &mut self, upstream_scope: UpstreamScope, @@ -322,6 +330,16 @@ impl<'app> AffectedTracker<'app> { return Ok(Some(AffectedBy::AlreadyMarked)); } + if self.ci { + match &task.options.run_in_ci { + TaskOptionRunInCI::Always => { + return Ok(Some(AffectedBy::AlwaysAffected)); + } + TaskOptionRunInCI::Enabled(false) => return Ok(None), + _ => {} + }; + } + // inputs: [] if task.state.empty_inputs { return Ok(None); diff --git a/crates/affected/tests/__fixtures__/tasks/ci/moon.yml b/crates/affected/tests/__fixtures__/tasks/ci/moon.yml new file mode 100644 index 0000000000..8b72603701 --- /dev/null +++ b/crates/affected/tests/__fixtures__/tasks/ci/moon.yml @@ -0,0 +1,13 @@ +tasks: + enabled: + options: + runInCI: true + disabled: + options: + runInCI: false + affected: + options: + runInCI: affected + always: + options: + runInCI: always diff --git a/crates/affected/tests/affected_tracker_test.rs b/crates/affected/tests/affected_tracker_test.rs index 2b078e4d7f..e38585b8b1 100644 --- a/crates/affected/tests/affected_tracker_test.rs +++ b/crates/affected/tests/affected_tracker_test.rs @@ -956,4 +956,110 @@ mod affected_tasks { ); } } + + mod ci { + use super::*; + + #[tokio::test] + async fn when_ci_tracks_for_true() { + let workspace_graph = generate_workspace_graph("tasks").await; + let touched_files = FxHashSet::from_iter(["ci/file.txt".into()]); + + let mut tracker = AffectedTracker::new(&workspace_graph, &touched_files); + tracker.set_ci_check(true); + tracker + .track_tasks_by_target(&[Target::parse("ci:enabled").unwrap()]) + .unwrap(); + let affected = tracker.build(); + + assert_eq!( + affected.tasks, + FxHashMap::from_iter([( + Target::parse("ci:enabled").unwrap(), + create_state_from_file("ci/file.txt") + )]) + ); + } + + #[tokio::test] + async fn when_not_ci_tracks_for_true() { + let workspace_graph = generate_workspace_graph("tasks").await; + let touched_files = FxHashSet::from_iter(["ci/file.txt".into()]); + + let mut tracker = AffectedTracker::new(&workspace_graph, &touched_files); + tracker.set_ci_check(false); + tracker + .track_tasks_by_target(&[Target::parse("ci:enabled").unwrap()]) + .unwrap(); + let affected = tracker.build(); + + assert_eq!( + affected.tasks, + FxHashMap::from_iter([( + Target::parse("ci:enabled").unwrap(), + create_state_from_file("ci/file.txt") + )]) + ); + } + + #[tokio::test] + async fn when_ci_doesnt_track_for_false() { + let workspace_graph = generate_workspace_graph("tasks").await; + let touched_files = FxHashSet::from_iter(["ci/file.txt".into()]); + + let mut tracker = AffectedTracker::new(&workspace_graph, &touched_files); + tracker.set_ci_check(true); + tracker + .track_tasks_by_target(&[Target::parse("ci:disabled").unwrap()]) + .unwrap(); + let affected = tracker.build(); + + assert!(affected.tasks.is_empty()); + } + + #[tokio::test] + async fn when_not_ci_tracks_for_false() { + let workspace_graph = generate_workspace_graph("tasks").await; + let touched_files = FxHashSet::from_iter(["ci/file.txt".into()]); + + let mut tracker = AffectedTracker::new(&workspace_graph, &touched_files); + tracker.set_ci_check(false); + tracker + .track_tasks_by_target(&[Target::parse("ci:disabled").unwrap()]) + .unwrap(); + let affected = tracker.build(); + + assert_eq!( + affected.tasks, + FxHashMap::from_iter([( + Target::parse("ci:disabled").unwrap(), + create_state_from_file("ci/file.txt") + )]) + ); + } + + #[tokio::test] + async fn when_ci_always_tracks_if_not_touched() { + let workspace_graph = generate_workspace_graph("tasks").await; + let touched_files = FxHashSet::default(); + + let mut tracker = AffectedTracker::new(&workspace_graph, &touched_files); + tracker.set_ci_check(true); + tracker + .track_tasks_by_target(&[Target::parse("ci:always").unwrap()]) + .unwrap(); + let affected = tracker.build(); + + assert_eq!( + affected.tasks, + FxHashMap::from_iter([( + Target::parse("ci:always").unwrap(), + AffectedTaskState { + other: true, + ..Default::default() + } + )]) + ); + } + } } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 9b95f63343..72546f5c8d 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -5,6 +5,7 @@ mod config_finder; mod config_loader; mod inherited_tasks_config; mod language_platform; +mod macros; pub mod patterns; mod portable_path; mod project; diff --git a/crates/config/src/macros.rs b/crates/config/src/macros.rs new file mode 100644 index 0000000000..5297c27a70 --- /dev/null +++ b/crates/config/src/macros.rs @@ -0,0 +1,32 @@ +#[macro_export] +macro_rules! generate_switch { + ($name:ident, [ $($value:literal),* ]) => { + impl $name { + pub fn is_enabled(&self) -> bool { + !matches!(self, Self::Enabled(false)) + } + } + + impl From for $name { + fn from(value: bool) -> Self { + Self::Enabled(value) + } + } + + impl Schematic for $name { + fn build_schema(mut schema: SchemaBuilder) -> Schema { + schema.union(UnionType::new_any([ + schema.infer::(), + schema.nest().string(StringType { + enum_values: Some(Vec::from_iter([ + $( + $value.into() + ),* + ])), + ..Default::default() + }), + ])) + } + } + }; +} diff --git a/crates/config/src/project/task_options_config.rs b/crates/config/src/project/task_options_config.rs index 3f7bbaa4a0..d05df76b84 100644 --- a/crates/config/src/project/task_options_config.rs +++ b/crates/config/src/project/task_options_config.rs @@ -1,10 +1,9 @@ +use crate::generate_switch; use crate::portable_path::FilePath; use crate::shapes::{InputPath, OneOrMany}; use moon_common::cacheable; use schematic::schema::{StringType, UnionType}; use schematic::{derive_enum, Config, ConfigEnum, Schema, SchemaBuilder, Schematic, ValidateError}; -use serde::{de, Deserialize, Deserializer, Serialize}; -use serde_yaml::Value; use std::env::consts; use std::str::FromStr; @@ -23,50 +22,21 @@ fn validate_interactive( Ok(()) } -/// The pattern in which affected files will be passed to the affected task. -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -#[serde(untagged, rename_all = "kebab-case")] -pub enum TaskOptionAffectedFiles { - /// Passed as command line arguments. - Args, - /// Passed as environment variables. - Env, - /// Passed as command line arguments and environment variables. - Enabled(bool), -} - -impl Schematic for TaskOptionAffectedFiles { - fn schema_name() -> Option { - Some("TaskOptionAffectedFiles".into()) - } - - fn build_schema(mut schema: SchemaBuilder) -> Schema { - schema.union(UnionType::new_any([ - schema.infer::(), - schema.nest().string(StringType { - enum_values: Some(vec!["args".into(), "env".into()]), - ..Default::default() - }), - ])) +derive_enum!( + /// The pattern in which affected files will be passed to the affected task. + #[serde(expecting = "expected `args`, `env`, or a boolean")] + pub enum TaskOptionAffectedFiles { + /// Passed as command line arguments. + Args, + /// Passed as environment variables. + Env, + /// Passed as command line arguments and environment variables. + #[serde(untagged)] + Enabled(bool), } -} +); -impl<'de> Deserialize<'de> for TaskOptionAffectedFiles { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - match Value::deserialize(deserializer)? { - Value::Bool(value) => Ok(TaskOptionAffectedFiles::Enabled(value)), - Value::String(value) if value == "args" || value == "env" => Ok(if value == "args" { - TaskOptionAffectedFiles::Args - } else { - TaskOptionAffectedFiles::Env - }), - _ => Err(de::Error::custom("expected `args`, `env`, or a boolean")), - } - } -} +generate_switch!(TaskOptionAffectedFiles, ["args", "env"]); derive_enum!( /// The pattern in which a task is dependent on a `.env` file. @@ -116,6 +86,22 @@ impl Schematic for TaskOptionEnvFile { } } +derive_enum!( + /// The pattern in which to run the task automatically in CI. + #[serde(expecting = "expected `always`, `affected`, or a boolean")] + pub enum TaskOptionRunInCI { + /// Always run, regardless of affected. + Always, + /// Only run if affected by touched files. + Affected, + /// Either affected, or don't run at all. + #[serde(untagged)] + Enabled(bool), + } +); + +generate_switch!(TaskOptionRunInCI, ["always", "affected"]); + derive_enum!( /// The strategy in which to merge a specific task option. #[derive(ConfigEnum, Copy, Default)] @@ -278,7 +264,7 @@ cacheable!( /// Whether to run the task in CI or not, when executing `moon ci` or `moon run`. #[serde(rename = "runInCI")] - pub run_in_ci: Option, + pub run_in_ci: Option, /// Runs the task from the workspace root, instead of the project root. pub run_from_workspace_root: Option, diff --git a/crates/config/tests/inherited_tasks_config_test.rs b/crates/config/tests/inherited_tasks_config_test.rs index bda4bad1fb..653d5d62db 100644 --- a/crates/config/tests/inherited_tasks_config_test.rs +++ b/crates/config/tests/inherited_tasks_config_test.rs @@ -2,11 +2,7 @@ mod utils; use httpmock::prelude::*; use moon_common::Id; -use moon_config::{ - ConfigLoader, InheritedTasksConfig, InheritedTasksManager, InputPath, LanguageType, - PlatformType, ProjectType, StackType, TaskArgs, TaskConfig, TaskDependency, - TaskDependencyConfig, TaskMergeStrategy, TaskOptionsConfig, -}; +use moon_config::*; use moon_target::Target; use rustc_hash::FxHashMap; use schematic::Config; @@ -97,7 +93,7 @@ tasks: TaskConfig { command: TaskArgs::String("e".to_owned()), options: TaskOptionsConfig { - run_in_ci: Some(false), + run_in_ci: Some(TaskOptionRunInCI::Enabled(false)), ..TaskOptionsConfig::default() }, ..TaskConfig::default() @@ -1111,7 +1107,6 @@ mod task_manager { mod pkl { use super::*; use moon_common::Id; - use moon_config::*; use starbase_sandbox::locate_fixture; #[test] @@ -1183,7 +1178,7 @@ mod task_manager { persistent: Some(true), retry_count: Some(3), run_deps_in_parallel: Some(false), - run_in_ci: Some(true), + run_in_ci: Some(TaskOptionRunInCI::Enabled(true)), run_from_workspace_root: Some(false), shell: Some(false), timeout: Some(60), diff --git a/crates/task-builder/src/task_deps_builder.rs b/crates/task-builder/src/task_deps_builder.rs index 78637bc4f1..cde10eacc1 100644 --- a/crates/task-builder/src/task_deps_builder.rs +++ b/crates/task-builder/src/task_deps_builder.rs @@ -128,7 +128,7 @@ impl TaskDepsBuilder<'_> { } // Do not depend on tasks that can't run in CI - if !dep_task_options.run_in_ci && self.task.options.run_in_ci { + if !dep_task_options.run_in_ci.is_enabled() && self.task.options.run_in_ci.is_enabled() { return Err(TasksBuilderError::RunInCiDepRequirement { dep: dep_task_target.to_owned(), task: self.task.target.to_owned(), diff --git a/crates/task-builder/src/tasks_builder.rs b/crates/task-builder/src/tasks_builder.rs index da747a85e4..451de05e86 100644 --- a/crates/task-builder/src/tasks_builder.rs +++ b/crates/task-builder/src/tasks_builder.rs @@ -6,8 +6,8 @@ use moon_common::{color, supports_pkl_configs, Id}; use moon_config::{ is_glob_like, InheritedTasksConfig, InputPath, PlatformType, ProjectConfig, ProjectWorkspaceInheritedTasksConfig, TaskArgs, TaskConfig, TaskDependency, - TaskDependencyConfig, TaskMergeStrategy, TaskOptionsConfig, TaskOutputStyle, TaskPreset, - TaskType, ToolchainConfig, + TaskDependencyConfig, TaskMergeStrategy, TaskOptionRunInCI, TaskOptionsConfig, TaskOutputStyle, + TaskPreset, TaskType, ToolchainConfig, }; use moon_target::Target; use moon_task::{Task, TaskOptions}; @@ -632,7 +632,7 @@ impl<'proj> TasksBuilder<'proj> { } if let Some(run_in_ci) = &config.run_in_ci { - options.run_in_ci = *run_in_ci; + options.run_in_ci = run_in_ci.to_owned(); } if let Some(run_from_workspace_root) = &config.run_from_workspace_root { @@ -660,7 +660,7 @@ impl<'proj> TasksBuilder<'proj> { // options.cache = false; options.output_style = Some(TaskOutputStyle::Stream); // options.persistent = false; - options.run_in_ci = false; + options.run_in_ci = TaskOptionRunInCI::Enabled(false); } Ok(options) @@ -781,7 +781,7 @@ impl<'proj> TasksBuilder<'proj> { interactive: preset.is_some_and(|set| set == TaskPreset::Watcher), output_style: Some(TaskOutputStyle::Stream), persistent: true, - run_in_ci: false, + run_in_ci: TaskOptionRunInCI::Enabled(false), ..TaskOptions::default() }, _ => TaskOptions::default(), diff --git a/crates/task-builder/tests/task_deps_builder_test.rs b/crates/task-builder/tests/task_deps_builder_test.rs index dd5fa7a974..f3ca9541df 100644 --- a/crates/task-builder/tests/task_deps_builder_test.rs +++ b/crates/task-builder/tests/task_deps_builder_test.rs @@ -92,7 +92,7 @@ mod task_deps_builder { #[should_panic(expected = "Task project:task cannot depend on task project:no-ci")] fn errors_if_dep_not_run_in_ci() { let mut task = create_task(); - task.options.run_in_ci = true; + task.options.run_in_ci = TaskOptionRunInCI::Enabled(true); task.deps .push(TaskDependencyConfig::new(Target::parse("no-ci").unwrap())); @@ -101,7 +101,7 @@ mod task_deps_builder { FxHashMap::from_iter([( Target::parse("project:no-ci").unwrap(), TaskOptions { - run_in_ci: false, + run_in_ci: TaskOptionRunInCI::Enabled(false), ..Default::default() }, )]), @@ -111,7 +111,7 @@ mod task_deps_builder { #[test] fn doesnt_errors_if_dep_run_in_ci() { let mut task = create_task(); - task.options.run_in_ci = false; + task.options.run_in_ci = TaskOptionRunInCI::Enabled(false); task.deps .push(TaskDependencyConfig::new(Target::parse("ci").unwrap())); @@ -120,7 +120,7 @@ mod task_deps_builder { FxHashMap::from_iter([( Target::parse("project:ci").unwrap(), TaskOptions { - run_in_ci: true, + run_in_ci: TaskOptionRunInCI::Enabled(true), ..Default::default() }, )]), diff --git a/crates/task-builder/tests/tasks_builder_test.rs b/crates/task-builder/tests/tasks_builder_test.rs index 952ab6b02f..574fa5a4d7 100644 --- a/crates/task-builder/tests/tasks_builder_test.rs +++ b/crates/task-builder/tests/tasks_builder_test.rs @@ -616,21 +616,21 @@ mod tasks_builder { // assert!(!task.options.cache); // assert!(!task.options.persistent); - assert!(!task.options.run_in_ci); + assert!(!task.options.run_in_ci.is_enabled()); assert_eq!(task.options.output_style, Some(TaskOutputStyle::Stream)); let task = tasks.get("interactive-local").unwrap(); // assert!(!task.options.cache); // assert!(!task.options.persistent); - assert!(!task.options.run_in_ci); + assert!(!task.options.run_in_ci.is_enabled()); assert_eq!(task.options.output_style, Some(TaskOutputStyle::Stream)); let task = tasks.get("interactive-override").unwrap(); // assert!(!task.options.cache); // assert!(!task.options.persistent); - assert!(!task.options.run_in_ci); + assert!(!task.options.run_in_ci.is_enabled()); assert_eq!(task.options.output_style, Some(TaskOutputStyle::Stream)); } @@ -706,7 +706,7 @@ mod tasks_builder { assert!(!task.options.cache); assert_eq!(task.options.output_style, Some(TaskOutputStyle::Stream)); assert!(task.options.persistent); - assert!(!task.options.run_in_ci); + assert!(!task.options.run_in_ci.is_enabled()); } #[tokio::test] @@ -742,7 +742,7 @@ mod tasks_builder { let ci = tasks.get("override-ci").unwrap(); assert!(ci.state.local_only); - assert!(ci.options.run_in_ci); + assert!(ci.options.run_in_ci.is_enabled()); } #[tokio::test] @@ -774,7 +774,7 @@ mod tasks_builder { assert!(!task.options.cache); assert!(!task.options.interactive); assert!(task.options.persistent); - assert!(!task.options.run_in_ci); + assert!(!task.options.run_in_ci.is_enabled()); assert_eq!(task.options.output_style, Some(TaskOutputStyle::Stream)); // Custom overrides @@ -784,7 +784,7 @@ mod tasks_builder { assert!(task.options.cache); assert!(!task.options.interactive); assert!(task.options.persistent); - assert!(!task.options.run_in_ci); + assert!(!task.options.run_in_ci.is_enabled()); assert_eq!(task.options.output_style, Some(TaskOutputStyle::Stream)); } @@ -799,7 +799,7 @@ mod tasks_builder { assert!(!task.options.cache); assert!(task.options.interactive); assert!(task.options.persistent); - assert!(!task.options.run_in_ci); + assert!(!task.options.run_in_ci.is_enabled()); assert_eq!(task.options.output_style, Some(TaskOutputStyle::Stream)); // Custom overrides @@ -809,7 +809,7 @@ mod tasks_builder { assert!(!task.options.cache); assert!(!task.options.interactive); assert!(task.options.persistent); - assert!(!task.options.run_in_ci); + assert!(!task.options.run_in_ci.is_enabled()); assert_eq!(task.options.output_style, Some(TaskOutputStyle::Stream)); } } @@ -1925,7 +1925,7 @@ mod tasks_builder { let task = tasks.get("extend-options").unwrap(); assert!(!task.options.cache); - assert!(task.options.run_in_ci); + assert!(task.options.run_in_ci.is_enabled()); assert!(task.options.persistent); assert_eq!(task.options.retry_count, 3); } @@ -1937,7 +1937,7 @@ mod tasks_builder { let task = tasks.get("extend-local").unwrap(); assert!(task.options.cache); - assert!(task.options.run_in_ci); + assert!(task.options.run_in_ci.is_enabled()); assert!(!task.options.persistent); } @@ -1962,7 +1962,7 @@ mod tasks_builder { ); assert!(task.options.cache); - assert!(!task.options.run_in_ci); + assert!(!task.options.run_in_ci.is_enabled()); assert!(task.options.persistent); assert_eq!(task.options.retry_count, 3); } diff --git a/crates/task/src/lib.rs b/crates/task/src/lib.rs index 7b3bb75e44..36e4963a43 100644 --- a/crates/task/src/lib.rs +++ b/crates/task/src/lib.rs @@ -1,7 +1,10 @@ mod task; mod task_options; -pub use moon_config::{TaskConfig, TaskOptionsConfig, TaskType}; +pub use moon_config::{ + TaskConfig, TaskOptionAffectedFiles, TaskOptionEnvFile, TaskOptionRunInCI, TaskOptionsConfig, + TaskType, +}; pub use moon_target::*; pub use task::*; pub use task_options::*; diff --git a/crates/task/src/task.rs b/crates/task/src/task.rs index e89328fd6c..97c98ecfd9 100644 --- a/crates/task/src/task.rs +++ b/crates/task/src/task.rs @@ -236,7 +236,7 @@ impl Task { /// Return true if the task should run in a CI environment. pub fn should_run_in_ci(&self) -> bool { - if !self.options.run_in_ci { + if !self.options.run_in_ci.is_enabled() { return false; } diff --git a/crates/task/src/task_options.rs b/crates/task/src/task_options.rs index 883ae807b6..e6621bdc28 100644 --- a/crates/task/src/task_options.rs +++ b/crates/task/src/task_options.rs @@ -1,7 +1,7 @@ use moon_common::cacheable; use moon_config::{ - InputPath, TaskMergeStrategy, TaskOperatingSystem, TaskOptionAffectedFiles, TaskOutputStyle, - TaskUnixShell, TaskWindowsShell, + InputPath, TaskMergeStrategy, TaskOperatingSystem, TaskOptionAffectedFiles, TaskOptionRunInCI, + TaskOutputStyle, TaskUnixShell, TaskWindowsShell, }; cacheable!( @@ -53,7 +53,7 @@ cacheable!( pub run_deps_in_parallel: bool, #[serde(rename = "runInCI")] - pub run_in_ci: bool, + pub run_in_ci: TaskOptionRunInCI, pub run_from_workspace_root: bool, @@ -93,7 +93,7 @@ impl Default for TaskOptions { persistent: false, retry_count: 0, run_deps_in_parallel: true, - run_in_ci: true, + run_in_ci: TaskOptionRunInCI::Affected, run_from_workspace_root: false, shell: None, timeout: None, diff --git a/packages/types/src/tasks-config.ts b/packages/types/src/tasks-config.ts index 84dfbd3376..a6772b3a30 100644 --- a/packages/types/src/tasks-config.ts +++ b/packages/types/src/tasks-config.ts @@ -18,8 +18,6 @@ export interface TaskDependencyConfig { export type TaskDependency = string | TaskDependencyConfig; -export type TaskOptionAffectedFiles = boolean | 'args' | 'env'; - export type TaskOptionEnvFile = boolean | string | string[]; /** The strategy in which to merge a specific task option. */ @@ -49,7 +47,7 @@ export type TaskWindowsShell = 'bash' | 'elvish' | 'fish' | 'murex' | 'nu' | 'pw /** Options to control task inheritance and execution. */ export interface TaskOptionsConfig { /** The pattern in which affected files will be passed to the task. */ - affectedFiles: TaskOptionAffectedFiles | null; + affectedFiles: boolean | 'args' | 'env' | null; /** * When affected and no files are matching, pass the task inputs * as arguments to the command, instead of `.`. @@ -150,7 +148,7 @@ export interface TaskOptionsConfig { /** Runs the task from the workspace root, instead of the project root. */ runFromWorkspaceRoot: boolean | null; /** Whether to run the task in CI or not, when executing `moon ci` or `moon run`. */ - runInCI: boolean | null; + runInCI: boolean | 'always' | 'affected' | null; /** * Runs the task within a shell. When not defined, runs the task * directly while relying on `PATH` resolution. @@ -301,7 +299,7 @@ export type PartialTaskDependency = string | PartialTaskDependencyConfig; /** Options to control task inheritance and execution. */ export interface PartialTaskOptionsConfig { /** The pattern in which affected files will be passed to the task. */ - affectedFiles?: TaskOptionAffectedFiles | null; + affectedFiles?: boolean | 'args' | 'env' | null; /** * When affected and no files are matching, pass the task inputs * as arguments to the command, instead of `.`. @@ -402,7 +400,7 @@ export interface PartialTaskOptionsConfig { /** Runs the task from the workspace root, instead of the project root. */ runFromWorkspaceRoot?: boolean | null; /** Whether to run the task in CI or not, when executing `moon ci` or `moon run`. */ - runInCI?: boolean | null; + runInCI?: boolean | 'always' | 'affected' | null; /** * Runs the task within a shell. When not defined, runs the task * directly while relying on `PATH` resolution. diff --git a/packages/types/src/toolchain-config.ts b/packages/types/src/toolchain-config.ts index 2589a8f144..1762e3021f 100644 --- a/packages/types/src/toolchain-config.ts +++ b/packages/types/src/toolchain-config.ts @@ -272,10 +272,8 @@ export interface PythonConfig { /** * Assumes only the root `requirements.txt` is used for dependencies. * Can be used to support the "one version policy" pattern. - * - * @default true */ - rootRequirementsOnly?: boolean; + rootRequirementsOnly: boolean; /** * Defines the virtual environment name, which will be created in the workspace root. * Project dependencies will be installed into this. @@ -665,8 +663,6 @@ export interface PartialPythonConfig { /** * Assumes only the root `requirements.txt` is used for dependencies. * Can be used to support the "one version policy" pattern. - * - * @default true */ rootRequirementsOnly?: boolean | null; /** diff --git a/website/docs/config/project.mdx b/website/docs/config/project.mdx index 35c50007d3..5e2bab1923 100644 --- a/website/docs/config/project.mdx +++ b/website/docs/config/project.mdx @@ -1300,9 +1300,14 @@ tasks: Whether to run the task automatically in a CI (continuous integration) environment when affected by -touched files, typically through the [`moon ci`](../commands/ci) command. Defaults to `true` unless -the [`local`](#local) setting is disabled, but is _always_ true when a task defines -[`outputs`](#outputs). +touched files, typically through the [`moon ci`](../commands/ci) command. Supports the following +values: + +- `always` - Always run in CI, regardless if affected or not. +- `true`, `affected` - Only run if affected by touched files. +- `false` - Never run in CI. + +Defaults to `true` unless the [`local`](#local) setting is enabled. ```yaml title="moon.yml" {5} tasks: diff --git a/website/static/schemas/project.json b/website/static/schemas/project.json index d95b059945..651aa7a562 100644 --- a/website/static/schemas/project.json +++ b/website/static/schemas/project.json @@ -1095,20 +1095,6 @@ "windows" ] }, - "TaskOptionAffectedFiles": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "enum": [ - "args", - "env" - ] - } - ] - }, "TaskOptionEnvFile": { "anyOf": [ { @@ -1134,7 +1120,18 @@ "description": "The pattern in which affected files will be passed to the task.", "anyOf": [ { - "$ref": "#/definitions/TaskOptionAffectedFiles" + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "enum": [ + "args", + "env" + ] + } + ] }, { "type": "null" @@ -1414,7 +1411,18 @@ "description": "Whether to run the task in CI or not, when executing moon ci or moon run.", "anyOf": [ { - "type": "boolean" + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "enum": [ + "always", + "affected" + ] + } + ] }, { "type": "null" diff --git a/website/static/schemas/tasks.json b/website/static/schemas/tasks.json index e2e106b153..98fb07209f 100644 --- a/website/static/schemas/tasks.json +++ b/website/static/schemas/tasks.json @@ -364,20 +364,6 @@ "windows" ] }, - "TaskOptionAffectedFiles": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "enum": [ - "args", - "env" - ] - } - ] - }, "TaskOptionEnvFile": { "anyOf": [ { @@ -403,7 +389,18 @@ "description": "The pattern in which affected files will be passed to the task.", "anyOf": [ { - "$ref": "#/definitions/TaskOptionAffectedFiles" + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "enum": [ + "args", + "env" + ] + } + ] }, { "type": "null" @@ -683,7 +680,18 @@ "description": "Whether to run the task in CI or not, when executing moon ci or moon run.", "anyOf": [ { - "type": "boolean" + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "enum": [ + "always", + "affected" + ] + } + ] }, { "type": "null" diff --git a/website/static/schemas/toolchain.json b/website/static/schemas/toolchain.json index 291befc070..b4cc88b5fa 100644 --- a/website/static/schemas/toolchain.json +++ b/website/static/schemas/toolchain.json @@ -677,7 +677,6 @@ "rootRequirementsOnly": { "title": "rootRequirementsOnly", "description": "Assumes only the root requirements.txt is used for dependencies. Can be used to support the \"one version policy\" pattern.", - "default": true, "type": "boolean", "markdownDescription": "Assumes only the root `requirements.txt` is used for dependencies. Can be used to support the \"one version policy\" pattern." },