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 projectspackage.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.jsonIf 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 --builddoes betweenapps/webanddesign-system/ui) - Has no cache — every CI run is from scratch
- Has no fan-out —
pnpm runis 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 --jsonmoon is in the catalog (@moonrepo/cli). Use pnpm exec moon … if moon
isn't on your PATH directly.
pnpm workspaces
pnpm 9 with a strict catalog and workspace protocol — single source of dependency versions across every app, package, and design-system project.
TypeScript with tsgo
Why this repo runs `tsgo --build`, not `tsc --noEmit` — and the two error codes (TS4023, TS6305) you only see under the native compiler.