Skip to main content

readme

Writes the module versions into the readmes, so they are visible where people actually look.

pnpm dist readme [--check]

It does different work depending on where it runs: in a distribution it records what that distribution ships, and in a module it records that module's own version.

Why

Browsing a distribution on GitHub shows a submodule as a commit hash and nothing else. The version a module is pinned at — the thing anyone wants to know — is invisible exactly there. Writing it into the readmes puts it back.

The distribution's readme gets its own version, then what it ships and what each module is actually pinned at:

Version **26.09.01-alpha.0**

| Module | Version | Pinned at | Path |
| --- | --- | --- | --- |
| `@acme/widget` | 1.2.0 | [v1.2.0](…/tree/v1.2.0) | `apps/widget` |
| `@acme/design` | 0.4.1 | [4d1b9d8](…/commit/4d1b9d8) — unreleased, after v0.4.1 | `packages/design` |
| `@acme/tools` | 0.1.0 | [7137657](…/commit/7137657) — untagged | `apps/tools` |

Pinned at a tag, or past one

A submodule pin is a commit, and a commit may or may not be a release. The three cases read differently on purpose:

  • At a tag — the pin is a released version, and links to the tag.
  • Past a tag — the pin is a commit after the last release, so the version in the manifest is not what is being shipped. It links to the commit and says which release it is ahead of.
  • Never tagged — the module has no releases at all.

A note explaining the second case is added underneath, and only when something is actually unreleased.

Addresses come from the submodule's own remote, rewritten to https so they are browsable. A remote with no web address — a local path — is shown without a link rather than with a broken one.

And each module's own readme states its version at the top:

# @acme/widget

Version **1.2.0**

At the end, the same readme gets the module's release flow — the Publish workflow, how it holds pull requests, pnpm next-version, where a fix to an older minor goes — with that module's own version and branch names filled in. The words are the same in every module, so "how do I release this" is answered where someone is already standing, and it is last because most people reading a readme are there to use the module, not to release it.

The distribution's readme ends the same way, with how a distribution is released: its calendar versions, the Publish workflow and the hold, and pnpm next-version moving it on. That section states no version, so the only line next-version has to rewrite in a distribution's readme is the one at the top — which it does, without reading or touching the modules.

The version block is only positioned when it is first inserted, under the first heading; moving it afterwards is left alone, since rearranging somebody's readme is not this command's business. The release block is the exception: it is kept last, wherever it was, so everything added to a readme goes above it.

It names no distribution. The same module is shipped in several — that is what extending one is for — so naming one would be a claim the module cannot make. The relationship lives in the distribution's readme, which is the side that knows it.

Versions come from the checkouts, the same as everywhere else — see Versioning.

In a module, on its own release

Run inside a module — a repository with no submodules — it records that module's version from its own package.json and nothing else:

# @acme/widget

Version **2.1.0**

No table, because a module ships nothing. This is what keeps a module's release flow its own: bump the version, run pnpm dist readme, tag, publish. The distribution finds out when someone bumps the submodule, which may be much later or never.

next-version runs this on both branches it creates, so this is usually only run by hand between releases.

npm version minor
pnpm dist readme          # the readme follows the manifest
git commit -am "Release" && git tag "v$(node -p "require('./package.json').version")"

How the file is edited

Each block is delimited by a pair of comments:

<!-- fg:version -->

Version **1.2.0**

<!-- /fg:version -->

The markdown is parsed to find that pair, and the new text is spliced into the original between them. Everything around it keeps its wording and formatting, and a mention of the marker inside a code span is not mistaken for the block itself.

A readme with no block yet gets one after its first heading, which is where someone looking for a version looks first. A module with no readme at all gets one — the point is that the version is visible.

Running it again when nothing has moved rewrites nothing.

Keeping it current

The commands that move a pin — bump, fork, contribute, unfork, inherit, add-module, extract — rewrite the distribution's readme as they go, and next-version its version. Only the distribution's: writing into a module's readme would leave that submodule dirty, and bump refuses to move a dirty one. Run pnpm dist readme for the modules' own.

In CI

pnpm dist readme --check

Exits non-zero and names the readmes that no longer match, without writing anything. In CI, verify runs it — on the distribution's own readme in a distribution, and on the module's in a module — along with the other files fg-dist writes.