The repository is a pnpm workspace orchestrated by turbo. The following commands cover the full development loop:
pnpm install
pnpm generate # regenerate all generated code (must leave a clean tree)
pnpm build # build all packages
pnpm test # type checks, treeshakability checks and unit tests
pnpm lint # oxlint && oxfmt --check
pnpm lint:fixMost generated/ directories — those under @codama/node-types, @codama/nodes and @codama/visitors-core — are produced by the private @codama-internal/spec-generators package from the @codama/spec meta-model, and must never be edited by hand. CI regenerates them and fails on any diff, so committed generated code always matches the single living spec pin in packages/spec-generators/package.json:
@codama/specis the living pin: the spec version the current major is generated from. It is exact rather than a range, so generated output only changes through a deliberate, reviewable pin (or generator) change.
The generator is single-major: it only renders the spec on its own branch. Node types for older majors live as frozen static snapshots under packages/upgrade/src/vN/generated (e.g. src/v1/generated); these are hand-maintained source, are not regenerated by pnpm generate, and are excluded from the CI freshness check. See packages/upgrade/src/v1/README.md.
Any user-facing change needs a changeset: run npx changeset add --empty and edit the created file. One changeset file per concern; entries start with a verb. Never edit CHANGELOG.md files — they are generated by the release workflow. Note that the core packages (codama, @codama/errors, @codama/node-types, @codama/nodes, @codama/validators and the @codama/visitors* packages) version in lockstep as a fixed group, while the remaining packages version independently — below a shared ceiling: all public packages carry the same major version (the era, matching the branch name), so no package ever bumps its major on its own; breaking changes to individual packages wait for the next major cut. The era currently coincides with the spec major it supports — a convenience, not a rule: an ecosystem-impacting codama-only major (no spec change) is legitimate and runs the full candidacy per RELEASING.md.
Branch, dist-tag and lifecycle mechanics (cut / bake / promote) are defined once for the whole ecosystem in the spec repository's RELEASING.md. Releasing major N+1 of the Codama spec additionally requires the following steps specific to this repository. The design intent is that this list never grows: one new upgrade function per major, everything else mechanical.
- Freeze the vN node types. Before moving the living
@codama/specpin to the (N+1).x release, snapshot the current vN node types into@codama/upgrade: copy@codama/node-types/src/generatedintopackages/upgrade/src/vN/generated, keeping the layout identical so a later backported vN change can be ported forward by applying the same patch. Also copy the hand-written siblings (brands.ts,Docs.ts,Version.ts) next to it as vN-shaped frozen copies, so the snapshot is self-contained, and export the vN types as a type-only namespace (export type * as vN) from the package index. The snapshot is static source — it is never regenerated. Then move the living@codama/specpin to the (N+1).x release and runpnpm generate, which rewrites the livinggenerated/dirs and restampsCODAMA_VERSION. - Write the upgrade step. Add a single hand-written, pure
upgradeVNToVN+1function to@codama/upgrade— a JSON-tree-in, JSON-tree-out converter in thenodes-from-anchortop-down style — and wire it intoupgrade()as itsif (major <= N)block. - Run the ecosystem lifecycle. Cut this repository's
N.xmaintenance branch per RELEASING.md, after whichmainhosts the vN+1 work, with one codama-specific detail: the seeded major changeset covers all public packages, upholding the same-major invariant.mainthen versions as(N+1).0.0-rc.nunder thercdist-tag through the candidacy, and is promoted tolatestwhileN.xswitches torelease-N.x. Old majors receive clarifications and documentation fixes only, never semantic changes.
Two invariants protect consumers and must never be broken:
- The upgrade chain is append-only. The upgrade functions and frozen node types in
@codama/upgradeare committed source with zero runtime dependencies; they are never removed or rewritten, so every major back to 1.0.0 stays upgradable forever. - The generator is single-major; snapshots are static. The generator on
mainonly ever renders the living spec of the current major — it is never forked or taught to speak an older spec dialect. Older majors' node types are frozen static snapshots underpackages/upgrade/src/vN/generated, whose layout mirrors that major's@codama/node-types/src/generatedso a rare backported change is ported forward as a patch. Slightly stale docblocks in a snapshot are acceptable, since old majors never change shape.