Migrate to moon v2.0
To ease the migration process from moon v1 to v2, we've compiled a list of all breaking changes and important changes that you should be aware of. Please read through these carefully before upgrading your workspace.
To automate some of the migration process, we've created the moon migrate v2 command that will
migrate all applicable settings in configuration files.
$ moon migrate v2
CLI
- Removed
x86_64-apple-darwin(Apple Intel) as a supported operating system. Onlyaarch64-apple-darwin(Apple Silicon) is now supported.
Commands
- We've done a large polish pass for all commands, based on the CLI guidelines.
- Updated the
moonxbinary to usemoon execinstead ofmoon rununder the hood. - Renamed all options and flags to kebab-case instead of camelCase.
- Example:
--logLevel->--log-level
- Example:
- Renamed options for all commands:
--platform->--toolchain
- Removed commands:
moon nodemoon migrate from-package-json(use themigrate-turborepoextension instead)moon query hash(usemoon hashinstead)moon query hash-diff(usemoon hashinstead)
moon action-graph
- Changed the output of
--jsonto a new JSON structure (now matches the project and task graphs).
moon check
- Now runs
moon execunder the hood, with some arguments/options pre-filled. - If the project ID is not specifed, it will no longer find the closest project. Instead you must
pass
--closest. - Renamed options:
--update-cache, -u->--force, -f
moon ci
- Now runs
moon execunder the hood, with some arguments/options pre-filled. - Renamed options:
--update-cache, -u->--force, -f
moon generate
- Changed the destination from a positional argument, to the
--tooption.- Example:
moon generate <id> ./dist->moon generate <id> --to ./dist
- Example:
moon init
- Removed "scaffolding a toolchain" functionality from the command. Use the
moon toolchain addcommand instead. - Removed options:
--to(use a positional argument instead)
moon mcp
- Updated the
get_projectstool to no longer have anincludeTasksoption. - Updated the
get_projectstool to return a list of project fragments, instead of the whole project object. This was required as the response was too large for MCP. - Updated the
get_taskstool to return a list of task fragments, instead of the whole task object. This was required as the response was too large for MCP.
moon query projects
- Removed options:
--dependents
moon run
- Now runs
moon execunder the hood, with some arguments/options pre-filled. - Updated options:
--dependentsnow requires a value, eitherdeepordirect
- Renamed options:
--update-cache, -u->--force, -f
- Removed options:
--no-bail(usemoon execinstead)--profile--remote(use--affected remoteinstead)
moon templates
- Changed the output to render a table instead of a list.
moon run
- Running a target without a scope no longer locates the closest project and instead uses the new
default project feature. To run a target in the closets project, use
~:scope instead.- Example:
moon run build->moon run ~:build
- Example:
Workspace
Configuration: .moon/workspace.*
- Renamed setting values:
codeowners.orderByvalueproject-name->project-id
- Renamed settings:
codeowners.syncOnRun->codeowners.syncconstraints.enforceProjectTypeRelationships->constraints.enforceLayerRelationshipsdocker.prune.installToolchainDeps->docker.prune.installToolchainDependenciesdocker.scaffold.include->docker.scaffold.configsPhaseGlobsrunner->pipelineunstable_remote->remotevcs.manager->vcs.clientvcs.syncHooks->vcs.sync
- Removed settings:
docker.scaffold.copyToolchainFilesexperiments.*hasher.batchSizepipeline.archivableTargets
Toolchains
Configuration: .moon/toolchain.*
- This file was renamed to
.moon/toolchains.*(plural) to reflect that multiple toolchains are configured. This also aligns with the new.moon/extensions.*file. - All toolchains have been stabilized, so the
unstable_prefix must be removed from identifiers.
JavaScript
The bun, deno, and node toolchains now require the javascript toolchain to be defined as
well. All shared settings have been moved to the javascript toolchain.
In addition, all node package managers are no longer nested under the node toolchain, but are
now top-level settings. These are only required when the javascript.packageManager setting is
defined.
- Moved settings:
bun.dependencyVersionFormat->javascript.dependencyVersionFormatbun.inferTasksFromScripts->javascript.inferTasksFromScriptsbun.rootPackageOnly->javascript.rootPackageDependenciesOnlybun.syncProjectWorkspaceDependencies->javascript.syncProjectWorkspaceDependenciesnode.dependencyVersionFormat->javascript.dependencyVersionFormatnode.dedupeOnLockfileChange->javascript.dedupeOnLockfileChangenode.inferTasksFromScripts->javascript.inferTasksFromScriptsnode.packageManager->javascript.packageManagernode.rootPackageOnly->javascript.rootPackageDependenciesOnlynode.syncPackageManagerField->javascript.syncPackageManagerFieldnode.syncProjectWorkspaceDependencies->javascript.syncProjectWorkspaceDependenciesnode.bun->bunnode.npm->npmnode.pnpm->pnpmnode.yarn->yarn
- Renamed settings:
node.binExecArgs->node.executeArgs
- Removed settings:
bun.packagesRootdeno.depsFiledeno.lockfilenode.addEnginesConstraintnode.packagesRoot
# Before
node:
version: '22.14.0'
packageManager: 'yarn'
inferTasksFromScripts: false
syncPackageManagerField: true
syncProjectWorkspaceDependencies: true
yarn:
version: '4.8.0'
# After
javascript:
packageManager: 'yarn'
inferTasksFromScripts: false
syncPackageManagerField: true
syncProjectWorkspaceDependencies: true
node:
version: '22.14.0'
yarn:
version: '4.8.0'
Extensions
Configuration: .moon/extensions.*
- The
extensionssetting from.moon/workspace.*has been moved (and flattened) to its own file,.moon/extensions.*. - The built-in extensions
download,migrate-nx, andmigrate-turborepomust now be enabled in the configuration file before they can be used. Simply set an empty object.- This change was made to reduce the number of extensions that are loaded by default, improving performance.
download: {}
migrate-nx: {}
Project
Configuration: moon.*
- Renamed settings:
docker.scaffold.include->docker.scaffold.sourcesPhaseGlobsproject.name->project.titletype->layertoolchain->toolchainsplatform->toolchains.default
- Removed settings:
Language detection
The primary language is now detected from toolchains, instead of being a hardcoded implementation.
The result may now differ, as the first toolchain in the list will be used. Additionally, languages
that don't have a toolchain yet, like PHP or Ruby, will not be detected and must be explicitly
configured.
# After
language: 'ruby'
Custom metadata
The project.metadata setting has been removed, but all custom metadata fields can now be defined
at the root of the project setting.
# Before
project:
metadata:
customField: 'value'
# After
project:
customField: 'value'
Toolchain disabling
The toolchain.*.disabled setting was removed. Instead set the toolchain itself to null/false.
# Before
toolchain:
typescript:
disabled: true
# After
toolchains:
typescript: null
Tasks
Configuration: moon.*, .moon/tasks
- The file
.moon/tasks.ymlhas been removed. If you want to support tasks that are inherited by all projects, then move this to.moon/tasks/all.ymland do not configure the newinheritedBysetting. - Renamed tokens:
$projectName->$projectTitle$projectType->$projectLayer$taskPlatform->$taskToolchain
- Renamed settings:
tasks.*.local->tasks.*.presetusingservervaluetasks.*.platform->tasks.*.toolchains
- Removed setting values:
tasks.*.presetvaluewatcher
- Changed option defaults:
tasks.*.options.envFilenow defaults to a list of files, instead of a single file, whentrue. Refer to the blog post for more information.tasks.*.options.inferInputsnow defaults tofalseinstead oftrue.tasks.*.options.shellnow defaults totrueinstead offalse.tasks.*.options.unixShellnow defaults tobashinstead of nothing.tasks.*.options.windowsShellnow defaults topwshinstead of nothing.
Local mode
The local task setting has been removed as the name was confusing. Users assumed it meant "only
run locally", but it actually meant "this is a persistent server that should only run locally".
Instead, use the preset setting with a value of server.
# Before
tasks:
dev:
command: 'start-dev'
local: true
# Before
tasks:
dev:
command: 'start-dev'
preset: 'server'
If you want a task that is simply "local only" without other options changes, use options.runInCI
directly.
tasks:
dev:
command: 'start-dev'
options:
runInCI: false
Shells by default
Tasks now run in a shell by default, and will use Bash on Unix (options.unixShell), and pwsh on
Windows (options.windowsShell). You can disable this behavior by setting options.shell to
false.
tasks:
dev:
command: 'start-dev'
options:
shell: false
Env var substitution behavior
The syntax and behavior for substituting (expanding/interpolation) environment variables has
changed, to better align with the standard of .env files. The biggest change is that flagless
tokens ($VAR) and ? flag tokens ($VAR?) swapped functionality. Refer to the following table:
| Syntax | v1 | v2 |
|---|---|---|
$VAR | Substitute with variable syntax ($VAR) if variable empty | 💥 Substitute with empty string if variable empty |
$VAR! | Don't substitute and keep variable syntax ($VAR) | 💥 Removed syntax |
$VAR? | Substitute with empty string if variable empty | 💥 Removed syntax |
${VAR} | Substitute with variable syntax ($VAR) if variable empty | 💥 Substitute with empty string if variable empty |
${VAR!} | Don't substitute and keep variable syntax ($VAR) | ~ |
${VAR?} | Substitute with empty string if variable empty | 💥 Substitute with variable syntax ($VAR) if variable empty |
${VAR:default} | Use default value if variable empty | ~ |
${VAR:-default}, ${VAR-default} | ⛔️ Not supported | Use default value if variable empty |
${VAR:+alternate}, ${VAR+alternate} | ⛔️ Not supported | Use alternate value if variable non-empty |
Legend:
~indicates the syntax/functionality is the same as v1💥indicates breaking change⛔️indicates not supported
Env var precedence
The order of precedence for environment variables has slightly changed when running tasks, as it was confusing to users. The new order of precedence is as follows, from lowest to highest tier, with the latter overwriting the former:
- Task
.envfiles- via
options.envFile
- via
- Task env variables
- via
envordeps.*.env
- via
- System variables
- via profile scripts (
.bashrc,.zshrc, etc) - via command line:
KEY=value moon ...
- via profile scripts (
In regards to variable substitution, each tier can reference variables within the same tier, or the higher tier(s), but not from lower tier(s). This is because variables are processed in reverse order. Refer to the following table:
| Tier | Can reference | Evaluated during |
|---|---|---|
| Dotenv | Dotenv, Task, System | Before task execution |
| Task | Task, System | After task creation |
| System | System | CLI startup |
If you don't want to inherit a system variable, you can override it in the task with a null value.
tasks:
dev:
env:
EXAMPLE: null
Deferred .env files
In v1, when options.envFile was enabled, the .env file(s) were loaded at the time of task
creation, during the building of project/task graphs. This meant that if the .env file changed
between the time of graph creation and task execution, the changes would not be reflected.
In v2 and later, .env files are loaded just before task execution, ensuring that any changes to
the file are picked up.
Because of this change, task env variables will continue to override .env file variables, BUT
can no longer reference them for substitution. This is because the .env files are loaded later in
the process.
Other changes
- When
options.affectedFilesis enabled, the list of files will be joined with the OS path separator (:on Unix,;on Windows) instead of a comma (,) when passed as theMOON_AFFECTED_FILESenvironment variable.
Task inheritance
Deep merged instead of shallow merged
In v1, when inheriting tasks, all global configs (those in .moon) were shallow merged into a
single config ignoring merge task options, and then merged with the local config (moon.*) using
merge task options. This was not intuitive, as users expected all configs to be merged in sequence.
To demonstrate this, take the following example configs, in order of inherited:
# .moon/tasks.yml
tasks:
build:
command: 'build --cache'
options:
mutex: 'build'
# .moon/tasks/tag-a.yml
tasks:
build:
args: '--force'
# .moon/tasks/tag-b.yml
tasks:
build:
args: '--clean'
Users would expect the final build task to be build --cache --force --clean with the mutex
option set. However, since the tasks setting was shallow merged, only the last config (tag-b)
would be used, resulting in the task being noop --clean without the mutex. The noop pops up
because the command setting was not configured in the last config.
To remedy this, and to improve task composition overall, global configs are no longer shallow merged into a single config before merging with the local config. Instead, all configs are merged in sequence, while respecting the task merge options. This is the order of operations for the new system:
- Load all global configs (
.moon) into a list, in order, based on theinheritedBysetting. - Load the local config (
moon.*). - Create an inheritance chain by resolving global configs first, then the local config last, while
respecting
extendand other composition settings. - Merge all task options in order, to create the final task options.
- Merge all tasks in order, using the final task options to guide the merging behavior.
File groups are merged
Because of the new deep merging behavior, file groups defined in inherited configs are now merged together, instead of being replaced by the following config in the sequence. For example, given the configs:
# .moon/tasks/tag-a.yml
fileGroups:
sources:
- 'src/**'
# .moon/tasks/tag-b.yml
fileGroups:
sources:
- 'docs/**'
In v1, the final sources file group would only include docs/**, as the second config would
replace the first. In v2, the final sources file group includes both src/** and docs/**.
VCS
New hooks system
We've rewritten our Git hooks from the ground up to be based around the core.hooksPath setting.
The following changes have been made:
- We no longer write hooks to the
.git/hooksdirectory. - Instead, all hooks are written to
.moon/hooksand Git is configured to use this directory. - Bash scripts no longer end in
.sh.
We currently don't have an easy way to clean the previous implementation of hooks. You may need to
manually remove the old scripts from .git/hooks and .moon/hooks if they are causing issues.
Other changes
Changed (touched) files
We renamed the terminology "touched files" to "changed files" throughout the codebase and documentation. This better aligns with common VCS terminology and reduces confusion. Because of this, the following changes were made:
- Renamed the
moon query touched-filesCLI command tomoon query changed-files. - Renamed the
get_touched_filesMCP tool toget_changed_files. - Renamed the
touchedFilesrun report field tochangedFiles.
# Before
$ moon query touched-files
# After
$ moon query changed-files
Docker
- The scaffolded
.moon/docker/workspacedirectory was renamed to.moon/docker/configs. - The
moon docker filecommand will now loop through all toolchains and use the first image found, otherwise it defaults to "scratch". If you want to be explicit, set thedocker.file.imagesetting.
Query language (MQL)
- Renamed fields:
projectName->projectIdprojectType->projectLayertaskPlatform->taskToolchain
# Before
projectType=application && taskPlatform=node
# After
projectLayer=application && taskToolchain=node
Webhooks
- Removed the
tool.*events. Usetoolchain.*events instead. - Removed the
runtimefield fromdependencies.*events. Use thetoolchainfield instead.