diff --git a/nextgen/project-expander/src/tasks_expander.rs b/nextgen/project-expander/src/tasks_expander.rs index e13c6c5cafe..33a6dabbd71 100644 --- a/nextgen/project-expander/src/tasks_expander.rs +++ b/nextgen/project-expander/src/tasks_expander.rs @@ -5,7 +5,7 @@ use moon_common::{color, Id}; use moon_config::InputPath; use moon_project::Project; use moon_task::{Target, TargetScope, Task}; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashSet; use starbase_utils::glob::GlobSet; use tracing::{trace, warn}; @@ -196,12 +196,13 @@ impl<'graph, 'query> TasksExpander<'graph, 'query> { "Expanding environment variables" ); - // Substitute environment variables - let cloned_env = task.env.clone(); - let mut env = FxHashMap::default(); + // Expand tokens + let mut env = self.token.expand_env(task)?; + let cloned_env = env.clone(); - for (key, val) in &task.env { - env.insert(key.to_owned(), substitute_env_var(val, &cloned_env)); + // Substitute environment variables + for (_, value) in env.iter_mut() { + *value = substitute_env_var(value, &cloned_env); } // Load variables from an .env file diff --git a/nextgen/project-expander/src/token_expander.rs b/nextgen/project-expander/src/token_expander.rs index 1e6a6446e57..f5d79b660ea 100644 --- a/nextgen/project-expander/src/token_expander.rs +++ b/nextgen/project-expander/src/token_expander.rs @@ -6,6 +6,7 @@ use moon_project::FileGroup; use moon_task::Task; use moon_time::{now_millis, now_timestamp}; use pathdiff::diff_paths; +use rustc_hash::FxHashMap; use tracing::warn; pub type ExpandedPaths = (Vec, Vec); @@ -14,6 +15,7 @@ pub type ExpandedPaths = (Vec, Vec "commands", TokenScope::Args => "args", + TokenScope::Env => "env", TokenScope::Inputs => "inputs", TokenScope::Outputs => "outputs", } @@ -127,6 +130,36 @@ impl<'graph, 'query> TokenExpander<'graph, 'query> { Ok(args) } + pub fn expand_env(&mut self, task: &Task) -> miette::Result> { + self.scope = TokenScope::Env; + + let mut env = FxHashMap::default(); + + for (key, value) in &task.env { + if self.has_token_function(value) { + let (files, globs) = self.replace_function(task, value)?; + + let mut list = vec![]; + list.extend(files); + list.extend(globs); + + env.insert( + key.to_owned(), + list.iter() + .map(|i| i.as_str()) + .collect::>() + .join(","), + ); + } else if self.has_token_variable(value) { + env.insert(key.to_owned(), self.replace_variables(task, value)?); + } else { + env.insert(key.to_owned(), value.to_owned()); + } + } + + Ok(env) + } + pub fn expand_inputs(&mut self, task: &Task) -> miette::Result { self.scope = TokenScope::Inputs; @@ -234,7 +267,12 @@ impl<'graph, 'query> TokenExpander<'graph, 'query> { let file_group = || -> miette::Result<&FileGroup> { self.check_scope( token, - &[TokenScope::Args, TokenScope::Inputs, TokenScope::Outputs], + &[ + TokenScope::Args, + TokenScope::Env, + TokenScope::Inputs, + TokenScope::Outputs, + ], )?; Ok(self.context.project.file_groups.get(arg).ok_or_else(|| { @@ -359,6 +397,7 @@ impl<'graph, 'query> TokenExpander<'graph, 'query> { &[ TokenScope::Command, TokenScope::Args, + TokenScope::Env, TokenScope::Inputs, TokenScope::Outputs, ], diff --git a/nextgen/project-expander/tests/tasks_expander_test.rs b/nextgen/project-expander/tests/tasks_expander_test.rs index 7b587f6aff6..6b86100106a 100644 --- a/nextgen/project-expander/tests/tasks_expander_test.rs +++ b/nextgen/project-expander/tests/tasks_expander_test.rs @@ -635,6 +635,52 @@ mod tasks_expander { ); } + #[test] + fn replaces_tokens() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.env.insert("KEY1".into(), "@globs(all)".into()); + task.env.insert("KEY2".into(), "$project-$task".into()); + + let context = create_context(&project, sandbox.path()); + TasksExpander::new(&context).expand_env(&mut task).unwrap(); + + assert_eq!( + task.env, + FxHashMap::from_iter([ + ( + "KEY1".into(), + "project/source/*.md,project/source/**/*.json".into() + ), + ("KEY2".into(), "project-task".into()), + ]) + ); + } + + #[test] + fn can_use_env_vars_and_token_vars() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.env + .insert("KEY".into(), "$project-$FOO-$unknown".into()); + + env::set_var("FOO", "foo"); + + let context = create_context(&project, sandbox.path()); + TasksExpander::new(&context).expand_env(&mut task).unwrap(); + + env::remove_var("FOO"); + + assert_eq!( + task.env, + FxHashMap::from_iter([("KEY".into(), "project-foo-$unknown".into()),]) + ); + } + #[test] fn loads_from_env_file() { let sandbox = create_sandbox("env-file"); diff --git a/nextgen/project-expander/tests/token_expander_test.rs b/nextgen/project-expander/tests/token_expander_test.rs index 655460ce9bc..f545911cf27 100644 --- a/nextgen/project-expander/tests/token_expander_test.rs +++ b/nextgen/project-expander/tests/token_expander_test.rs @@ -3,6 +3,7 @@ mod utils; use moon_common::path::WorkspaceRelativePathBuf; use moon_config::{InputPath, LanguageType, OutputPath, ProjectType}; use moon_project_expander::TokenExpander; +use rustc_hash::FxHashMap; use starbase_sandbox::{create_empty_sandbox, create_sandbox, predicates::prelude::*}; use utils::{create_context, create_project, create_task}; @@ -272,6 +273,186 @@ mod token_expander { } } + mod env { + use super::*; + + #[test] + fn passes_through() { + let sandbox = create_empty_sandbox(); + let project = create_project(sandbox.path()); + let mut task = create_task(); + + task.env.insert("KEY".into(), "value".into()); + + let context = create_context(&project, sandbox.path()); + let mut expander = TokenExpander::new(&context); + + assert_eq!( + expander.expand_env(&task).unwrap(), + FxHashMap::from_iter([("KEY".into(), "value".into())]) + ); + } + + #[test] + fn replaces_one_var() { + let sandbox = create_empty_sandbox(); + let project = create_project(sandbox.path()); + let mut task = create_task(); + + task.env.insert("VAR".into(), "$project-prod".into()); + + let context = create_context(&project, sandbox.path()); + let mut expander = TokenExpander::new(&context); + + assert_eq!( + expander.expand_env(&task).unwrap(), + FxHashMap::from_iter([("VAR".into(), "project-prod".into())]) + ); + } + + #[test] + fn replaces_two_vars() { + let sandbox = create_empty_sandbox(); + let project = create_project(sandbox.path()); + let mut task = create_task(); + + task.env + .insert("VARS".into(), "$project-debug-$task".into()); + + let context = create_context(&project, sandbox.path()); + let mut expander = TokenExpander::new(&context); + + assert_eq!( + expander.expand_env(&task).unwrap(), + FxHashMap::from_iter([("VARS".into(), "project-debug-task".into())]) + ); + } + + #[test] + fn supports_group_func() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + let mut task = create_task(); + + task.env.insert("GROUP".into(), "@group(all)".into()); + + let context = create_context(&project, sandbox.path()); + let mut expander = TokenExpander::new(&context); + + assert_eq!( + expander.expand_env(&task).unwrap(), + FxHashMap::from_iter([("GROUP".into(), "project/source/config.yml,project/source/dir/subdir,project/source/*.md,project/source/**/*.json".into())]) + ); + } + + #[test] + fn supports_dirs_func() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + let mut task = create_task(); + + task.env.insert("DIRS".into(), "@dirs(dirs)".into()); + + let context = create_context(&project, sandbox.path()); + let mut expander = TokenExpander::new(&context); + + assert_eq!( + expander.expand_env(&task).unwrap(), + FxHashMap::from_iter([( + "DIRS".into(), + "project/source/dir/subdir,project/source/other".into() + )]) + ); + } + + #[test] + fn supports_files_func() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + let mut task = create_task(); + + task.env.insert("FILES".into(), "@files(all)".into()); + + let context = create_context(&project, sandbox.path()); + let mut expander = TokenExpander::new(&context); + + assert_eq!( + expander.expand_env(&task).unwrap(), + FxHashMap::from_iter([( + "FILES".into(), + "project/source/config.yml,project/source/dir/subdir/nested.json,project/source/docs.md,project/source/other/file.json".into() + )]) + ); + } + + #[test] + fn supports_globs_func() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + let mut task = create_task(); + + task.env.insert("GLOBS".into(), "@globs(all)".into()); + + let context = create_context(&project, sandbox.path()); + let mut expander = TokenExpander::new(&context); + + assert_eq!( + expander.expand_env(&task).unwrap(), + FxHashMap::from_iter([( + "GLOBS".into(), + "project/source/*.md,project/source/**/*.json".into() + )]) + ); + } + + #[test] + fn supports_root_func() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + let mut task = create_task(); + + task.env.insert("ROOT".into(), "@root(all)".into()); + + let context = create_context(&project, sandbox.path()); + let mut expander = TokenExpander::new(&context); + + assert_eq!( + expander.expand_env(&task).unwrap(), + FxHashMap::from_iter([("ROOT".into(), "project/source/dir/subdir".into())]) + ); + } + + #[test] + #[should_panic(expected = "Token @in(0) cannot be used within task env.")] + fn errors_for_in_func() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + let mut task = create_task(); + + task.env.insert("IN".into(), "@in(0)".into()); + + let context = create_context(&project, sandbox.path()); + let mut expander = TokenExpander::new(&context); + + expander.expand_env(&task).unwrap(); + } + + #[test] + #[should_panic(expected = "Token @out(0) cannot be used within task env.")] + fn errors_for_out_func() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + let mut task = create_task(); + + task.env.insert("OUT".into(), "@out(0)".into()); + + let context = create_context(&project, sandbox.path()); + let mut expander = TokenExpander::new(&context); + + expander.expand_env(&task).unwrap(); + } + } + mod inputs { use super::*; diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index e3cde90fd63..6ceff42d6b5 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -6,6 +6,7 @@ - Added an `extends` field to task configurations. This allows tasks to extend and inherit settings from sibling tasks. +- Updated task `env` values to support token functions and variables. ## 1.11.1 diff --git a/website/blog/2023-01-30_v0.23.mdx b/website/blog/2023-01-30_v0.23.mdx index a7f1f7d4909..1e6485cf1aa 100644 --- a/website/blog/2023-01-30_v0.23.mdx +++ b/website/blog/2023-01-30_v0.23.mdx @@ -77,7 +77,7 @@ tasks: The biggest change to task inheritance is that tasks can now be scoped by a project's [`language`](/docs/config/project#language) or [`type`](/docs/config/project#type) using the new `.moon/tasks/.yml` or `.moon/tasks/-.yml` configuration files! Jump to the -[official documentation on task inheritance](/docs/concepts/task#inheritance) for more information +[official documentation on task inheritance](/docs/concepts/task-inheritance) for more information on how scoping works, the lookup order of files, and much more. As a demonstration, you can scope tasks to Node.js projects with `.moon/tasks/node.yml`, Rust diff --git a/website/blog/2023-04-17_moon-v1.2.mdx b/website/blog/2023-04-17_moon-v1.2.mdx index 53ea0248808..4abbb9cf0ca 100644 --- a/website/blog/2023-04-17_moon-v1.2.mdx +++ b/website/blog/2023-04-17_moon-v1.2.mdx @@ -69,7 +69,7 @@ tasks: Each of these Astro applications will now inherit all 5 tasks and the file group automatically! This helps to greatly reduce maintenance overhead and help enforce consistency across projects. Jump to -the official [task inheritance docs](/docs/concepts/task#inheritance) for more information on tag +the official [task inheritance docs](/docs/concepts/task-inheritance) for more information on tag based inheritance. ## Other changes diff --git a/website/blog/2023-06-12_moon-v1.8.mdx b/website/blog/2023-06-12_moon-v1.8.mdx index 4ce067028ce..eac2b594f0d 100644 --- a/website/blog/2023-06-12_moon-v1.8.mdx +++ b/website/blog/2023-06-12_moon-v1.8.mdx @@ -116,7 +116,7 @@ configurations for popular programming languages, frameworks, libraries, and mor repository is kind of empty, but we're hoping to grow it over time, so feel free to contribute! If you're curious how this works in practice, we'll use our Rust configuration as an example. The -entire system is based around [tag inheritance](/docs/concepts/task#inheritance), where a project +entire system is based around [tag inheritance](/docs/concepts/task-inheritance), where a project can inherit tasks from a remote source, and then extend or override them as needed. For example, create the tag-based config: diff --git a/website/blog/2023-08-21_moon-v1.12.mdx b/website/blog/2023-08-21_moon-v1.12.mdx index 6bff0dd54e2..08a1129a4ce 100644 --- a/website/blog/2023-08-21_moon-v1.12.mdx +++ b/website/blog/2023-08-21_moon-v1.12.mdx @@ -1,6 +1,6 @@ --- slug: moon-v1.12 -title: moon v1.12 - Task extending +title: moon v1.12 - Task extending, tokens in env vars, and more authors: [milesj] tags: [tasks, inheritance] # image: ./img/moon/v1.11.png @@ -81,11 +81,49 @@ functionality behind the scenes, which we believe to have achieved. Some other interesting facts around task extending: -- When extending a task, [merge strategies](/docs/concepts/task#merge-strategies) are applied in a - similar fashion to inheritance. +- When extending a task, [merge strategies](/docs/concepts/task-inheritance#merge-strategies) are + applied in a similar fashion to inheritance. - Inherited tasks can be extended from by project-level tasks. - It's possible to create multiple extended chains. +## Tokens in enviroment variables + +Up until now, [token functions and variables](/docs/concepts/token) were only supported in task +commands, args, inputs, and outputs, but not environment variables... why? Honestly, there was no +real reason they weren't supported, it simply never crossed our mind! But thanks to requests from +the community, both token functions and variables are now supported in task +[`env`](/docs/config/project#env-1). + +This is great for propagating moon values to other systems. For example, say you want to use moon +project names for Sentry, keeping a 1:1 mapping. + +```yaml title="moon.yml" +tasks: + start: + command: 'run-server' + env: + SENTRY_PROJECT: '$project' +``` + +If you're familiar with tokens, you may be asking yourself, "How do token functions work since they +expand to a list of paths?" That's a great question! When token functions are used in an environment +variable, like `@group(sources)`, the list of paths will be joined with a comma (`,`). + +```yaml title="moon.yml" +tasks: + build: + # ... + env: + SRCS: '@group(sources)' +``` + +Since the environment variable is simply a string, you could parse it with your language of choice +to extract the list of paths. + +```js +const paths = process.env.SRCS.split(','); +``` + ## Other changes View the [official release](https://github.com/moonrepo/moon/releases/tag/v1.12.0) for a full list diff --git a/website/docs/comparison.mdx b/website/docs/comparison.mdx index 1e189d0c0c4..f61943a7478 100644 --- a/website/docs/comparison.mdx +++ b/website/docs/comparison.mdx @@ -33,7 +33,7 @@ centric task runners do not, such as... - **[Integrated toolchain](./concepts/toolchain)** - moon manages its own version of programming languages and dependency managers behind the scenes, so that every task is executed with the _exact same version_, across _all machines_. -- **[Task inheritance](./concepts/task#inheritance)** - Instead of defining the same tasks (lint, +- **[Task inheritance](./concepts/task-inheritance)** - Instead of defining the same tasks (lint, test, etc) over and over again for _every_ project in the monorepo, moon supports a task inheritance model where it only needs to be defined once at the top-level. Projects can then merge with, exclude, or override if need be. diff --git a/website/docs/concepts/task-inheritance.mdx b/website/docs/concepts/task-inheritance.mdx new file mode 100644 index 00000000000..e16c562103d --- /dev/null +++ b/website/docs/concepts/task-inheritance.mdx @@ -0,0 +1,139 @@ +--- +title: Task inheritance +--- + +Unlike other task runners that require the same tasks to be repeatedly defined for _every_ project, +moon uses an inheritance model where tasks can be defined once at the workspace-level, and are then +inherited by _many or all_ projects. + +Workspace-level tasks (also known as global tasks) are defined in [`.moon/tasks.yml`][tasks] or +[`.moon/tasks/*.yml`][tasks], and are inherited by default. However, projects are able to include, +exclude, or rename inherited tasks using the +[`workspace.inheritedTasks`](../config/project#inheritedtasks) in [`moon.yml`](../config/project). + +## Scope by project metadata + +By default tasks defined in [`.moon/tasks.yml`][tasks] will be inherited by _all_ projects. This +approach works well when a monorepo is comprised of a single programming language, but breaks down +quickly in multi-language setups. + +To support these complex repositories, we support scoped tasks with [`.moon/tasks/*.yml`][tasks], +where `*.yml` maps to a project based on a combination of its [language][language], [type][type], or +[tags][tags]. This enables you to easily declare tasks for "JavaScript projects", "Go applications", +"Ruby libraries", so on and so forth. + +When resolving configuration files, moon will locate and _shallow_ merge files in the following +order, from widest scope to narrowest scope: + +- `.moon/tasks.yml` - All projects. +- `.moon/tasks/.yml` - Projects with a matching [`language`][language] setting. +- `.moon/tasks/-.yml` - Projects with matching [`language`][language] and + [`type`][type] settings. +- `.moon/tasks/tag-.yml` - Projects with a matching [`tag`][tags]. + + +As mentioned above, all of these files are shallow merged into a single "global tasks" configuration +that is unique per-project. Merging **does not** utilize the [merge strategies](#merge-strategies) +below, as those strategies are only utilized when merging global and local tasks. + +> Tags are resolved in the order they are defined in `moon.yml` `tags` setting. + +### JavaScript platforms + +Unlike most languages that have 1 runtime, JavaScript has 3 (Node.js, Deno, Bun), and we must +support repositories that are comprised of any combination of these 3. As such, JavaScript (and +TypeScript) based projects have a special lookup order using +[`platform`](../config/project#platform-1) to account for this: + +- `.moon/tasks.yml` +- `.moon/tasks/.yml` +- `.moon/tasks/.yml` +- `.moon/tasks/-.yml` +- `.moon/tasks/-.yml` +- `.moon/tasks/tag-.yml` + +For example, `node.yml` would be inherited for Node.js projects, `bun-library.yml` for Bun +libraries, and `deno-application.yml` for Deno applications. While `javascript.yml`, +`typescript-library.yml`, etc, will be inherited for all platforms. + +## Merge strategies + +When a [global task](../config/tasks#tasks) and [local task](../config/project#tasks) of the same +name exist, they are merged into a single task. To accomplish this, one of many +[merge strategies](../config/project#options) can be used. + +Merging is applied to the parameters [`args`](../config/project#args), +[`deps`](../config/project#deps), [`env`](../config/project#env-1), +[`inputs`](../config/project#inputs), and [`outputs`](../config/project#outputs), using the +[`mergeArgs`](../config/project#mergeargs), [`mergeDeps`](../config/project#mergedeps), +[`mergeEnv`](../config/project#mergeenv), [`mergeInputs`](../config/project#mergeinputs) and +[`mergeOutputs`](../config/project#mergeoutputs) options respectively. Each of these options support +one of the following strategy values. + +- `append` (default) - Values found in the local task are merged _after_ the values found in the + global task. For example, this strategy is useful for toggling flag arguments. +- `prepend` - Values found in the local task are merged _before_ the values found in the global + task. For example, this strategy is useful for applying option arguments that must come before + positional arguments. +- `replace` - Values found in the local task entirely _replaces_ the values in the global task. This + strategy is useful when you need full control. + +All 3 of these strategies are demonstrated below, with a somewhat contrived example, but you get the +point. + +```yaml +# Global +tasks: + build: + command: + - 'webpack' + - '--mode' + - 'production' + - '--color' + deps: + - 'designSystem:build' + inputs: + - '/webpack.config.js' + outputs: + - 'build/' + +# Local +tasks: + build: + args: '--no-color --no-stats' + deps: + - 'reactHooks:build' + inputs: + - 'webpack.config.js' + options: + mergeArgs: 'append' + mergeDeps: 'prepend' + mergeInputs: 'replace' + +# Merged result +tasks: + build: + command: + - 'webpack' + - '--mode' + - 'production' + - '--color' + - '--no-color' + - '--no-stats' + deps: + - 'reactHooks:build' + - 'designSystem:build' + inputs: + - 'webpack.config.js' + outputs: + - 'build/' + options: + mergeArgs: 'append' + mergeDeps: 'prepend' + mergeInputs: 'replace' +``` + +[tags]: ../config/project#tags +[tasks]: ../config/tasks +[language]: ../config/project#language +[type]: ../config/project#type diff --git a/website/docs/concepts/task.mdx b/website/docs/concepts/task.mdx index 02e85698b9b..1a7f200068f 100644 --- a/website/docs/concepts/task.mdx +++ b/website/docs/concepts/task.mdx @@ -30,142 +30,8 @@ Tasks are categorized into 1 of the following types based on their configured pa ## Configuration Tasks can be configured per project through [`moon.yml`](../config/project), or for many projects -through [`.moon/tasks.yml`][tasks]. +through [`.moon/tasks.yml`](../config/tasks). ## Inheritance -Unlike other task runners that require the same tasks to be repeatedly defined for _every_ project, -moon uses an inheritance model where tasks can be defined once at the workspace-level, and are then -inherited by _many or all_ projects. - -Workspace-level tasks (also known as global tasks) are defined in [`.moon/tasks.yml`][tasks] or -[`.moon/tasks/*.yml`][tasks], and are inherited by default. However, projects are able to include, -exclude, or rename inherited tasks using the -[`workspace.inheritedTasks`](../config/project#inheritedtasks) in [`moon.yml`](../config/project). - -### Scope by project metadata - -By default tasks defined in [`.moon/tasks.yml`][tasks] will be inherited by _all_ projects. This -approach works well when a monorepo is comprised of a single programming language, but breaks down -quickly in multi-language setups. - -To support these complex repositories, we support scoped tasks with [`.moon/tasks/*.yml`][tasks], -where `*.yml` maps to a project based on a combination of its [language][language], [type][type], or -[tags][tags]. This enables you to easily declare tasks for "JavaScript projects", "Go applications", -"Ruby libraries", so on and so forth. - -When resolving configuration files, moon will locate and _shallow_ merge files in the following -order, from widest scope to narrowest scope: - -- `.moon/tasks.yml` - All projects. -- `.moon/tasks/.yml` - Projects with a matching [`language`][language] setting. -- `.moon/tasks/-.yml` - Projects with matching [`language`][language] and - [`type`][type] settings. -- `.moon/tasks/tag-.yml` - Projects with a matching [`tag`][tags]. - - -As mentioned above, all of these files are shallow merged into a single "global tasks" configuration -that is unique per-project. Merging **does not** utilize the [merge strategies](#merge-strategies) -below, as those strategies are only utilized when merging global and local tasks. - -> Tags are resolved in the order they are defined in `moon.yml` `tags` setting. - -#### JavaScript platforms - -Unlike most languages that have 1 runtime, JavaScript has 3 (Node.js, Deno, Bun), and we must -support repositories that are comprised of any combination of these 3. As such, JavaScript (and -TypeScript) based projects have a special lookup order using -[`platform`](../config/project#platform-1) to account for this: - -- `.moon/tasks.yml` -- `.moon/tasks/.yml` -- `.moon/tasks/.yml` -- `.moon/tasks/-.yml` -- `.moon/tasks/-.yml` -- `.moon/tasks/tag-.yml` - -For example, `node.yml` would be inherited for Node.js projects, `bun-library.yml` for Bun -libraries, and `deno-application.yml` for Deno applications. While `javascript.yml`, -`typescript-library.yml`, etc, will be inherited for all platforms. - -### Merge strategies - -When a [global task](../config/tasks#tasks) and [local task](../config/project#tasks) of the same -name exist, they are merged into a single task. To accomplish this, one of many -[merge strategies](../config/project#options) can be used. - -Merging is applied to the parameters [`args`](../config/project#args), -[`deps`](../config/project#deps), [`env`](../config/project#env-1), -[`inputs`](../config/project#inputs), and [`outputs`](../config/project#outputs), using the -[`mergeArgs`](../config/project#mergeargs), [`mergeDeps`](../config/project#mergedeps), -[`mergeEnv`](../config/project#mergeenv), [`mergeInputs`](../config/project#mergeinputs) and -[`mergeOutputs`](../config/project#mergeoutputs) options respectively. Each of these options support -one of the following strategy values. - -- `append` (default) - Values found in the local task are merged _after_ the values found in the - global task. For example, this strategy is useful for toggling flag arguments. -- `prepend` - Values found in the local task are merged _before_ the values found in the global - task. For example, this strategy is useful for applying option arguments that must come before - positional arguments. -- `replace` - Values found in the local task entirely _replaces_ the values in the global task. This - strategy is useful when you need full control. - -All 3 of these strategies are demonstrated below, with a somewhat contrived example, but you get the -point. - -```yaml -# Global -tasks: - build: - command: - - 'webpack' - - '--mode' - - 'production' - - '--color' - deps: - - 'designSystem:build' - inputs: - - '/webpack.config.js' - outputs: - - 'build/' - -# Local -tasks: - build: - args: '--no-color --no-stats' - deps: - - 'reactHooks:build' - inputs: - - 'webpack.config.js' - options: - mergeArgs: 'append' - mergeDeps: 'prepend' - mergeInputs: 'replace' - -# Merged result -tasks: - build: - command: - - 'webpack' - - '--mode' - - 'production' - - '--color' - - '--no-color' - - '--no-stats' - deps: - - 'reactHooks:build' - - 'designSystem:build' - inputs: - - 'webpack.config.js' - outputs: - - 'build/' - options: - mergeArgs: 'append' - mergeDeps: 'prepend' - mergeInputs: 'replace' -``` - -[tags]: ../config/project#tags -[tasks]: ../config/tasks -[language]: ../config/project#language -[type]: ../config/project#type +View the official documentation on [task inheritance](./task-inheritance). diff --git a/website/docs/concepts/token.mdx b/website/docs/concepts/token.mdx index 6c9f46b3122..baf92728015 100644 --- a/website/docs/concepts/token.mdx +++ b/website/docs/concepts/token.mdx @@ -3,10 +3,10 @@ title: Tokens --- Tokens are variables and functions that can be used by [`command`](../config/project#command), -[`args`](../config/project#args), [`inputs`](../config/project#inputs), and -[`outputs`](../config/project#outputs) when configuring a task. They provide a way of accessing file -group paths, referencing values from other task fields, and referencing metadata about the project -and task itself. +[`args`](../config/project#args), [`env`](../config/project#env) (>= v1.12), +[`inputs`](../config/project#inputs), and [`outputs`](../config/project#outputs) when configuring a +task. They provide a way of accessing file group paths, referencing values from other task fields, +and referencing metadata about the project and task itself. ## Functions @@ -14,8 +14,12 @@ A token function is labeled as such as it takes a single argument, starts with a formatted as `@name(arg)`. The following token functions are available, grouped by their functionality. -> Token functions _must_ be the only content within a list item, as they expand to multiple file -> paths. +:::caution + +Token functions _must_ be the only content within a value, as they expand to multiple files. When +used in an `env` value, multiple files are joined with a comma (`,`). + +::: ### File groups @@ -23,7 +27,7 @@ These functions reference file groups by name, where the name is passed as the a ### `@group` -> Usable in `args`, `inputs`, and `outputs`. +> Usable in `args`, `env`, `inputs`, and `outputs`. The `@group(file_group)` token is a standard token that will be replaced with the file group items as-is, for both file paths and globs. This token merely exists for reusability purposes. @@ -62,7 +66,7 @@ tasks: ### `@dirs` -> Usable in `args`, `inputs`, and `outputs`. +> Usable in `args`, `env`, `inputs`, and `outputs`. The `@dirs(file_group)` token will be replaced with an expanded list of directory paths, derived from the file group of the same name. If a glob pattern is detected within the file group, it will @@ -100,7 +104,7 @@ tasks: ### `@files` -> Usable in `args`, `inputs`, and `outputs`. +> Usable in `args`, `env`, `inputs`, and `outputs`. The `@files(file_group)` token will be replaced with an expanded list of file paths, derived from the file group of the same name. If a glob pattern is detected within the file group, it will walk @@ -136,7 +140,7 @@ tasks: ### `@globs` -> Usable in `args`, `inputs`, and `outputs`. +> Usable in `args`, `env`, `inputs`, and `outputs`. The `@globs(file_group)` token will be replaced with the list of glob patterns as-is, derived from the file group of the same name. If a non-glob pattern is detected within the file group, it will be @@ -170,7 +174,7 @@ tasks: ### `@root` -> Usable in `args`, `inputs`, and `outputs`. +> Usable in `args`, `env`, `inputs`, and `outputs`. The `@root(file_group)` token will be replaced with the lowest common directory, derived from the file group of the same name. If a glob pattern is detected within the file group, it will walk the @@ -276,8 +280,6 @@ tasks: ## Variables -> Usable in `command`, `args`, and `inputs` only. - A token variable is a value that starts with `$` and is substituted to a value derived from the current workspace, project, and task. And unlike token functions, token variables can be placed _within_ content when necessary, and supports multiple variables within the same content. diff --git a/website/docs/config/project.mdx b/website/docs/config/project.mdx index 5e7473c3d22..c3ea85f3112 100644 --- a/website/docs/config/project.mdx +++ b/website/docs/config/project.mdx @@ -240,7 +240,7 @@ suggest _not_ listing people/developers as the owner, use [maintainers](#maintai Tags are a simple mechanism for categorizing projects. They can be used to group projects together for [easier querying](../commands/query/projects), enforcing of [project boundaries and constraints](./workspace#constraints), -[task inheritance](../concepts/task#inheritance), and more. +[task inheritance](../concepts/task-inheritance), and more. ```yaml title="moon.yml" tags: @@ -355,11 +355,11 @@ tasks: The `extends` field can be used to extend the settings from a sibling task within the same project, -or [inherited from the global scope](../concepts/task#inheritance). This is useful for composing +or [inherited from the global scope](../concepts/task-inheritance). This is useful for composing similar tasks with different arguments or options. -When extending another task, the same [merge strategies](../concepts/task#merge-strategies) used for -inheritance are applied. +When extending another task, the same +[merge strategies](../concepts/task-inheritance#merge-strategies) used for inheritance are applied. ```yaml title="moon.yml" tasks: @@ -637,7 +637,7 @@ tasks: The `options` field is an object of configurable options that can be used to modify the task and its execution. The following fields can be provided, with merge related fields supporting all -[merge strategies](../concepts/task#merge-strategies). +[merge strategies](../concepts/task-inheritance#merge-strategies). ```yaml title="moon.yml" {6-8} tasks: @@ -733,36 +733,36 @@ differences are around quote handling and variable substitution, so be aware of -The [strategy](../concepts/task#merge-strategies) to use when merging the [`args`](#args) list with -an inherited task. Defaults to "append". +The [strategy](../concepts/task-inheritance#merge-strategies) to use when merging the +[`args`](#args) list with an inherited task. Defaults to "append". #### `mergeDeps` -The [strategy](../concepts/task#merge-strategies) to use when merging the [`deps`](#deps) list with -an inherited task. Defaults to "append". +The [strategy](../concepts/task-inheritance#merge-strategies) to use when merging the +[`deps`](#deps) list with an inherited task. Defaults to "append". #### `mergeEnv` -The [strategy](../concepts/task#merge-strategies) to use when merging the [`env`](#env-1) map with -an inherited task. Defaults to "append". +The [strategy](../concepts/task-inheritance#merge-strategies) to use when merging the +[`env`](#env-1) map with an inherited task. Defaults to "append". #### `mergeInputs` -The [strategy](../concepts/task#merge-strategies) to use when merging the [`inputs`](#inputs) list -with an inherited task. Defaults to "append". +The [strategy](../concepts/task-inheritance#merge-strategies) to use when merging the +[`inputs`](#inputs) list with an inherited task. Defaults to "append". #### `mergeOutputs` -The [strategy](../concepts/task#merge-strategies) to use when merging the [`outputs`](#outputs) list -with an inherited task. Defaults to "append". +The [strategy](../concepts/task-inheritance#merge-strategies) to use when merging the +[`outputs`](#outputs) list with an inherited task. Defaults to "append". #### `outputStyle` diff --git a/website/docs/config/tasks.mdx b/website/docs/config/tasks.mdx index 1641ff853ad..69f84bac43b 100644 --- a/website/docs/config/tasks.mdx +++ b/website/docs/config/tasks.mdx @@ -7,7 +7,7 @@ import HeadingApiLink from '@site/src/components/Docs/HeadingApiLink'; The `.moon/tasks.yml` file configures file groups and tasks that are inherited by _every_ project in the workspace, while `.moon/tasks/*.yml` configures for projects based on their language or type. -[Learn more about task inheritance!](../concepts/task#inheritance) +[Learn more about task inheritance!](../concepts/task-inheritance) Projects can override or merge with these settings within their respective [`moon.yml`](./project). diff --git a/website/sidebars.js b/website/sidebars.js index 5b5f50f4f0b..cb0bcab5f2d 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -52,6 +52,7 @@ const sidebars = { 'concepts/project', 'concepts/target', 'concepts/task', + 'concepts/task-inheritance', 'concepts/token', 'concepts/toolchain', 'concepts/workspace', diff --git a/website/src/components/Docs/VersionLabel.tsx b/website/src/components/Docs/VersionLabel.tsx index 42622c03fd2..9664253daac 100644 --- a/website/src/components/Docs/VersionLabel.tsx +++ b/website/src/components/Docs/VersionLabel.tsx @@ -3,16 +3,18 @@ import Label from '../../ui/typography/Label'; interface VersionLabelProps { header?: boolean; + inline?: boolean; updated?: string; version: string; } -export default function VersionLabel({ header, updated, version }: VersionLabelProps) { +export default function VersionLabel({ header, inline, updated, version }: VersionLabelProps) { return (