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/**/* , and are inherited based on conditions. However, projects are able to include, exclude, or rename inherited tasks using the workspace.inheritedTasks in moon.yml .
Overview
Task inheritance enables:
DRY principle - Define common tasks once
Consistency - Same task configuration across projects
Flexibility - Override or extend tasks per-project
Maintenance - Update tasks in one place
Conditional Inheritance
Task inheritance is powered by the inheritedBy setting in global task configurations (those in .moon/tasks/**/* ). This setting defines conditions that a project must meet in order for inheritance to occur. If the setting is not defined, or no conditions are defined, the configuration is inherited by all projects.
The following conditions are supported:
file, files - Inherit for projects that contain specific files (does not support globs)
language, languages - Inherit for projects that belong to specific language s
layer, layers - Inherit for projects that belong to specific layer s
stack, stacks - Inherit for projects that belong to specific stack s
tag, tags - Inherit for projects that have specific tags
toolchain, toolchains - Inherit for projects that belong to specific toolchains
Single Condition
One or many conditions can be defined, and all conditions must be met for inheritance to occur. For example, the following configuration will only be inherited by Node.js frontend libraries:
.moon/tasks/node-frontend-library.yml
inheritedBy :
toolchain : 'node'
stack : 'frontend'
layer : 'library'
tasks :
build :
command : 'tsup'
outputs :
- 'dist'
Multiple Conditions
Each condition supports a single value or an array of values. For example, the previous example could be rewritten to inherit for both Node.js or Deno frontend libraries:
.moon/tasks/js-frontend-library.yml
inheritedBy :
toolchains : [ 'node' , 'deno' ]
stack : 'frontend'
layer : 'library'
tasks :
build :
command : 'build-tool'
outputs :
- 'dist'
Clauses
The tags and toolchains conditions support special clauses and, or (the default), and not for matching more complex scenarios.
.moon/tasks/conditional.yml
inheritedBy :
toolchains :
or : [ 'javascript' , 'typescript' ]
not : [ 'ruby' ]
tags :
and : [ 'frontend' , 'library' ]
layer : 'library'
tasks :
lint :
command : 'eslint .'
Real-World Examples
Node.js Projects
$schema : '../cache/schemas/tasks.json'
inheritedBy :
toolchains : 'node'
fileGroups :
sources :
- 'src/**/*'
tests :
- 'tests/**/*'
tasks :
build :
command : 'packemon build --addFiles --addExports --declaration'
env :
NODE_ENV : 'production'
inputs :
- '@globs(sources)'
- 'package.json'
- 'tsconfig.json'
outputs :
- 'dist'
lint :
command : 'eslint'
args :
- '--cache'
- '--cache-location'
- './.eslintcache'
- '--color'
- '--exit-on-fatal-error'
- '.'
inputs :
- '@globs(sources)'
- '@globs(tests)'
- '.eslintrc.js'
test :
command : 'jest'
args :
- '--cache'
- '--color'
- '--passWithNoTests'
inputs :
- '@globs(sources)'
- '@globs(tests)'
- 'jest.config.js'
typecheck :
command : 'tsc --build'
inputs :
- '@globs(sources)'
- '@globs(tests)'
- 'tsconfig.json'
All Node.js projects now automatically inherit these tasks!
Frontend vs Backend
inheritedBy :
stack : 'frontend'
toolchain : 'node'
tasks :
build :
command : 'vite build'
outputs :
- 'dist'
dev :
command : 'vite'
preset : 'server'
preview :
command : 'vite preview'
preset : 'server'
inheritedBy :
stack : 'backend'
toolchain : 'node'
tasks :
build :
command : 'tsc'
outputs :
- 'dist'
dev :
command : 'node --watch src/index.js'
preset : 'server'
start :
command : 'node dist/index.js'
Library vs Application
inheritedBy :
layer : 'library'
tasks :
build :
command : 'tsup'
inputs :
- 'src/**/*'
- 'package.json'
- 'tsconfig.json'
outputs :
- 'dist'
.moon/tasks/application.yml
inheritedBy :
layer : 'application'
tasks :
build :
command : 'webpack'
deps :
- '^:build' # Build all dependencies first
inputs :
- 'src/**/*'
- 'webpack.config.js'
outputs :
- 'dist'
deploy :
command : 'deploy-script'
deps :
- '~:build'
Merge Strategies
When a global task and local task of the same name exist, they are merged into a single task. To accomplish this, one of many merge strategies can be used.
Merging is applied to the parameters:
Using the options:
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
prepend - Values found in the local task are merged before the values found in the global task
preserve - Preserve the original global task values
replace - Values found in the local task entirely replace the values in the global task
Append Strategy
The default strategy. Local values are added after global values:
# Global: .moon/tasks/node.yml
tasks :
build :
command : 'webpack'
args :
- '--mode'
- 'production'
inputs :
- 'src/**/*'
# Local: packages/app/moon.yml
tasks :
build :
args :
- '--color'
inputs :
- 'webpack.config.js'
options :
mergeArgs : 'append' # default
mergeInputs : 'append' # default
# Merged result
tasks :
build :
command : 'webpack'
args :
- '--mode'
- 'production'
- '--color' # Appended
inputs :
- 'src/**/*'
- 'webpack.config.js' # Appended
Prepend Strategy
Local values are added before global values:
# Global
tasks :
build :
command : 'webpack'
args :
- 'build'
deps :
- 'designSystem:build'
# Local
tasks :
build :
args :
- '--config'
- 'webpack.prod.js'
deps :
- 'utils:build'
options :
mergeArgs : 'prepend'
mergeDeps : 'prepend'
# Merged result
tasks :
build :
command : 'webpack'
args :
- '--config'
- 'webpack.prod.js' # Prepended
- 'build'
deps :
- 'utils:build' # Prepended
- 'designSystem:build'
Replace Strategy
Local values completely replace global values:
# Global
tasks :
build :
command : 'webpack'
inputs :
- 'src/**/*'
- 'webpack.config.js'
outputs :
- 'dist'
# Local
tasks :
build :
inputs :
- 'app/**/*' # Completely different
options :
mergeInputs : 'replace'
# Merged result
tasks :
build :
command : 'webpack'
inputs :
- 'app/**/*' # Replaced
outputs :
- 'dist' # Inherited (not replaced)
Preserve Strategy
Keep only global values, ignore local values:
# Global
tasks :
build :
command : 'webpack'
args :
- '--mode'
- 'production'
# Local
tasks :
build :
args :
- '--mode'
- 'development' # Will be ignored
options :
mergeArgs : 'preserve'
# Merged result
tasks :
build :
command : 'webpack'
args :
- '--mode'
- 'production' # Preserved from global
Complete Example
All strategies demonstrated together:
# Global: .moon/tasks/node.yml
tasks :
build :
command : 'webpack'
args :
- '--mode'
- 'production'
- '--color'
deps :
- 'designSystem:build'
inputs :
- 'src/**/*'
- 'webpack.config.js'
outputs :
- 'dist'
# Local: packages/app/moon.yml
tasks :
build :
args :
- '--no-color'
- '--no-stats'
deps :
- 'utils:build'
inputs :
- 'app.config.js'
options :
mergeArgs : 'append' # Add after global args
mergeDeps : 'prepend' # Add before global deps
mergeInputs : 'replace' # Replace global inputs
# Merged result
tasks :
build :
command : 'webpack'
args :
- '--mode'
- 'production'
- '--color'
- '--no-color' # Appended (note the conflict!)
- '--no-stats'
deps :
- 'utils:build' # Prepended
- 'designSystem:build'
inputs :
- 'app.config.js' # Replaced
outputs :
- 'dist' # Inherited unchanged
Controlling Inheritance
Projects can control which tasks they inherit:
Include Specific Tasks
packages/special/moon.yml
workspace :
inheritedTasks :
include :
- 'lint'
- 'test'
- 'typecheck'
# Only these tasks are inherited, all others ignored
Exclude Specific Tasks
workspace :
inheritedTasks :
exclude :
- 'build' # Use custom build instead
- 'deploy' # No deploy for this project
tasks :
build :
command : 'custom-build-tool' # Custom implementation
Rename Inherited Tasks
workspace :
inheritedTasks :
rename :
typecheck : 'type-check' # Rename typecheck to type-check
lint : 'check-lint' # Rename lint to check-lint
Combining Controls
workspace :
inheritedTasks :
include :
- 'test'
- 'lint'
- 'build'
exclude :
- 'deploy'
rename :
test : 'test-unit'
tasks :
# Override inherited build
build :
command : 'custom-builder'
options :
mergeArgs : 'replace'
Best Practices
Organize by toolchain first
Layer additional conditions
Add more specific files for stack, layer, or tag combinations. .moon/tasks/
├── node.yml # Base Node.js tasks
├── frontend.yml # Frontend-specific
├── backend.yml # Backend-specific
└── library.yml # Library-specific
Use file groups for maintainability
Define file groups in global tasks to standardize input patterns. fileGroups :
sources :
- 'src/**/*'
tests :
- 'tests/**/*'
tasks :
test :
inputs :
- '@globs(sources)'
- '@globs(tests)'
Prefer append for flexibility
The default append strategy allows projects to add their specific needs while keeping common configuration.
Document merge strategies
When using non-default strategies, add comments explaining why. tasks :
build :
inputs :
- 'custom/**/*'
options :
# This project has completely different structure
mergeInputs : 'replace'
Troubleshooting
Task not inheriting
Check conditions:
# View project details
moon project < projec t > --json | jq '.language, .stack, .layer, .tags'
# View inherited tasks
moon project < projec t > --json | jq '.tasks'
Verify inheritedBy conditions match project attributes.
Unexpected merge behavior
Inspect merged task:
# View task details after merging
moon task < projec t > : < tas k > --json
Check merge strategy settings in local moon.yml.
Conflicts after inheritance
# Global defines --color
args : [ '--color' ]
# Local adds --no-color (conflict!)
args : [ '--no-color' ]
options :
mergeArgs : 'append'
# Solution: Use replace strategy
options :
mergeArgs : 'replace'
Tasks - Task configuration and types
Projects - Project configuration
Workspace - Workspace-level configuration
Configuration Reference
For detailed configuration options, see: