Today Platform Web — Dev Docs
Toolchain

Moon task orchestration

Moon defines per-project build / typecheck / test tasks and the dependency graph between them. The root pnpm scripts are thin wrappers over `moon run :*`.

Moon is the task graph runner. Every workspace project ships a moon.yml declaring its tasks (build, typecheck, test, …) and their inputs / outputs. The root pnpm scripts delegate to it.

Workspace config

.moon/workspace.yml tells Moon which directories contain projects:

projects:
  - apps/*
  - packages/*
  - design-system/*

Each matched directory must have a moon.yml for Moon to register it. A package in pnpm-workspace.yaml without a moon.yml is invisible to Moon (harmless — pnpm and moon are independent graphs).

The : task syntax

moon run web:build runs build in the web project. moon run :build runs build in every project that has a build task. The repo's quality gates use the latter:

moon run :typecheck    # all projects
moon run :test         # all projects
moon run :build        # all projects

package.json exposes these as pnpm typecheck / pnpm test / pnpm build — they are not plain pnpm workspace recursion; they go through Moon and pick up the task graph (project references, input/output hashes, parallelism).

Caching

Moon hashes every task's inputs: glob and caches the run. A task whose inputs haven't changed since the last successful run is skipped✓ web:test (cached, ac8488be) in the logs.

This is what makes pnpm test feel instant on the second run. It also means you should declare inputs honestly: a missing entry causes false cache hits. Example from apps/dev-docs/moon.yml:

typecheck:
  command: pnpm typecheck
  inputs:
    - app/**/*
    - content/**/*
    - lib/**/*
    - mdx-components.tsx
    - next.config.ts
    - package.json
    - source.config.ts
    - tsconfig.json

If you add a new file outside these globs that should affect typecheck (rare — most things go in app/ or lib/), update the inputs list.

runInCI

Tasks like dev and start are marked runInCI: false to keep them out of moon run :* invocations. CI runs them by name only, never by :*.

Why not just pnpm?

The repo could call pnpm --filter @todayai-labs/web typecheck && pnpm --filter @todayai-labs/admin typecheck && … everywhere, but that:

  • Doesn't dedupe shared TypeScript project references (the work tsgo --build does between apps/web and design-system/ui)
  • Has no cache — every CI run is from scratch
  • Has no fan-out — pnpm run is sequential within a project group

Moon solves all three. The pnpm scripts at the repo root exist mostly as muscle-memory aliases; the real engine is Moon.

Common operations

# Run one project's task
moon run web:build
moon run dev-docs:typecheck

# Run a task across all projects
moon run :typecheck
moon run :test

# Bypass cache (rarely needed)
moon run :typecheck --updateCache

# See the project graph
moon project-graph

# See what would run for a task
moon query tasks --json

moon is in the catalog (@moonrepo/cli). Use pnpm exec moon … if moon isn't on your PATH directly.

On this page