[dev] Adopt moonrepo#809
Conversation
Each docs site variant (diffs, trees) now builds into its own `.next/<site>` directory in production, not just dev. This lets the two site builds run concurrently and be cached independently by the task runner, and makes `next start` serve the build matching the serving env's NEXT_PUBLIC_SITE instead of whichever variant was built last. Vercel resolves the custom distDir from next.config.mjs, with NEXT_PUBLIC_SITE set per project; verify via preview deploys on both docs projects before relying on it.
Add the moon v2 workspace config (project globs for packages/* and apps/*, plus the repo root as the `root` project for repo-wide tooling), the toolchains config (javascript + bun, versions inherited from .prototools, frozen-lockfile installs), and gitignore entries for moon's generated cache and hooks directories. moon 2.3.3 is pinned in .prototools and enforced via versionConstraint so the proto-managed binary and the workspace config move together.
Add inherited task files and per-project moon.yml configs covering every npm script that is a real task: - bun-common (all bun projects): test, typecheck. Typecheck depends on ^:build because cross-package types resolve through each dependency's published dist, replacing every build:deps script. - tag-tsdown (diffs, theming, truncate, trees): build + dev watcher. theming/trees override build to append their post-build asserts; trees also watches the inlined path-store sources. - tag-publishable (diffs, theming, truncate, trees, storage-elements-next): prepublish guard chain (assert-bun-version + build) for the prepublishOnly lifecycle hook. trees appends assert-safe-publish. assert-bun-version previously only guarded diffs; it now covers every published package. - Apps define build/dev/start/prod with ^:build and ^:dev deps; moon runs dependency watchers alongside the app server, replacing the concurrently pipelines. Port math and run-dev.sh stale-listener cleanup live in task scripts with PIERRE_PORT_OFFSET loaded from /.env.worktree. - The two docs site builds write disjoint .next/<site> outputs but share a mutex because generate-llms-txt writes site-dependent content to the shared public/ dir. - path-store's build is renamed build-demo (it builds the demo site, not a library), so consumers' ^:build never drags it in. - Repo-wide lint/format/stylelint/icons/clean/chrome/wt live on the root project; lint and format checks always run in CI. - docs test-e2e and path-store test-demo keep runInCI: skip for parity with the previous CI setup.
Playwright webServer commands now spawn moon tasks instead of npm scripts (moon normalizes the working directory to the project root, matching what bun run did). prepublishOnly in every published package delegates to its moon prepublish guard chain; storage-elements-next gains the guard for the first time. moon is now a pinned root devDependency so environments without proto (Vercel builders) get the binary from bun install; the bunfig release-age gate excludes @moonrepo packages since the version is exact-pinned everywhere it is consumed. Each Vercel-deployed app gets a vercel.json whose buildCommand runs the app's moon build from node_modules/.bin. The two docs Vercel projects share one file, dispatching on NEXT_PUBLIC_SITE to pick the site build task. Cutover is atomic at merge since Vercel reads vercel.json from the deploying commit; verify via preview deploys before merging.
Running the full affected pipeline surfaced four issues: - test tasks raced dependency builds: bun resolves workspace imports through each dependency's dist, so test now depends on ^:build like typecheck (diffshub:test failed when theming:build had not finished). - Explicit input globs are filesystem-walked and ignore gitignore, so the repo-wide root tasks and the shared typecheck inputs now negate node_modules and generated output trees instead of hashing them. - Tasks spawned by playwright webServer (trees:test-e2e-server, path-store:test-demo-server, docs:start) must not use the server preset: moon run refuses runInCI-disabled tasks in CI environments, which broke e2e there. They now set persistent/cache/runInCI explicitly. Nothing depends on them and no CI lane targets them, so they never enter a moon ci pipeline. - demo has no test suite and now excludes the inherited test task; docs' build outputs dropped the vestigial fumadocs .source dir that nothing generates (it failed moon's output validation).
Replace the six-job pipeline (build + artifact fan-out) with two moon lanes plus the unchanged actions-pinned guard: - ci: moon ci --include-relations over every CI task except trees e2e. Affected projects come from the VCS diff with graph relations enabled, so a package change re-runs its dependents' builds, tests, and typechecks; unchanged work restores from the moon cache. - e2e: moon ci --include-relations trees:test-e2e in parallel, so the slowest suite never serializes behind builds and skips itself when trees is unaffected. The setup action drops bun-version parsing, setup-bun, actions/cache, and the manual bun install: moonrepo/setup-toolchain provisions the .prototools toolchain via proto, moon installs dependencies itself with a frozen lockfile, and Blacksmith sticky disks persist ~/.proto, bun's download cache, and .moon/cache across runs. Artifact upload/download steps are gone; outputs flow through the moon cache. Runners consolidate to one 8vcpu machine per lane.
moon now generates .moon/hooks (gitignored) from vcs.hooks in the workspace config and points the worktree-local core.hooksPath at it, so every clone and linked worktree gets hooks from any moon command — no install-time prepare script. Verified: generated hook scripts inline commands with "$@" intact and inherit stdin, so the git-lfs pre-push guard keeps receiving its ref list. pre-commit replaces scripts/precommit-tsc.ts with `moon exec :typecheck --affected --status staged`, which also typechecks dependents of the staged projects (the old script only checked workspaces containing staged files), then runs lint-staged as before. husky and the prepare lifecycle script are removed.
Every package.json scripts block is now lifecycle-only: the five published packages keep exactly prepublishOnly (pointing at their moon prepublish guard), and every other manifest has no scripts. All human/agent/CI entrypoints are moon tasks (`moon run`, `moonx`). Deleted along with the scripts: - scripts/ws.ts, the custom workspace runner moon replaces (worktree env loading is handled by task envFile options, and TTY/signal babysitting by moon's pipeline). - concurrently (catalog + three app devDependencies): moon runs dependency watchers alongside app servers as persistent task deps. - husky's catalog entry (the dep itself left with the hooks change). CLI help text, code comments, and the profiler's chrome launcher now reference moon tasks or invoke scripts directly; the file-tree profiler help test asserts the new strings.
AGENTS.md now documents the moon command surface (moon run / moonx, task discovery), tells agent sessions to unset CI (moon refuses to run local-only tasks like dev servers and formatters in CI-detected environments), and rewrites the verification baseline as moon tasks. Skills (tooling-and-dependencies, testing-and-verification, worktrees-and-dev-servers, typescript-monorepo), scripts/README.md, and package docs (trees README/TESTING/PUBLISHING, path-store and diffs READMEs) drop every bun ws / npm-script reference in favor of the moon equivalents, including the renamed tasks (tsc -> typecheck, colon-separated names -> kebab-case). trees' publish.ts now builds through moon run trees:build (it spawned the deleted build script), and assert-safe-publish's guidance points at moonx trees:publish.
Cache correctness (stale-cache and miss-invalidation bugs): - tsdown builds hash the package tsconfig and root tsconfig.options (tsdown loads them for dts emit); theming/trees overrides match. - typecheck inputs exclude *.tsbuildinfo and demo/dist; root:lint hashes tsconfig.options.json; root:icons hashes svgo.config.js. - tree-test-data:test hashes ../path-store/src (index.js imports the builder directly; a manifest dep would cycle with path-store's devDependency on this package). Execution correctness: - Type-aware oxlint resolves @pierre/* through built dist exactly like typecheck, so the root project now dependsOn the dist-producing packages and root:lint/lint-fix order after ^:build (lint raced tsdown --clean and saw error-types). stylelint ignores generated CSS for the same build-race reason. - profileFileTree.ts and theming's dist-guards test spawned deleted npm scripts (bun run build / test:e2e:server); profileDemo.ts spawned a script that never existed. All now spawn moon tasks. runInCI policy, after discovering that moon ci --include-relations runs affected runInCI-enabled tasks even when unrequested: - Tasks with no graph edges (formatters, benchmarks, wt, chrome, clean, icons, playwright-spawned servers) set runInCI 'always' so they work in CI-marked shells (agent harnesses export CI=1) and never enter the pipeline. - Graph-connected local tasks (dev/prod, e2e variants, lint-fix, the publish guard chain — trees' assert-safe-publish fails by design outside a real publish) stay runInCI 'skip'; CI-marked shells run them with a CI= prefix. The previous `unset CI` advice did not survive per-command env injection in agent harnesses and is gone. CI workflow: - Collapsed to a single lane: e2e joins via graph relations whenever trees is affected, so a separate lane double-ran it. The playwright install is gated on a fail-open affected query. - Sticky disks persist only moon's portable cache state (hashes, outputs), not machine-specific run state. - vercel.json build commands prefix PATH with both the repo-root and app-relative node_modules/.bin so they work whether the Vercel root directory is the repo or the app.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Root cause of the CI failures (diffs/trees tests and trees:typecheck failing to resolve @pierre/theming moments after theming:build completed): theming's dist-guards test spawned a nested `moon run theming:build`, which cache-hit and re-hydrated — cleared and re-unpacked — packages/theming/dist while sibling tasks were resolving @pierre/theming through it. Verified locally by sampling dist during a cold pipeline: it went missing for ~300ms mid-run, the exact clear-then-untar signature and the same window as the CI failures. - dist-guards no longer builds or restores anything; theming:test declares a dep on theming:build and the test just asserts dist exists. Invariant recorded in the workflow: no task may write another task's outputs mid-pipeline. - storage-elements builds use `tsgo --build --force`: a stale tsconfig.tsbuildinfo with a deleted dist made tsgo skip emit and fail moon's output validation; task-level caching owns incrementality anyway. - docs site builds declare explicit inputs excluding public/llms*: moon only filters a task's OWN outputs from greedy inputs, so each site build was hashing its sibling's llms output and the two hashes ping-ponged, forcing rebuilds every pipeline. All four app builds now cache-hit on warm reruns. - CI workflow reverted to a single moon ci phase (a two-phase split added during diagnosis was illusory: moon ci executes every affected runInCI-enabled task regardless of requested targets). Comments and skills now state those semantics precisely: targets add entry points, never subtract; anything CI-unsafe must be runInCI skip. Validated cold (rm dist/tsbuildinfo/.next + empty moon cache): 33 tasks, 0 failures, dist written exactly once; warm rerun: 31/33 cached, 0 failures.
Vercel's Next builder collects output from the literal .next directory and does not honor the env-dependent distDir when locating routes-manifest.json, so both docs deployments failed after the per-site distDir change. Per-site build dirs only matter where both variants share a workspace (local dev and CI); on Vercel each project builds exactly one site in an isolated container, so distDir now falls back to .next when VERCEL is set. The moon tasks declare their .next outputs as globs because globs are optional: on Vercel the per-site directory never exists and exact-dir outputs would fail moon's output validation after a successful build. Unrebased PRs are unaffected: they deploy with their own commit's config (old prod distDir = .next, npm scripts intact) and the Vercel dashboard settings are unchanged; only commits carrying vercel.json get the new build path. Verified: VERCEL=1 moon run docs:build produces .next/routes-manifest.json and passes; normal runs keep .next/diffs and cache hits.
moon 2.3.3 fails output validation when any declared output matches nothing — including glob outputs, despite the error hint — so the docs site-build tasks cannot run on Vercel where the build writes plain .next and the per-site directory never exists (the trees deployment failed with task_runner::missing_outputs after a successful next build). Split the responsibilities instead: a new docs:build-deps aggregator (noop + ^:build, CI-enabled because Vercel builders set CI=1) gives Vercel the dependency graph, and its buildCommand then runs generate-llms-txt and next build directly — which is also what Vercel's builder expects to own (it applies modifyConfig to the next build it observes). The command is cwd-agnostic so it works whether the project's Root Directory is the repo root or apps/docs, and it no longer needs the NEXT_PUBLIC_SITE dispatch since the env alone selects the site. The docs moon tasks get their strict exact-dir outputs back (.next/diffs, .next/trees); they are simply never invoked on Vercel. Verified: the buildCommand passes from both candidate root directories with VERCEL=1 CI=1 (routes-manifest.json lands at apps/docs/.next/); local moon builds keep per-site dirs and full cache hits.
CONTRIBUTING.md covers the full fresh-clone path: installing proto (with a link to the official guide), git-lfs, `proto use` against the .prototools pins, bun install with the catalog convention, the moon-managed git hooks, the moon command surface with a common-tasks table, and the pre-push verification baseline. AGENTS.md and the tooling skill gain a Toolchain section: versions are pinned in .prototools and managed by proto (never installed globally), moon's pin is additionally enforced by workspace versionConstraint and mirrored in the @moonrepo/cli catalog entry for Vercel builders, and CI resolves the identical toolchain via moonrepo/setup-toolchain. A sweep for stale workflow commands (bun run format, bun ws, npm run) found none left; remaining npm/pnpm mentions are docs-site content and fixtures about external repos, not repo workflow.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 825405ab7b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| moon ci --include-relations :build docs:build-trees-site | ||
| path-store:build-demo :test :typecheck trees:test-e2e root:lint | ||
| root:lint-css root:format-check |
There was a problem hiding this comment.
Keep app builds on package changes
With the CI lane now running only moon ci --include-relations, a package-only change no longer explicitly targets the app builds that used to run unconditionally in this workflow (docs and diffshub). Since --include-relations expands from affected projects to related tasks, it will not make unaffected downstream apps build when the triggering task is :build; for example, changing packages/diffs/src/... can be satisfied by the package build/test/typecheck targets and skip docs:build, docs:build-trees-site, and diffshub:build, so broken Next integration or transpilePackages regressions in consumers would no longer be caught. Please include downstream app build targets for affected package changes, or otherwise make these app builds depend on the package targets in CI.
Useful? React with 👍 / 👎.
| bun run lint:fix | ||
| bun run lint:css | ||
| bun run lint:css:fix | ||
| moon run root:format-check |
There was a problem hiding this comment.
Is this different from how moon works in the monorepo (e.g, iirc, moon :format-check etc) or just another way of doing the same thing?
There was a problem hiding this comment.
it's a little differently setup. the short version is that our formatters and linters are so fast that it didn't make a ton of sense to not just run them from root every time. it's probably true in monorepo as well now, but still has configuration from when it wasn't true.
There was a problem hiding this comment.
that said moonx :format-check will run format-check in all the places it exists, which should just be root anyways. so it should work fine that way.
| bun ws <project> <task> | ||
| bun ws <project> <task> --some --flag | ||
| moon run <project>:<task> | ||
| moonx <project>:<task> # shorthand for moon exec |
There was a problem hiding this comment.
what's the difference between moon exec <project>:<task> and moon <project>:<task>?
There was a problem hiding this comment.
moon run === moonx
CI failed with `Process git failed: short read while indexing .moon/cache/hashes/<hash>.js`: root:lint's input glob (`**/*.js` and friends) and root:format-check's `**/*` are filesystem-walked and matched moon's own cache directory — including the hash manifests moon writes concurrently while the pipeline runs. git hash-object then raced a manifest mid-write and the whole moon ci invocation died. Negate `.moon/cache/**` and `.moon/hooks/**` in both input sets. `.moon/*.yml` configs stay included in format-check's inputs since oxfmt formats them. Verified with a cold full pipeline (maximum concurrent manifest writes): 34 tasks green, and none of the 41 generated hash manifests reference .moon/cache anymore.
Replace custom script orchestration with moon
Migrates the monorepo from hand-rolled task running (
scripts/ws.ts, per-packagenpm scripts,
concurrently, husky, artifact-passing CI) tomoon v2.3.3, pinned in
.prototoolsand enforced viaversionConstraint. Every human/agent/CI entry point is now a moon task;package.jsonscripts are npm lifecycle hooks only.Command surface
bun ws <project> <task>moon run <project>:<task>/moonx <project>:<task>bun run tsc(root or package)moonx <project>:typecheck/moon run :typecheckbun run lint/format/lint:cssmoon run root:lint/root:format/root:lint-cssbun run wt …moonx root:wt -- …test:e2e,dev:demo,icons:sprite, …test-e2e,dev-demo,icons, …Task names are flat kebab-case with one meaning everywhere:
buildproduces dist,devis the persistent watcher,testisbun test,tscis renamedtypecheck.Task architecture
.moon/tasks/bun-common.yml—test+typecheckfor every bun project, bothwith
deps: ['^:build'](cross-package imports and types resolve through eachdependency's built
dist; this replaces everybuild:depsscript and fixed areal race where
diffshub:testcould run beforetheming:buildfinished)..moon/tasks/tag-tsdown.yml—build/devfor the tsdown packages; theming andtrees override to append their post-build asserts.
.moon/tasks/tag-publishable.yml—prepublishguard chain(
assert-bun-version+build) behind each package'sprepublishOnly.moon.ymlfor everything else (apps, benchmarks, e2e, publish).App
devtasks depend on^:build+^:dev: moon finishes dependency builds,then runs dependency watchers alongside the app server —
concurrentlyis gone.Port offsets still come from
.env.worktree(loaded via taskenvFile);wt.ts,run-dev.sh, andload-worktree-env.mjssurvive unchanged in role.CI
One affected-aware lane (plus the unchanged
actions-pinnedguard):moon ci --include-relations <explicit targets>computes affected projects fromthe VCS diff and expands through graph relations, so a package change re-runs its
dependents' builds/tests/typechecks — including trees e2e when affected.
~/.proto, bun's download cache, moon'sportable cache (
hashes+outputs), playwright browsers. No more artifactupload/download between jobs.
moonrepo/setup-toolchain, SHA-pinned); mooninstalls dependencies itself with a frozen lockfile. The bun-version awk parsing,
setup-bun, and manualbun installare gone.Hooks
moon-managed (
vcs.hooks, worktree-compatible — hooks land in.moon/hookswith aworktree-local
core.hooksPath; no install-timeprepare). Pre-commit runsmoon exec :typecheck --affected --status staged(now also checks dependents,which
precommit-tsc.tsmissed) +lint-staged. Pre-push keeps the git-lfs guard;generated hook scripts forward
"$@"and stdin. husky andprecommit-tsc.tsaredeleted.
Vercel
Builds run via the GitHub integration, so each app ships a
vercel.jsonwhosebuildCommandruns the app's moon build (moon comes from a pinned rootdevDependency; PATH covers both repo-root and app-dir Root Directory layouts).
The two docs projects share one file and dispatch on
NEXT_PUBLIC_SITE.docs
distDiris now per-site in every mode (.next/diffs/.next/trees),making the two site builds disjoint and independently cacheable; they share a
mutex only for the site-dependent
public/llms*.txt.Deliberate behavior changes
tsc→typecheckeverywhere.assert-bun-versionguards all five published packages (was diffs-only);storage-elements-next gains its first prepublish guard.
buildis renamedbuild-demo(it builds the demo site).root:lintnow orders after package builds (it resolves@pierre/*types through dist; previously raced
tsdown --clean).runInCIpolicy: graph-edge-free local tasks are'always'(work in CI-markedagent shells); graph-connected ones (
dev/prod, e2e variants, publish guards)stay
'skip'and need aCI=prefix in such shells (CI= moonx docs:dev-diffs).Verification
30 cache hits); affected-skip verified on isolated changes.
moon runwebServer).bun publish --dry-run× 5 fires the prepublish chains (trees' guard correctlyblocks direct publish).
scriptsblock is lifecycle-only; zerobun ws/husky/concurrentlyreferences in live code; all embedded invokers (playwrightwebServers, profiler/test spawns, trees publish flow) point at moon tasks.
broken spawns, lint/build race, Vercel PATH robustness, CI lane consolidation).
Merge checklist (externally gated)
NEXT_PUBLIC_SITEenv (and whether demo is a fourth project)explicit
--basein the workflow)Follow-ups (deferred by design)
(stickydisk remains as L1).
test-e2ein CI — enable deliberately, not as migration fallout.