diff --git a/Cargo.lock b/Cargo.lock index 1b0c2b0921a..246c7e383d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2635,6 +2635,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "moon_common" +version = "0.1.0" +dependencies = [ + "miette", + "once_cell", + "regex", + "starbase_styles", + "thiserror", +] + [[package]] name = "moon_config" version = "0.1.0" @@ -2905,7 +2916,6 @@ dependencies = [ "moon_platform", "moon_project", "moon_project_graph", - "moon_target", "moon_task", "moon_terminal", "moon_test_utils", @@ -3149,9 +3159,10 @@ dependencies = [ name = "moon_target" version = "0.1.0" dependencies = [ - "moon_utils", + "moon_common", + "once_cell", + "regex", "serde", - "starbase_styles", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index fa7942448d6..1ce23cf3efe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,10 @@ members = [ "crates/ruby/*", "crates/rust/*", "crates/system/*", - "crates/typescript/*" + "crates/typescript/*", + + # Next-gen + "nextgen/*" ] default-members = ["crates/cli"] @@ -26,6 +29,7 @@ clap = { version = "4.2.4", features = ["derive", "env", "wrap_help"] } clap_complete = "4.2.1" console = "0.15.5" criterion = { version = "0.4.0", features = ["async_tokio"] } +miette = "5.7.0" once_cell = "1.17.1" petgraph = { version = "0.6.3", default-features = false, features = ["serde-1"] } proto_cli = "0.7.2" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index a470f040b22..1025df82585 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -33,7 +33,7 @@ moon_project = { path = "../core/project" } moon_project_graph = { path = "../core/project-graph" } moon_query = { path = "../core/query" } moon_system_platform = { path = "../system/platform" } -moon_target = { path = "../core/target" } +moon_target = { path = "../../nextgen/target" } moon_task = { path = "../core/task" } moon_terminal = { path = "../core/terminal" } moon_tool = { path = "../core/tool" } diff --git a/crates/cli/src/commands/task.rs b/crates/cli/src/commands/task.rs index 3351e4e16ac..0be52dcdb5c 100644 --- a/crates/cli/src/commands/task.rs +++ b/crates/cli/src/commands/task.rs @@ -8,7 +8,7 @@ use starbase_styles::color; pub async fn task(id: String, json: bool) -> Result<(), AnyError> { let target = Target::parse(&id)?; - let Some(project_id) = target.project_id else { + let Some(project_id) = target.scope_id else { return Err("A project ID is required.".into()); }; diff --git a/crates/cli/tests/run_test.rs b/crates/cli/tests/run_test.rs index db8e6d10c44..b21db679ad8 100644 --- a/crates/cli/tests/run_test.rs +++ b/crates/cli/tests/run_test.rs @@ -1110,7 +1110,7 @@ mod outputs { }); assert!(predicates::str::contains( - "Project dependencies scope (^:) is not supported in run contexts." + "Dependencies scope (^:) is not supported in run contexts." ) .eval(&assert.output())); } @@ -1127,10 +1127,10 @@ mod outputs { cmd.arg("run").arg("outputs:noOutput"); }); - assert!(predicates::str::contains( - "Project self scope (~:) is not supported in run contexts." - ) - .eval(&assert.output())); + assert!( + predicates::str::contains("Self scope (~:) is not supported in run contexts.") + .eval(&assert.output()) + ); } } } diff --git a/crates/cli/tests/snapshots/run_test__target_scopes__errors_for_deps_scope.snap b/crates/cli/tests/snapshots/run_test__target_scopes__errors_for_deps_scope.snap index 8d67b8d888e..d9848f3c5cc 100644 --- a/crates/cli/tests/snapshots/run_test__target_scopes__errors_for_deps_scope.snap +++ b/crates/cli/tests/snapshots/run_test__target_scopes__errors_for_deps_scope.snap @@ -1,10 +1,9 @@ --- source: crates/cli/tests/run_test.rs -assertion_line: 240 -expression: get_assert_output(&assert) +expression: assert.output() --- - ERROR Project dependencies scope (^:) is not supported in run contexts. + ERROR Dependencies scope (^:) is not supported in run contexts. diff --git a/crates/cli/tests/snapshots/run_test__target_scopes__errors_for_self_scope.snap b/crates/cli/tests/snapshots/run_test__target_scopes__errors_for_self_scope.snap index 4dc8afe1646..4a7be80077a 100644 --- a/crates/cli/tests/snapshots/run_test__target_scopes__errors_for_self_scope.snap +++ b/crates/cli/tests/snapshots/run_test__target_scopes__errors_for_self_scope.snap @@ -1,10 +1,9 @@ --- source: crates/cli/tests/run_test.rs -assertion_line: 252 -expression: get_assert_output(&assert) +expression: assert.output() --- - ERROR Project self scope (~:) is not supported in run contexts. + ERROR Self scope (~:) is not supported in run contexts. diff --git a/crates/core/action-context/Cargo.toml b/crates/core/action-context/Cargo.toml index b1d84e2829f..eea0e6b6704 100644 --- a/crates/core/action-context/Cargo.toml +++ b/crates/core/action-context/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -moon_target = { path = "../target" } +moon_target = { path = "../../../nextgen/target" } clap = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true } diff --git a/crates/core/action-pipeline/Cargo.toml b/crates/core/action-pipeline/Cargo.toml index 0aced3b145d..8f7a0b6d356 100644 --- a/crates/core/action-pipeline/Cargo.toml +++ b/crates/core/action-pipeline/Cargo.toml @@ -24,7 +24,7 @@ moon_platform = { path = "../platform" } moon_project = { path = "../project" } moon_project_graph = { path = "../project-graph" } moon_runner = { path = "../runner" } -moon_target = { path = "../target" } +moon_target = { path = "../../../nextgen/target" } moon_terminal = { path = "../terminal" } moon_tool = { path = "../tool" } moon_utils = { path = "../utils" } diff --git a/crates/core/action-pipeline/src/estimator.rs b/crates/core/action-pipeline/src/estimator.rs index 4903bf5c5c6..5ee61a12a2c 100644 --- a/crates/core/action-pipeline/src/estimator.rs +++ b/crates/core/action-pipeline/src/estimator.rs @@ -76,11 +76,14 @@ impl Estimator { ActionNode::RunTarget(_, target) => { let task_id = Target::parse(target).unwrap().task_id; - if let Some(task) = tasks.get_mut(&task_id) { + if let Some(task) = tasks.get_mut(task_id.as_str()) { task.count += 1; task.total += task_duration; } else { - tasks.insert(task_id, TaskEstimate::new(task_duration.to_owned())); + tasks.insert( + task_id.to_string(), + TaskEstimate::new(task_duration.to_owned()), + ); } } _ => {} diff --git a/crates/core/action-pipeline/src/processor.rs b/crates/core/action-pipeline/src/processor.rs index dec1d1f96e0..0e85f81e4ff 100644 --- a/crates/core/action-pipeline/src/processor.rs +++ b/crates/core/action-pipeline/src/processor.rs @@ -150,7 +150,7 @@ pub async fn process_action( // Run a task within a project ActionNode::RunTarget(runtime, target_id) => { let target = Target::parse(target_id)?; - let project = local_project_graph.get(target.project_id.as_ref().unwrap())?; + let project = local_project_graph.get(target.scope_id.as_ref().unwrap())?; local_emitter .emit(Event::TargetRunning { target: &target }) diff --git a/crates/core/dep-graph/Cargo.toml b/crates/core/dep-graph/Cargo.toml index a1c4a2300bb..1c57d0f56e6 100644 --- a/crates/core/dep-graph/Cargo.toml +++ b/crates/core/dep-graph/Cargo.toml @@ -18,7 +18,7 @@ moon_platform = { path = "../platform" } moon_project = { path = "../project" } moon_project_graph = { path = "../project-graph" } moon_query = { path = "../query" } -moon_target = { path = "../target" } +moon_target = { path = "../../../nextgen/target" } moon_task = { path = "../task" } petgraph = { workspace = true } rustc-hash = { workspace = true } diff --git a/crates/core/dep-graph/src/dep_builder.rs b/crates/core/dep-graph/src/dep_builder.rs index 1b648ebc55d..b51d8eb4bae 100644 --- a/crates/core/dep-graph/src/dep_builder.rs +++ b/crates/core/dep-graph/src/dep_builder.rs @@ -6,7 +6,7 @@ use moon_platform::{PlatformManager, Runtime}; use moon_project::Project; use moon_project_graph::ProjectGraph; use moon_query::build as build_query; -use moon_target::{Target, TargetError, TargetProjectScope}; +use moon_target::{Target, TargetError, TargetScope}; use moon_task::{Task, TouchedFilePaths}; use petgraph::graph::NodeIndex; use petgraph::Graph; @@ -183,15 +183,16 @@ impl<'ws> DepGraphBuilder<'ws> { color::label(&target.id), ); - let (project_id, task_id) = target.ids()?; - let project = self.project_graph.get(&project_id)?; - let dependents = self.project_graph.get_dependents_of(project)?; + if let TargetScope::Project(project_id) = &target.scope { + let project = self.project_graph.get(project_id)?; + let dependents = self.project_graph.get_dependents_of(project)?; - for dependent_id in dependents { - let dep_project = self.project_graph.get(&dependent_id)?; + for dependent_id in dependents { + let dep_project = self.project_graph.get(&dependent_id)?; - if let Some(dep_task) = dep_project.tasks.get(&task_id) { - self.run_target(&dep_task.target, None)?; + if let Some(dep_task) = dep_project.tasks.get(target.task_id.as_str()) { + self.run_target(&dep_task.target, None)?; + } } } @@ -207,9 +208,9 @@ impl<'ws> DepGraphBuilder<'ws> { let mut inserted_targets = FxHashSet::default(); let mut inserted_indexes = FxHashSet::default(); - match &target.project { + match &target.scope { // :task - TargetProjectScope::All => { + TargetScope::All => { let mut projects = vec![]; if let Some(queried_projects) = &self.queried_projects { @@ -219,7 +220,7 @@ impl<'ws> DepGraphBuilder<'ws> { }; for project in projects { - if project.tasks.contains_key(&target.task_id) { + if project.tasks.contains_key(target.task_id.as_str()) { let all_target = Target::new(&project.id, &target.task_id)?; if let Some(index) = @@ -232,11 +233,11 @@ impl<'ws> DepGraphBuilder<'ws> { } } // ^:task - TargetProjectScope::Deps => { - target.fail_with(TargetError::NoProjectDepsInRunContext)?; + TargetScope::Deps => { + return Err(DepGraphError::Target(TargetError::NoDepsInRunContext)); } // project:task - TargetProjectScope::Id(project_id) => { + TargetScope::Project(project_id) => { let project = self.project_graph.get(project_id)?; let task = project.get_task(&target.task_id)?; @@ -247,9 +248,11 @@ impl<'ws> DepGraphBuilder<'ws> { inserted_indexes.insert(index); } } + // #tag:task + TargetScope::Tag(_) => todo!(), // ~:task - TargetProjectScope::OwnSelf => { - target.fail_with(TargetError::NoProjectSelfInRunContext)?; + TargetScope::OwnSelf => { + return Err(DepGraphError::Target(TargetError::NoSelfInRunContext)); } }; diff --git a/crates/core/dep-graph/tests/dep_graph_test.rs b/crates/core/dep-graph/tests/dep_graph_test.rs index f945c6c605e..b0f1015b7c2 100644 --- a/crates/core/dep-graph/tests/dep_graph_test.rs +++ b/crates/core/dep-graph/tests/dep_graph_test.rs @@ -317,7 +317,7 @@ mod run_target { } #[tokio::test] - #[should_panic(expected = "Target(NoProjectDepsInRunContext)")] + #[should_panic(expected = "Target(NoDepsInRunContext)")] async fn errors_for_target_deps_scope() { let (workspace, projects, _sandbox) = create_project_graph().await; @@ -328,7 +328,7 @@ mod run_target { } #[tokio::test] - #[should_panic(expected = "Target(NoProjectSelfInRunContext)")] + #[should_panic(expected = "Target(NoSelfInRunContext)")] async fn errors_for_target_self_scope() { let (workspace, projects, _sandbox) = create_project_graph().await; diff --git a/crates/core/emitter/Cargo.toml b/crates/core/emitter/Cargo.toml index 26455360210..419cec731c8 100644 --- a/crates/core/emitter/Cargo.toml +++ b/crates/core/emitter/Cargo.toml @@ -17,7 +17,7 @@ moon_cache = { path = "../cache" } moon_error = { path = "../error" } moon_platform_runtime = { path = "../platform-runtime" } moon_project = { path = "../project" } -moon_target = { path = "../target" } +moon_target = { path = "../../../nextgen/target" } moon_task = { path = "../task" } moon_utils = { path = "../utils" } moon_workspace = { path = "../workspace" } diff --git a/crates/core/project-graph/Cargo.toml b/crates/core/project-graph/Cargo.toml index e53e8739ad8..8c3b4b30d8a 100644 --- a/crates/core/project-graph/Cargo.toml +++ b/crates/core/project-graph/Cargo.toml @@ -19,7 +19,7 @@ moon_logger = { path = "../logger" } moon_platform_detector = { path = "../platform-detector" } moon_project = { path = "../project" } moon_query = { path = "../query" } -moon_target = { path = "../target" } +moon_target = { path = "../../../nextgen/target" } moon_task = { path = "../task" } moon_utils = { path = "../utils" } moon_vcs = { path = "../vcs" } diff --git a/crates/core/project-graph/src/project_builder.rs b/crates/core/project-graph/src/project_builder.rs index de0b8bbaade..e45ee927318 100644 --- a/crates/core/project-graph/src/project_builder.rs +++ b/crates/core/project-graph/src/project_builder.rs @@ -13,7 +13,7 @@ use moon_hasher::{convert_paths_to_strings, to_hash}; use moon_logger::{debug, map_list, trace, warn, Logable}; use moon_platform_detector::{detect_project_language, detect_task_platform}; use moon_project::{Project, ProjectDependency, ProjectDependencySource, ProjectError}; -use moon_target::{Target, TargetError, TargetProjectScope}; +use moon_target::{Target, TargetError, TargetScope}; use moon_task::{Task, TaskError, TaskFlag}; use moon_utils::path::expand_to_workspace_relative; use moon_utils::regex::{ENV_VAR, ENV_VAR_SUBSTITUTE}; @@ -324,34 +324,38 @@ impl<'ws> ProjectGraphBuilder<'ws> { }; for target in &task.deps { - match &target.project { + match &target.scope { // ^:task - TargetProjectScope::Deps => { + TargetScope::Deps => { for dep_id in project.get_dependency_ids() { let dep_index = self.indices.get(dep_id).unwrap(); let dep_project = self.graph.node_weight(*dep_index).unwrap(); - if let Some(dep_task) = dep_project.tasks.get(&target.task_id) { + if let Some(dep_task) = dep_project.tasks.get(target.task_id.as_str()) { push_target(dep_task.target.clone()); } } } // ~:task - TargetProjectScope::OwnSelf => { + TargetScope::OwnSelf => { if target.task_id != task.id { push_target(Target::new(&project.id, &target.task_id)?); } } // project:task - TargetProjectScope::Id(project_id) => { + TargetScope::Project(project_id) => { if project_id == &project.id && target.task_id == task.id { // Avoid circular references } else { push_target(target.clone()); } } + // #tag:task + TargetScope::Tag(_) => todo!(), _ => { - target.fail_with(TargetError::NoProjectAllInTaskDeps(target.id.clone()))?; + return Err(ProjectGraphError::Target(TargetError::NoAllInTaskDeps( + target.id.clone(), + ))); } }; } diff --git a/crates/core/project-graph/tests/projects_test.rs b/crates/core/project-graph/tests/projects_test.rs index 1bb7f62130b..604fe0a3052 100644 --- a/crates/core/project-graph/tests/projects_test.rs +++ b/crates/core/project-graph/tests/projects_test.rs @@ -925,7 +925,7 @@ mod task_expansion { } #[tokio::test] - #[should_panic(expected = "Target(NoProjectAllInTaskDeps(\":build\"))")] + #[should_panic(expected = "Target(NoAllInTaskDeps(\":build\"))")] async fn errors_for_all_scope() { tasks_sandbox_with_setup(|sandbox| { sandbox.create_file( diff --git a/crates/core/project/Cargo.toml b/crates/core/project/Cargo.toml index 03dfc4b4ac2..c79e61eebaf 100644 --- a/crates/core/project/Cargo.toml +++ b/crates/core/project/Cargo.toml @@ -9,7 +9,7 @@ moon_constants = { path = "../constants" } moon_error = { path = "../error" } moon_logger = { path = "../logger" } moon_query = { path = "../query" } -moon_target = { path = "../target" } +moon_target = { path = "../../../nextgen/target" } moon_task = { path = "../task" } moon_utils = { path = "../utils" } rustc-hash = { workspace = true } diff --git a/crates/core/runner/Cargo.toml b/crates/core/runner/Cargo.toml index 61dca0a3d3c..222a49d09aa 100644 --- a/crates/core/runner/Cargo.toml +++ b/crates/core/runner/Cargo.toml @@ -14,7 +14,7 @@ moon_hasher = { path = "../hasher" } moon_logger = { path = "../logger" } moon_platform_runtime = { path = "../platform-runtime" } moon_project = { path = "../project" } -moon_target = { path = "../target" } +moon_target = { path = "../../../nextgen/target" } moon_task = { path = "../task" } moon_tool = { path = "../tool" } moon_terminal = { path = "../terminal" } diff --git a/crates/core/runner/src/runner.rs b/crates/core/runner/src/runner.rs index 296deca779d..27981a1f339 100644 --- a/crates/core/runner/src/runner.rs +++ b/crates/core/runner/src/runner.rs @@ -11,7 +11,7 @@ use moon_hasher::HashSet; use moon_logger::{debug, warn}; use moon_platform_runtime::Runtime; use moon_project::Project; -use moon_target::{Target, TargetError, TargetProjectScope}; +use moon_target::{Target, TargetError, TargetScope}; use moon_task::{Task, TaskError, TaskOptionAffectedFiles}; use moon_terminal::{label_checkpoint, Checkpoint}; use moon_utils::{ @@ -379,21 +379,22 @@ impl<'a> Runner<'a> { for target in &self.workspace.config.runner.archivable_targets { let target = Target::parse(target)?; - match &target.project { - TargetProjectScope::All => { + match &target.scope { + TargetScope::All => { if task.target.task_id == target.task_id { return Ok(true); } } - TargetProjectScope::Id(project_id) => { - if let Some(owner_id) = &task.target.project_id { + TargetScope::Project(project_id) => { + if let Some(owner_id) = &task.target.scope_id { if owner_id == project_id && task.target.task_id == target.task_id { return Ok(true); } } } - TargetProjectScope::Deps => return Err(TargetError::NoProjectDepsInRunContext), - TargetProjectScope::OwnSelf => return Err(TargetError::NoProjectSelfInRunContext), + TargetScope::Tag(_) => todo!(), + TargetScope::Deps => return Err(TargetError::NoDepsInRunContext), + TargetScope::OwnSelf => return Err(TargetError::NoSelfInRunContext), }; } diff --git a/crates/core/target/src/errors.rs b/crates/core/target/src/errors.rs deleted file mode 100644 index 6fc90d8fc82..00000000000 --- a/crates/core/target/src/errors.rs +++ /dev/null @@ -1,29 +0,0 @@ -use starbase_styles::{Style, Stylize}; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum TargetError { - #[error( - "Target {} requires literal project and task identifiers, found a scope.", .0.style(Style::Label) - )] - IdOnly(String), - - #[error( - "Invalid target {}, must be in the format of \"project_id:task_id\".", .0.style(Style::Label) - )] - InvalidFormat(String), - - #[error("Target \":\" encountered. Wildcard project and task not supported.")] - TooWild, - - #[error( - "All projects scope (:) is not supported in task deps, for target {}.", .0.style(Style::Label) - )] - NoProjectAllInTaskDeps(String), - - #[error("Project dependencies scope (^:) is not supported in run contexts.")] - NoProjectDepsInRunContext, - - #[error("Project self scope (~:) is not supported in run contexts.")] - NoProjectSelfInRunContext, -} diff --git a/crates/core/target/src/target.rs b/crates/core/target/src/target.rs deleted file mode 100644 index a602f95af5e..00000000000 --- a/crates/core/target/src/target.rs +++ /dev/null @@ -1,383 +0,0 @@ -use crate::errors::TargetError; -use moon_utils::regex::TARGET_PATTERN; -use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; -// use std::fmt; - -#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd)] -pub enum TargetProjectScope { - All, // :task - Deps, // ^:task - Id(String), // project:task - OwnSelf, // ~:task -} - -// impl fmt::Display for TargetProjectScope { -// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -// match self { -// TargetProjectScope::All => write!(f, ""), -// TargetProjectScope::Deps => write!(f, "^"), -// TargetProjectScope::Id(id) => write!(f, "{}", id), -// TargetProjectScope::Own => write!(f, "~"), -// } -// } -// } - -// #[derive(Debug, PartialEq)] -// pub enum TargetTask { -// All, // project: -// Id(TaskID), // project:id -// } - -// impl fmt::Display for TargetTask { -// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -// match self { -// TargetTask::All => write!(f, ""), -// TargetTask::Id(name) => write!(f, "{}", name), -// } -// } -// } - -#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] -#[serde(try_from = "String", into = "String")] -pub struct Target { - pub id: String, - - pub project: TargetProjectScope, - - pub project_id: Option, - - pub task_id: String, -} - -impl Target { - pub fn new(project_id: &str, task_id: &str) -> Result { - Ok(Target { - id: Target::format(project_id, task_id)?, - project: TargetProjectScope::Id(project_id.to_owned()), - project_id: Some(project_id.to_owned()), - task_id: task_id.to_owned(), - }) - } - - pub fn new_self(task_id: &str) -> Result { - Ok(Target { - id: Target::format("~", task_id)?, - project: TargetProjectScope::OwnSelf, - project_id: None, - task_id: task_id.to_owned(), - }) - } - - pub fn format(project_id: &str, task_id: &str) -> Result { - Ok(format!("{project_id}:{task_id}")) - } - - pub fn parse(target_id: &str) -> Result { - if target_id == ":" { - return Err(TargetError::TooWild); - } - - let Some(matches) = TARGET_PATTERN.captures(target_id) else { - return Err(TargetError::InvalidFormat(target_id.to_owned())); - }; - - let mut project_id = None; - - let project = match matches.name("project") { - Some(value) => match value.as_str() { - "" => TargetProjectScope::All, - "^" => TargetProjectScope::Deps, - "~" => TargetProjectScope::OwnSelf, - id => { - project_id = Some(id.to_owned()); - TargetProjectScope::Id(id.to_owned()) - } - }, - None => TargetProjectScope::All, - }; - - let task_id = matches.name("task").unwrap().as_str().to_owned(); - - // let task = match matches.name("task") { - // Some(value) => match value.as_str() { - // "" => TargetTask::All, - // id => TargetTask::Id(id.to_owned()), - // }, - // None => TargetTask::All, - // }; - - Ok(Target { - id: target_id.to_owned(), - project, - project_id, - task_id, - }) - } - - pub fn fail_with(&self, error: TargetError) -> Result<(), TargetError> { - Err(error) - } - - pub fn ids(&self) -> Result<(String, String), TargetError> { - let project_id = match &self.project_id { - Some(id) => id, - None => match &self.project { - TargetProjectScope::Id(id) => id, - _ => return Err(TargetError::IdOnly(self.id.clone())), - }, - }; - - // let task_id = match &self.task { - // TargetTask::Id(id) => id, - // _ => return Err(TargetError::Target(TargetError::IdOnly(self.id.clone()))), - // }; - - Ok((project_id.clone(), self.task_id.clone())) - } - - pub fn is_all_task(&self, task_id: &str) -> bool { - if matches!(&self.project, TargetProjectScope::All) { - return if let Some(id) = task_id.strip_prefix(':') { - self.task_id == id - } else { - self.task_id == task_id - }; - } - - false - } -} - -impl Default for Target { - fn default() -> Self { - Target { - id: "~:unknown".into(), - project: TargetProjectScope::OwnSelf, - project_id: None, - task_id: "unknown".into(), - } - } -} - -impl AsRef for Target { - fn as_ref(&self) -> &Target { - self - } -} - -impl AsRef for Target { - fn as_ref(&self) -> &str { - &self.id - } -} - -impl PartialOrd for Target { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Target { - fn cmp(&self, other: &Self) -> Ordering { - self.id.cmp(&other.id) - } -} - -impl TryFrom for Target { - type Error = TargetError; - - fn try_from(value: String) -> Result { - Target::parse(&value) - } -} - -#[allow(clippy::from_over_into)] -impl Into for Target { - fn into(self) -> String { - self.id - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn format() { - assert_eq!(Target::format("foo", "build").unwrap(), "foo:build"); - } - - #[test] - fn format_with_slashes() { - assert_eq!( - Target::format("foo/sub", "build/esm").unwrap(), - "foo/sub:build/esm" - ); - } - - #[test] - fn format_node() { - assert_eq!( - Target::format("@scope/foo", "build").unwrap(), - "@scope/foo:build" - ); - } - - #[test] - #[should_panic(expected = "InvalidFormat(\"foo$:build\")")] - fn invalid_chars() { - Target::parse("foo$:build").unwrap(); - } - - #[test] - #[should_panic(expected = "InvalidFormat(\"foo:@build\")")] - fn invalid_task_no_at() { - Target::parse("foo:@build").unwrap(); - } - - #[test] - fn parse_ids() { - assert_eq!( - Target::parse("foo:build").unwrap(), - Target { - id: String::from("foo:build"), - project: TargetProjectScope::Id("foo".to_owned()), - project_id: Some("foo".to_owned()), - task_id: "build".to_owned(), - // task: TargetTask::Id("build".to_owned()) - } - ); - } - - #[test] - fn parse_deps_project() { - assert_eq!( - Target::parse("^:build").unwrap(), - Target { - id: String::from("^:build"), - project: TargetProjectScope::Deps, - project_id: None, - task_id: "build".to_owned(), - // task: TargetTask::Id("build".to_owned()) - } - ); - } - - // #[test] - // fn parse_deps_project_all_tasks() { - // assert_eq!( - // Target::parse("^:").unwrap(), - // Target { - // id: String::from("^:"), - // project: TargetProjectScope::Deps, - // task: TargetTask::All, - // } - // ); - // } - - #[test] - fn parse_self_project() { - assert_eq!( - Target::parse("~:build").unwrap(), - Target { - id: String::from("~:build"), - project: TargetProjectScope::OwnSelf, - project_id: None, - task_id: "build".to_owned(), - // task: TargetTask::Id("build".to_owned()) - } - ); - } - - // #[test] - // fn parse_self_project_all_tasks() { - // assert_eq!( - // Target::parse("~:").unwrap(), - // Target { - // id: String::from("~:"), - // project: TargetProjectScope::Own, - // task: TargetTask::All, - // } - // ); - // } - - #[test] - fn parse_all_projects() { - assert_eq!( - Target::parse(":build").unwrap(), - Target { - id: String::from(":build"), - project: TargetProjectScope::All, - project_id: None, - task_id: "build".to_owned(), - // task: TargetTask::Id("build".to_owned()) - } - ); - } - - // #[test] - // fn parse_all_tasks() { - // assert_eq!( - // Target::parse("foo:").unwrap(), - // Target { - // id: String::from("foo:"), - // project: TargetProjectScope::Id("foo".to_owned()), - // task: TargetTask::All, - // } - // ); - // } - - #[test] - #[should_panic(expected = "TooWild")] - fn parse_too_wild() { - Target::parse(":").unwrap(); - } - - #[test] - fn parse_node() { - assert_eq!( - Target::parse("@scope/foo:build").unwrap(), - Target { - id: String::from("@scope/foo:build"), - project: TargetProjectScope::Id("@scope/foo".to_owned()), - project_id: Some("@scope/foo".to_owned()), - task_id: "build".to_owned(), - // task: TargetTask::Id("build".to_owned()) - } - ); - } - - #[test] - fn parse_slashes() { - assert_eq!( - Target::parse("foo/sub:build/esm").unwrap(), - Target { - id: String::from("foo/sub:build/esm"), - project: TargetProjectScope::Id("foo/sub".to_owned()), - project_id: Some("foo/sub".to_owned()), - task_id: "build/esm".to_owned(), - // task: TargetTask::Id("build".to_owned()) - } - ); - } - - #[test] - fn matches_all() { - let all = Target::parse(":lint").unwrap(); - - assert!(all.is_all_task("lint")); - assert!(all.is_all_task(":lint")); - assert!(!all.is_all_task("build")); - assert!(!all.is_all_task(":build")); - assert!(!all.is_all_task("foo:lint")); - - let full = Target::parse("foo:lint").unwrap(); - - assert!(!full.is_all_task("lint")); - assert!(!full.is_all_task(":lint")); - assert!(!full.is_all_task("build")); - assert!(!full.is_all_task(":build")); - assert!(!full.is_all_task("foo:lint")); - } -} diff --git a/crates/core/task/Cargo.toml b/crates/core/task/Cargo.toml index 10085f1d5d1..7a16070c972 100644 --- a/crates/core/task/Cargo.toml +++ b/crates/core/task/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" moon_config = { path = "../config" } moon_error = { path = "../error" } moon_logger = { path = "../logger" } -moon_target = { path = "../target" } +moon_target = { path = "../../../nextgen/target" } moon_utils = { path = "../utils" } common-path = "1.0.0" once_cell = { workspace = true } diff --git a/crates/core/task/src/task.rs b/crates/core/task/src/task.rs index 6bf531e860b..b43e8c98c7e 100644 --- a/crates/core/task/src/task.rs +++ b/crates/core/task/src/task.rs @@ -103,7 +103,7 @@ impl Task { env: cloned_config.env.unwrap_or_default(), flags: FxHashSet::default(), global_inputs: cloned_config.global_inputs, - id: target.task_id.clone(), + id: target.task_id.to_string(), inputs: cloned_config.inputs.unwrap_or_default(), input_vars: FxHashSet::default(), input_globs: FxHashSet::default(), diff --git a/crates/core/utils/src/regex.rs b/crates/core/utils/src/regex.rs index 2dd0688286a..039a74b7b96 100644 --- a/crates/core/utils/src/regex.rs +++ b/crates/core/utils/src/regex.rs @@ -15,7 +15,7 @@ pub static ID_PATTERN: Lazy = pub static TARGET_PATTERN: Lazy = Lazy::new(|| { // Only target projects support `@` because of Node.js, // we don't want to support it in regular IDs! - create_regex( "^(?P(?:[A-Za-z@]{1}[0-9A-Za-z/\\._-]*|\\^|~))?:(?P[A-Za-z]{1}[0-9A-Za-z/\\._-]*)$").unwrap() + create_regex("^(?P(?:[A-Za-z@]{1}[0-9A-Za-z/\\._-]*|\\^|~))?:(?P[A-Za-z]{1}[0-9A-Za-z/\\._-]*)$").unwrap() }); // Input values diff --git a/crates/node/platform/Cargo.toml b/crates/node/platform/Cargo.toml index df153fce02a..a40b91d940c 100644 --- a/crates/node/platform/Cargo.toml +++ b/crates/node/platform/Cargo.toml @@ -14,7 +14,6 @@ moon_node_lang = { path = "../lang" } moon_node_tool = { path = "../tool" } moon_platform = { path = "../../core/platform" } moon_project = { path = "../../core/project" } -moon_target = { path = "../../core/target" } moon_task = { path = "../../core/task" } moon_terminal = { path = "../../core/terminal" } moon_tool = { path = "../../core/tool" } diff --git a/crates/node/platform/src/task.rs b/crates/node/platform/src/task.rs index 4c03170a0b6..a9647864f6c 100644 --- a/crates/node/platform/src/task.rs +++ b/crates/node/platform/src/task.rs @@ -1,7 +1,6 @@ use moon_config::{TaskCommandArgs, TaskConfig, TasksConfigsMap}; use moon_logger::{debug, warn}; use moon_node_lang::package_json::{PackageJson, ScriptsSet}; -use moon_target::Target; use moon_task::{PlatformType, TaskError, TaskID}; use moon_utils::regex::{ID_CLEAN, UNIX_SYSTEM_COMMAND, WINDOWS_SYSTEM_COMMAND}; use moon_utils::{process, regex, string_vec}; @@ -285,7 +284,7 @@ impl<'a> ScriptParser<'a> { } let task_id = clean_script_name(name); - let target_id = Target::format(self.project_id, &task_id)?; + let target_id = format!("{}:{}", self.project_id, task_id); self.tasks.insert( task_id, @@ -471,7 +470,7 @@ impl<'a> ScriptParser<'a> { let name = name.as_ref(); let value = value.as_ref(); let task_id = clean_script_name(name); - let target_id = Target::format(self.project_id, &task_id)?; + let target_id = format!("{}:{}", self.project_id, task_id); self.names_to_ids.insert(name.to_owned(), task_id.clone()); diff --git a/nextgen/common/Cargo.toml b/nextgen/common/Cargo.toml new file mode 100644 index 00000000000..a67f4cae17e --- /dev/null +++ b/nextgen/common/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "moon_common" +version = "0.1.0" +edition = "2021" + +[dependencies] +miette = { workspace = true } +once_cell = { workspace = true } +regex = { workspace = true } +starbase_styles = { workspace = true } +thiserror = { workspace = true } diff --git a/nextgen/common/src/id.rs b/nextgen/common/src/id.rs new file mode 100644 index 00000000000..a547e6735a4 --- /dev/null +++ b/nextgen/common/src/id.rs @@ -0,0 +1,93 @@ +use once_cell::sync::Lazy; +use regex::Regex; +use starbase_styles::{Style, Stylize}; +use std::{ + fmt::{self, Display}, + ops::Deref, + str::FromStr, +}; +use thiserror::Error; + +pub static ID_CHARS: &str = r"[0-9A-Za-z/\._-]*"; + +pub static ID_PATTERN: Lazy = + Lazy::new(|| Regex::new(&format!("^([A-Za-z]{{1}}{})$", ID_CHARS)).unwrap()); + +#[derive(Error, Debug)] +#[error("Invalid identifier {}. May only contain alpha-numeric characters, dashes (-), slashes (/), underscores (_), and dots (.).", .0.style(Style::Id))] +pub struct IdError(String); + +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Id(String); + +impl Id { + pub fn new>(id: S) -> Result { + let id = id.as_ref(); + + if !ID_PATTERN.is_match(id) { + return Err(IdError(id.to_owned())); + } + + Ok(Self::raw(id)) + } + + pub fn raw>(id: S) -> Id { + Id(id.as_ref().to_owned()) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl Display for Id { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl AsRef for Id { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl AsRef for Id { + fn as_ref(&self) -> &String { + &self.0 + } +} + +impl AsRef for Id { + fn as_ref(&self) -> &Id { + self + } +} + +impl Deref for Id { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl PartialEq<&str> for Id { + fn eq(&self, other: &&str) -> bool { + &self.0 == other + } +} + +impl PartialEq for Id { + fn eq(&self, other: &String) -> bool { + &self.0 == other + } +} + +impl FromStr for Id { + type Err = IdError; + + fn from_str(s: &str) -> Result { + Id::new(s) + } +} diff --git a/nextgen/common/src/lib.rs b/nextgen/common/src/lib.rs new file mode 100644 index 00000000000..8881b0e4079 --- /dev/null +++ b/nextgen/common/src/lib.rs @@ -0,0 +1,8 @@ +mod id; + +pub use id::*; + +// Error handling +pub use miette::Diagnostic; +pub use starbase_styles::*; +pub use thiserror::Error; diff --git a/crates/core/target/Cargo.toml b/nextgen/target/Cargo.toml similarity index 59% rename from crates/core/target/Cargo.toml rename to nextgen/target/Cargo.toml index 0a83a7941d7..2ceceed5ad4 100644 --- a/crates/core/target/Cargo.toml +++ b/nextgen/target/Cargo.toml @@ -4,7 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -moon_utils = { path = "../utils" } +moon_common = { path = "../common" } +once_cell = { workspace = true } +regex = { workspace = true } serde = { workspace = true } -starbase_styles = { workspace = true } thiserror = { workspace = true } diff --git a/nextgen/target/src/errors.rs b/nextgen/target/src/errors.rs new file mode 100644 index 00000000000..211ddf75182 --- /dev/null +++ b/nextgen/target/src/errors.rs @@ -0,0 +1,28 @@ +use moon_common::{Diagnostic, Error, IdError, Style, Stylize}; + +#[derive(Error, Debug)] +pub enum TargetError { + #[error( + "Invalid target {}, must be in the format of \"scope:task\".", .0.style(Style::Label) + )] + InvalidFormat(String), + + #[error( + "All scope (:) is not supported in task deps, for target {}.", .0.style(Style::Label) + )] + NoAllInTaskDeps(String), + + #[error("Dependencies scope (^:) is not supported in run contexts.")] + NoDepsInRunContext, + + #[error("Self scope (~:) is not supported in run contexts.")] + NoSelfInRunContext, + + #[error("Target \":\" encountered. Wildcard scope and task not supported.")] + TooWild, + + #[error(transparent)] + IdError(#[from] IdError), +} + +impl Diagnostic for TargetError {} diff --git a/crates/core/target/src/lib.rs b/nextgen/target/src/lib.rs similarity index 59% rename from crates/core/target/src/lib.rs rename to nextgen/target/src/lib.rs index b798341cd6e..d10b4114edb 100644 --- a/crates/core/target/src/lib.rs +++ b/nextgen/target/src/lib.rs @@ -1,5 +1,7 @@ mod errors; mod target; +mod target_scope; pub use errors::*; pub use target::*; +pub use target_scope::*; diff --git a/nextgen/target/src/target.rs b/nextgen/target/src/target.rs new file mode 100644 index 00000000000..0136b181ef0 --- /dev/null +++ b/nextgen/target/src/target.rs @@ -0,0 +1,179 @@ +use crate::errors::TargetError; +use crate::target_scope::TargetScope; +use moon_common::{Id, ID_CHARS}; +use once_cell::sync::Lazy; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use std::{ + cmp::Ordering, + fmt::{self, Display}, +}; + +// The project scope supports `@` because of Node.js packages, +// but we don't want to support it in regular IDs! +pub static TARGET_PATTERN: Lazy = Lazy::new(|| { + Regex::new(&format!( + r"^(?P(?:[A-Za-z@#]{{1}}{chars}|\^|~))?:(?P{chars})$", + chars = ID_CHARS + )) + .unwrap() +}); + +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[serde(try_from = "String", into = "String")] +pub struct Target { + pub id: String, + pub scope: TargetScope, + pub scope_id: Option, + pub task_id: Id, +} + +impl Target { + pub fn new(project_id: S, task_id: T) -> Result + where + S: AsRef, + T: AsRef, + { + let project_id = project_id.as_ref(); + let task_id = task_id.as_ref(); + let scope = TargetScope::Project(Id::new(project_id)?); + + Ok(Target { + id: Target::format(&scope, task_id)?, + scope, + scope_id: Some(Id::raw(project_id)), + task_id: Id::new(task_id)?, + }) + } + + pub fn new_self(task_id: T) -> Result + where + T: AsRef, + { + let task_id = task_id.as_ref(); + + Ok(Target { + id: Target::format(TargetScope::OwnSelf, task_id)?, + scope: TargetScope::OwnSelf, + scope_id: None, + task_id: Id::new(task_id)?, + }) + } + + pub fn format(scope: S, task: T) -> Result + where + S: AsRef, + T: AsRef, + { + Ok(format!("{}:{}", scope.as_ref(), task.as_ref())) + } + + pub fn parse(target_id: &str) -> Result { + if target_id == ":" { + return Err(TargetError::TooWild); + } + + let Some(matches) = TARGET_PATTERN.captures(target_id) else { + return Err(TargetError::InvalidFormat(target_id.to_owned())); + }; + + let mut scope_id = None; + let scope = match matches.name("scope") { + Some(value) => match value.as_str() { + "" => TargetScope::All, + "^" => TargetScope::Deps, + "~" => TargetScope::OwnSelf, + id => { + if let Some(tag) = id.strip_prefix('#') { + scope_id = Some(Id::raw(tag)); + TargetScope::Tag(Id::raw(tag)) + } else { + scope_id = Some(Id::raw(id)); + TargetScope::Project(Id::raw(id)) + } + } + }, + None => TargetScope::All, + }; + + let task_id = Id::raw(matches.name("task").unwrap().as_str()); + + Ok(Target { + id: target_id.to_owned(), + scope, + scope_id, + task_id, + }) + } + + pub fn is_all_task(&self, task_id: &str) -> bool { + if matches!(&self.scope, TargetScope::All) { + return if let Some(id) = task_id.strip_prefix(':') { + self.task_id == id + } else { + self.task_id == task_id + }; + } + + false + } +} + +impl Default for Target { + fn default() -> Self { + Target { + id: "~:unknown".into(), + scope: TargetScope::OwnSelf, + scope_id: None, + task_id: Id::raw("unknown"), + } + } +} + +impl Display for Target { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.id) + } +} + +impl AsRef for Target { + fn as_ref(&self) -> &Target { + self + } +} + +impl AsRef for Target { + fn as_ref(&self) -> &str { + &self.id + } +} + +impl PartialOrd for Target { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Target { + fn cmp(&self, other: &Self) -> Ordering { + self.id.cmp(&other.id) + } +} + +// These traits are for converting targets within configuration +// into the `Target` object instead of strings. + +impl TryFrom for Target { + type Error = TargetError; + + fn try_from(value: String) -> Result { + Target::parse(&value) + } +} + +#[allow(clippy::from_over_into)] +impl Into for Target { + fn into(self) -> String { + self.id + } +} diff --git a/nextgen/target/src/target_scope.rs b/nextgen/target/src/target_scope.rs new file mode 100644 index 00000000000..1013a33bf55 --- /dev/null +++ b/nextgen/target/src/target_scope.rs @@ -0,0 +1,29 @@ +use moon_common::Id; +use std::fmt::{self, Display}; + +#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd)] +pub enum TargetScope { + All, // :task + Deps, // ^:task + OwnSelf, // ~:task + Project(Id), // project:task + Tag(Id), // #tag:task +} + +impl Display for TargetScope { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TargetScope::All => write!(f, ""), + TargetScope::Deps => write!(f, "^"), + TargetScope::OwnSelf => write!(f, "~"), + TargetScope::Project(id) => write!(f, "{}", id), + TargetScope::Tag(id) => write!(f, "#{}", id), + } + } +} + +impl AsRef for TargetScope { + fn as_ref(&self) -> &TargetScope { + self + } +} diff --git a/nextgen/target/tests/target_test.rs b/nextgen/target/tests/target_test.rs new file mode 100644 index 00000000000..0855c654eeb --- /dev/null +++ b/nextgen/target/tests/target_test.rs @@ -0,0 +1,206 @@ +use moon_common::Id; +use moon_target::{Target, TargetScope}; + +#[test] +#[should_panic(expected = "InvalidFormat(\"foo$:build\")")] +fn errors_on_invalid_chars() { + Target::parse("foo$:build").unwrap(); +} + +#[test] +#[should_panic(expected = "InvalidFormat(\"foo:@build\")")] +fn errors_on_invalid_task_no_at() { + Target::parse("foo:@build").unwrap(); +} + +#[test] +#[should_panic(expected = "TooWild")] +fn errors_on_too_wild() { + Target::parse(":").unwrap(); +} + +#[test] +fn format_all_scope() { + assert_eq!(Target::format(TargetScope::All, "build").unwrap(), ":build"); +} + +#[test] +fn format_deps_scope() { + assert_eq!( + Target::format(TargetScope::Deps, "build").unwrap(), + "^:build" + ); +} + +#[test] +fn format_self_scope() { + assert_eq!( + Target::format(TargetScope::OwnSelf, "build").unwrap(), + "~:build" + ); +} + +#[test] +fn format_project_scope() { + assert_eq!( + Target::format(TargetScope::Project(Id::raw("foo")), "build").unwrap(), + "foo:build" + ); +} + +#[test] +fn format_tag_scope() { + assert_eq!( + Target::format(TargetScope::Tag(Id::raw("foo")), "build").unwrap(), + "#foo:build" + ); +} + +#[test] +fn format_with_slashes() { + assert_eq!( + Target::format(TargetScope::Project(Id::raw("foo/sub")), "build/esm").unwrap(), + "foo/sub:build/esm" + ); +} + +#[test] +fn format_node_package() { + assert_eq!( + Target::format(TargetScope::Project(Id::raw("@scope/foo")), "build").unwrap(), + "@scope/foo:build" + ); +} + +#[test] +fn parse_ids() { + assert_eq!( + Target::parse("foo:build").unwrap(), + Target { + id: String::from("foo:build"), + scope: TargetScope::Project(Id::raw("foo")), + scope_id: Some(Id::raw("foo")), + task_id: Id::raw("build"), + } + ); +} + +#[test] +fn parse_deps_scope() { + assert_eq!( + Target::parse("^:build").unwrap(), + Target { + id: String::from("^:build"), + scope: TargetScope::Deps, + scope_id: None, + task_id: Id::raw("build"), + } + ); +} + +// #[test] +// fn parse_deps_scope_all_tasks() { +// assert_eq!( +// Target::parse("^:").unwrap(), +// Target { +// id: String::from("^:"), +// scope: TargetScope::Deps, +// task: TargetTask::All, +// } +// ); +// } + +#[test] +fn parse_self_scope() { + assert_eq!( + Target::parse("~:build").unwrap(), + Target { + id: String::from("~:build"), + scope: TargetScope::OwnSelf, + scope_id: None, + task_id: Id::raw("build"), + } + ); +} + +// #[test] +// fn parse_self_scope_all_tasks() { +// assert_eq!( +// Target::parse("~:").unwrap(), +// Target { +// id: String::from("~:"), +// scope: TargetScope::Own, +// task: TargetTask::All, +// } +// ); +// } + +#[test] +fn parse_all_scopes() { + assert_eq!( + Target::parse(":build").unwrap(), + Target { + id: String::from(":build"), + scope: TargetScope::All, + scope_id: None, + task_id: Id::raw("build"), + } + ); +} + +// #[test] +// fn parse_all_tasks() { +// assert_eq!( +// Target::parse("foo:").unwrap(), +// Target { +// id: String::from("foo:"), +// scope: TargetScope::Id("foo".to_owned()), +// task: TargetTask::All, +// } +// ); +// } + +#[test] +fn parse_node_package() { + assert_eq!( + Target::parse("@scope/foo:build").unwrap(), + Target { + id: String::from("@scope/foo:build"), + scope: TargetScope::Project(Id::raw("@scope/foo")), + scope_id: Some(Id::raw("@scope/foo")), + task_id: Id::raw("build"), + } + ); +} + +#[test] +fn parse_slashes() { + assert_eq!( + Target::parse("foo/sub:build/esm").unwrap(), + Target { + id: String::from("foo/sub:build/esm"), + scope: TargetScope::Project(Id::raw("foo/sub")), + scope_id: Some(Id::raw("foo/sub")), + task_id: Id::raw("build/esm"), + } + ); +} + +#[test] +fn matches_all() { + let all = Target::parse(":lint").unwrap(); + + assert!(all.is_all_task("lint")); + assert!(all.is_all_task(":lint")); + assert!(!all.is_all_task("build")); + assert!(!all.is_all_task(":build")); + assert!(!all.is_all_task("foo:lint")); + + let full = Target::parse("foo:lint").unwrap(); + + assert!(!full.is_all_task("lint")); + assert!(!full.is_all_task(":lint")); + assert!(!full.is_all_task("build")); + assert!(!full.is_all_task(":build")); + assert!(!full.is_all_task("foo:lint")); +} diff --git a/website/docs/commands/run.mdx b/website/docs/commands/run.mdx index 23336ae0f35..e2dc11e3ff6 100644 --- a/website/docs/commands/run.mdx +++ b/website/docs/commands/run.mdx @@ -4,9 +4,9 @@ title: run import VersionLabel from '@site/src/components/Docs/VersionLabel'; -The `moon run` (or `moon r`) command will run one or many [targets](../concepts/target) (a task -within a project) and all of its dependencies in topological order. Each run will incrementally -cache each task, improving speed and development times... over time. +The `moon run` (or `moon r`) command will run one or many [targets](../concepts/target) and all of +its dependencies in topological order. Each run will incrementally cache each task, improving speed +and development times... over time. ```shell # Run `lint` in project `app` diff --git a/website/docs/concepts/project.mdx b/website/docs/concepts/project.mdx index 35889211349..f49c31d5423 100644 --- a/website/docs/concepts/project.mdx +++ b/website/docs/concepts/project.mdx @@ -30,8 +30,8 @@ in moon. Instead, they are specific to a project's primary programming language, based on that context (when enabled in settings). For example, a JavaScript or TypeScript project will use the `name` field from its `package.json` as the alias. -Because of this, a project can either be reference by its name or alias, or both. Choose the pattern -that makes the most sense for your company or team! +Because of this, a project can either be referenced by its name or alias, or both. Choose the +pattern that makes the most sense for your company or team! ## Dependencies diff --git a/website/docs/concepts/target.mdx b/website/docs/concepts/target.mdx index 21dbc60a5ca..6aadb85f963 100644 --- a/website/docs/concepts/target.mdx +++ b/website/docs/concepts/target.mdx @@ -2,8 +2,8 @@ title: Targets --- -A target is an identifier that pairs a [project](./project) to one of its [tasks](./task), in the -format of `project_name_or_alias:task_name`. +A target is a compound identifier that pairs a [scope](#scope) to a [task](./task), separated by a +`:`, in the format of `scope:task`. Targets are used by terminal commands... @@ -21,23 +21,26 @@ tasks: - 'designSystem:build' ``` -## Project scopes +## Scopes -While a target typically pairs project and task names, we also support advanced targets through a -concept known as project scopes. Scopes allow us to easily define targets with external or many -sources, but _are not available in all contexts_. +### Project + +The most common scope is the project scope, which requires the name of a project, as defined in +[`.moon/workspace.yml`](../config/workspace). This will run a specific task from that project. + +```shell +# Run `lint` in project `app` +$ moon run app:lint +``` ### All projects > Only available on the command line when running targets. For situations where you want to run a specific target in _all_ projects, for example `lint`ing, you -can utilize the all projects scope by omitting the project from the target: `:lint`. +can utilize the all projects scope by omitting the project name from the target: `:lint`. ```shell -# Run `lint` in project `app` -$ moon run app:lint - # Run `lint` in all projects $ moon run :lint ``` @@ -78,7 +81,7 @@ tasks: When referring to another task within the current project, you can utilize the `~` scope, or emit the `~:` prefix altogether, which will be expanded to the current project's name. This is useful for situations where the name is unknown, for example, when configuring -[`.moon/tasks.yml`](../config/tasks). Or if you just want a shortcut! +[`.moon/tasks.yml`](../config/tasks), or if you just want a shortcut! ```yaml title=".moon/tasks.yml" # Configured as diff --git a/website/docs/concepts/task.mdx b/website/docs/concepts/task.mdx index 300c646fb01..5f86b695bd2 100644 --- a/website/docs/concepts/task.mdx +++ b/website/docs/concepts/task.mdx @@ -14,7 +14,7 @@ explicitly configured as a key within the [`tasks`](../config/project#tasks) set written in camel/kebab/snake case. Names support `a-z`, `A-Z`, `0-9`, `_`, `-`, `/`, `.`, and must start with a character. -A task name can be paired with a project name to create a [target](./target). +A task name can be paired with a scope to create a [target](./target). ## Types diff --git a/website/docs/concepts/token.mdx b/website/docs/concepts/token.mdx index 953c5aaf473..59cefbf4a9b 100644 --- a/website/docs/concepts/token.mdx +++ b/website/docs/concepts/token.mdx @@ -438,7 +438,7 @@ tasks: ### `$target` -Target that is currently running. Is a combination of project and task name. +Target that is currently running. ```yaml # Configured as diff --git a/website/docs/config/project.mdx b/website/docs/config/project.mdx index f6b4867f1a3..f55c3528f23 100644 --- a/website/docs/config/project.mdx +++ b/website/docs/config/project.mdx @@ -641,9 +641,7 @@ following values: - `buffer` - Buffers output and displays after the task has exited (either success or failure). - `buffer-only-failure` - Like `buffer`, but only displays on failures. - - `hash` - Ignores output and only displays the generated [hash](../concepts/cache#hashing). - - `none` - Ignores output. - `stream` - Streams output directly to the terminal. Will prefix each line of output with the target. diff --git a/website/docs/config/workspace.mdx b/website/docs/config/workspace.mdx index 313813892a0..a0e5c1e0866 100644 --- a/website/docs/config/workspace.mdx +++ b/website/docs/config/workspace.mdx @@ -280,9 +280,9 @@ runner: -Defines a list of [targets](../concepts/target), with or without a project scope, that will be -cached and archived within the runner. Tasks that produce [outputs](./project#outputs) are -automatically archived, and do not need to be defined here. Defaults to an empty list. +Defines a list of [targets](../concepts/target), with or without scope, that will be cached and +archived within the runner. Tasks that produce [outputs](./project#outputs) are automatically +archived, and do not need to be defined here. Defaults to an empty list. ```yaml title=".moon/workspace.yml" {2-4} runner: diff --git a/website/docs/run-task.mdx b/website/docs/run-task.mdx index 72d4fd2e118..7310c14d8f0 100644 --- a/website/docs/run-task.mdx +++ b/website/docs/run-task.mdx @@ -11,8 +11,8 @@ import NextSteps from '@site/src/components/NextSteps'; Even though we've [created a task](./create-task), it's not useful unless we _run it_, which is done with the [`moon run `](./commands/run) command. This command requires a single argument, a -[primary target](./concepts/target), which is the pairing of a project name and task name. In the -example below, our project is `app`, the task is `build`, and the target is `app:build`. +[primary target](./concepts/target), which is the pairing of a scope and task name. In the example +below, our project is `app`, the task is `build`, and the target is `app:build`. ```shell $ moon run app:build