Skip to main content
The toolchain is an internal layer for downloading, installing, and managing tools (languages, dependency managers, libraries, and binaries) that are required at runtime. We embrace this approach over relying on these tools “existing” in the current environment, as it ensures the following across any environment or machine:
  • The version and enabled features of a tool are identical
  • Tools are isolated and unaffected by external sources
  • Builds are consistent, reproducible, and hopefully deterministic
Furthermore, this avoids a developer, pipeline, machine, etc, having to pre-install all the necessary tools, and to keep them in sync as time passes.

Overview

The toolchain manages:
  • Language runtimes - Node.js, Deno, Bun, Rust, Python, etc.
  • Package managers - npm, yarn, pnpm, pip, cargo, etc.
  • Build tools - TypeScript, webpack, esbuild, etc.
The toolchain eliminates the “works on my machine” problem by ensuring everyone uses the exact same tool versions.

How it Works

The toolchain is built around proto, our stand-alone multi-language version manager. Moon will piggyback off proto’s toolchain found at ~/.proto and reuse any tools available, or download and install them if they’re missing.

Installation Flow

  1. Moon reads .moon/toolchains.yml
  2. Checks if required tools exist in ~/.proto/tools/
  3. Downloads and installs missing tools
  4. Adds tool binaries to PATH for task execution
~/.proto/
├── tools/
   ├── node/
   ├── 18.0.0/
   ├── 20.0.0/
   └── 22.0.0/
   ├── yarn/
   └── 4.0.0/
   └── typescript/
       └── 5.0.0/
└── shims/           # Binaries available on PATH
    ├── node
    ├── yarn
    └── tsc

Configuration

The tools that are managed by the toolchain are configured through the .moon/toolchains.yml file, but can be overridden in each project with moon.yml.

Basic Configuration

.moon/toolchains.yml
$schema: './cache/schemas/toolchains.json'

# Node.js configuration
node:
  version: '20.0.0'

# Package manager
yarn:
  version: '4.0.0'

# JavaScript/TypeScript settings
javascript:
  packageManager: 'yarn'
  inferTasksFromScripts: false
  syncPackageManagerField: true
  syncProjectWorkspaceDependencies: true

# TypeScript settings
typescript:
  routeOutDirToCache: true
  syncProjectReferences: true

Language-Specific Configuration

Node.js

.moon/toolchains.yml
node:
  version: '20.14.0'
  addEnginesConstraint: true
  dedupeOnLockfileChange: true
  inferTasksFromScripts: false

javascript:
  packageManager: 'yarn'
  syncPackageManagerField: true
  syncProjectWorkspaceDependencies: true

yarn:
  version: '4.12.0'
  plugins:
    - 'workspace-tools'

TypeScript

.moon/toolchains.yml
typescript:
  createMissingConfig: true
  routeOutDirToCache: true
  syncProjectReferences: true
  syncProjectReferencesToPaths: true

Rust

.moon/toolchains.yml
rust:
  version: '1.75.0'
  components:
    - 'rustfmt'
    - 'clippy'

Python

.moon/toolchains.yml
python:
  version: '3.11.0'
  packageManager: 'pip'

pip:
  version: 'latest'

Version Specification

As mentioned above, tools within the toolchain are managed by version for consistency across machines. These versions are configured on a per-tool basis in .moon/toolchains.yml. So what kinds of versions are allowed?

Full Versions

A full version is a semantic version that is fully specified, such as 1.2.3 or 2.0.0-rc.1. This is the most common way to specify a version, and is preferred to avoid subtle deviations.
.moon/toolchains.yml
node:
  version: '20.14.0'  # Exact version

yarn:
  version: '4.12.0'   # Exact version
Full versions are recommended for production environments to ensure maximum consistency.

Partial Versions

A partial version is a version that is either missing a patch number, minor number, or both, such as 1.2 or 1. These can also be represented with requirement syntax, such as ^1.2 or ~1.
.moon/toolchains.yml
node:
  version: '20'     # Latest 20.x.x

yarn:
  version: '4.12'   # Latest 4.12.x
For partial versions, we first check locally installed versions for a match by scanning ~/.proto/tools/<tool>. For example, if the requested version is 1.2 and we have 1.2.10 installed locally, we’ll use that version instead of downloading the latest 1.2.* version. Otherwise, we’ll download the latest version that matches the partial version, and install it locally.
Partial versions can lead to subtle inconsistencies across machines. If using partials, we suggest having at least a major and minor number.

Aliases

An alias is a human-readable word that maps to a specific version. For example:
  • latest or stable maps to the latest stable version of a tool
  • canary maps to the canary release
  • berry (for Yarn) maps to Yarn 2+
.moon/toolchains.yml
node:
  version: 'latest'  # Always use the latest

yarn:
  version: 'stable'  # Use stable release
Aliases are not suggested for use since they can change at any time (or even daily!), leading to non-reproducible builds.

Project-Level Overrides

Individual projects can override workspace toolchain versions:
packages/legacy/moon.yml
language: typescript

toolchain:
  node: '18.0.0'  # This project requires older Node.js

tasks:
  build:
    command: 'tsc'
This is useful for:
  • Legacy projects that can’t be upgraded yet
  • Projects with specific runtime requirements
  • Gradual migration to new tool versions

Force Disabling

The MOON_TOOLCHAIN_FORCE_GLOBALS environment variable can be set to true to force moon to use tool binaries available on PATH, instead of downloading and installing them. This is useful for pre-configured environments, like CI and Docker.
# Disable toolchain entirely
MOON_TOOLCHAIN_FORCE_GLOBALS=true moon run app:build
Additionally, the name of one or many tools can be passed to this variable to only force globals for those tools, and use the toolchain for the remaining tools.
# Use global node and yarn, but download other tools
MOON_TOOLCHAIN_FORCE_GLOBALS=node,yarn moon run app:build
This is particularly useful in Docker containers where tools are already installed at specific versions.

Real-World Examples

Monorepo with Multiple Node Versions

.moon/toolchains.yml
$schema: './cache/schemas/toolchains.json'

node:
  version: '20.0.0'  # Default for most projects

yarn:
  version: '4.0.0'

javascript:
  packageManager: 'yarn'
  syncProjectWorkspaceDependencies: true
packages/legacy-api/moon.yml
language: typescript

toolchain:
  node: '18.0.0'  # Legacy API needs older Node

TypeScript with Strict Configuration

.moon/toolchains.yml
typescript:
  createMissingConfig: true
  routeOutDirToCache: true
  syncProjectReferences: true
  syncProjectReferencesToPaths: true

node:
  version: '20.0.0'

javascript:
  packageManager: 'yarn'

Multi-Language Workspace

.moon/toolchains.yml
$schema: './cache/schemas/toolchains.json'

# Frontend tools
node:
  version: '20.0.0'

yarn:
  version: '4.0.0'

javascript:
  packageManager: 'yarn'

# Backend tools
rust:
  version: '1.75.0'
  components:
    - 'rustfmt'
    - 'clippy'

# Scripts and tooling
python:
  version: '3.11.0'

Benefits

Everyone on the team uses the exact same tool versions, eliminating “works on my machine” issues.
New team members don’t need to manually install and configure tools. Moon handles it automatically on first run.
CI environments automatically use the correct tool versions without complex setup scripts.
Upgrade tool versions for the entire team by updating a single config file.
Tools are isolated to the workspace and don’t conflict with globally installed versions.

Best Practices

Always specify full semantic versions (e.g., 20.14.0) in production to ensure reproducibility.
When upgrading tool versions, test thoroughly before committing. Consider using a feature branch.
If a project requires a specific version, document why in the moon.yml file with comments.
Try to keep all projects on the same tool versions when possible. Use overrides sparingly.

Troubleshooting

Tools not found

If tools aren’t being downloaded:
# Clear proto cache and reinstall
rm -rf ~/.proto/tools/<tool>
moon run app:build

Version conflicts

If you see version conflicts:
# Check installed versions
proto list node

# Check moon's resolved versions
moon project --json app | grep -A 5 toolchain

CI/CD issues

In CI, you may want to cache proto tools:
# GitHub Actions example
- uses: actions/cache@v3
  with:
    path: ~/.proto
    key: ${{ runner.os }}-proto-${{ hashFiles('.moon/toolchains.yml') }}
  • Workspace - Where toolchains are configured
  • Projects - Can override toolchain versions
  • Tasks - Run using toolchain tools

Configuration Reference

For detailed configuration options, see:

Build docs developers (and LLMs) love