Polyglot monorepos release orchestration: conv commits in, ordered release out

yohimik1 pts0 comments

The monorepo release tool | dispat

Skip to main content<br>Why one more monorepo tool?<br>Every major monorepo tool can topologically sort a dependency graph: build everything in order, then publish everything, or publish only what changed, one package at a time. Two situations break that model in practice:<br>An error in the middle of a run. Half the packages are published and half are not. Most tools either abort the whole run or plough on and leave you to reconstruct what shipped. Re-running tends to re-release what is already out, so you end up writing recovery scripts by hand.<br>A consumer that can only be built once its provider is published. A Node package can be built before its consumers publish, but a Docker image is often buildable only by pulling its base image from a registry, which means the provider has to be published first. Build everything then publish everything assumes every ecosystem behaves like npm, and mixed graphs break it.<br>Modern projects are exactly that mix: many packages on different infrastructure, npm next to Docker next to Go, wired into one dependency graph. dispat is built for that case. Concepts works both situations through end to end.<br>Releases the graph, not a list<br>Consumer and provider ordering, parallel builds and publishes with separate concurrency budgets, and isBuildWaitingPublish for the ecosystems, Docker among them, where a consumer can only build once its provider is published.

Blast radius written in the commit<br>feat(core): releases core alone, ^ reaches its direct consumers, ^^ the transitive closure, and +N exactly N edges. Nothing is released on a guess.

Self-healing runs, because a release is a distributed transaction<br>Publishing a graph means irreversible writes across independent services with no rollback to fall back on, so each package's leg commits by durably recording its own completion: the annotated git tag, written only once the publish succeeded. A broken package skips only its true dependants while everything else keeps releasing, and re-running is the recovery: the plan is a pure function of history, graph and configuration, so the next run recomputes the same transaction and executes only the legs whose record is missing. No state files, no double releases and no repair scripts. Details.

Release control from commits<br>%beta starts a prerelease train and %beta>stable graduates it, Release-As: none holds a package and Release-As: auto resumes it, Release-As: 2.0.0 pins an exact version and cancel(pkg) discards pending work. It is written in commits, so release decisions are reviewed and versioned like code. Details.

Polyglot by construction: any language, any registry, any tooling<br>Stages are shell commands fed a rich DISPAT_* environment, release state lives in git tags, and dispat reads and rewrites twenty-three manifest formats across fifteen ecosystems, npm to go.mod to Podfile, so dispat compute can derive the dependency graph and each package's starting version from the repository you already have. And because an unchanged package is simply not in the plan, there is no task cache to manage, clear or distrust: BuildKit layers, an Nx, Turborepo or Bazel cache and the Gradle build cache all keep working inside the stage, and none of them can change what is versioned, ordered or tagged.

Every release step is also a command, with the records built in<br>Per-package changelogs, annotated tags, GitHub releases and an optional release commit, each also runnable alone (dispat changelog, commit, github) with the release stage finding the work done and skipping it. dispat status dry-runs the whole plan, a release lock refuses two releases of one repository at once, and dispat release -p core (or -s libs, -g platform) ships a subset at exactly the versions a full release would have given it, with dispat if, exec, autowriter and autoreplacer as the glue a custom pipeline is assembled from. Details.

Lightweight libraries, usable on their own<br>Parsing commit messages and reading and rewriting dependency manifests are problems far older than releases, so dispat keeps all three as standalone Go modules with no dependency on the CLI, on git or on a network. The manifest pair shares its vocabulary through pkg/manifest (dependency kinds, manifest file-name rules, PEP 503 normalisation) so the reader and the writer can never drift apart.<br>pkg/ccme: the commit parser<br>Conventional Commits, Monorepo Extension: a strict superset of Conventional Commits 1.0.0 that adds scopes as packages, propagation depth and prerelease channels. No regular expressions: one left-to-right index scan with a byte of lookahead, no backtracking, no recursion, O(n) time and O(1) working space, which is what matters when the input is untrusted commit messages in CI. The specification is vendored beside it as SPEC.md, and every section reference in the code points into it.

pkg/scanner: the manifest reader<br>Thin per-format parsers turning every manifest below into one ecosystem-neutral shape: declared...

release dispat package commits dependency graph

Related Articles