release-train

The release train reads the npm packages of the GitHub account mark1russell7. It makes the dependency graph of these packages and a plan of the next releases and updates.

Stage 1 only reads. Stage 2 (apply) opens a pull request for each update of the current wave. Stage 3 (the template sync, also in apply) keeps the files from the template repository current in each repository. The train does not publish to npm itself: the release workflow of each repository publishes.

What it reads

A package is a package.json with a name and without "private": true. An edge is a dependency on a package of the account.

The actions of the plan

Action When
new-package The package is not on npm. Only the owner can make a new package.
release The version on main is newer than npm, or a dependency range of the package changes.
adopt-in-range The range lets the newest version in, but the lockfile holds an older version. A lockfile update is sufficient.
adopt-breaking The newest version is outside the range. The range must change.

Each repository gets one combined action of each kind. The plan puts the actions in a topological sequence and shows each cycle.

Use

GH_TOKEN=$(gh auth token) pnpm --filter @release-train/release-train plan
node packages/release-train/src/bin.ts plan --json
node packages/release-train/src/bin.ts plan --write-db <folder>

CI

plan.yml computes the plan every 6 hours and on a manual start. It keeps plan.json as a workflow artifact. It uses the default token, so it sees only public repositories.

Stage 2: apply

apply makes the plan again from the real state, then changes the repositories. It keeps no state between runs.

GH_TOKEN=<token> node packages/release-train/src/bin.ts apply --dry-run
node packages/release-train/src/bin.ts apply --only <repo> --report apply.json

Releases

When a dependency gets a new version outside the range of a dependent (a new major version, or a new minor version before 1.0.0), the range of the dependent changes. The dependent must then have a new release too, else its users do not get the change. The train puts this release into the same pull request as the range change.

After the merge, the release workflow of the repository publishes the new version with OIDC. Some release workflows start only by hand (for example with changesets: one start makes the version pull request, and one start after its merge publishes). With --release-dispatch, the train merges a green version pull request (branch changeset-release/main) and starts such a workflow when CI on main is green. This needs the permission Actions: write of the GitHub App.

The report of apply lists the versions in flight. Such a version is on main of a public repository, and npm does not have it. Its release workflow starts on a push and its last run did not fail, or the workflow is active, or the train started it. The command await-release --report <file> --minutes <n> checks npm every 30 seconds. When npm has one of these versions, it sends the released event to the train, and the next wave starts at once.

When CI of a train pull request fails, the pull request gets the label release-train:blocked. plan --write-db then writes an alert into the collection alerts of the tracker. The strategist tells the role that owns the repository.

Stage 3: template sync

The template sync keeps the files from the repository template current in each other repository. Each rule is data in SYNC_RULES (src/sync.ts): an id, a reason and a check that gives the edits. To add a rule, add an object to this list.

The rules do not change the repository template. Each repository gets one branch release-train/template and one pull request with the label release-train, apart from the update pull request. The body tells each change and its reason, the changes that wait, and the problems that need a hand. The fingerprint in the body stops a push when the changes are the same. CI, the label release-train:blocked, the merge on green and the close follow the rules of the update pull request.

GitHub refuses a change to a workflow file from an App without the permission Workflows: write. Until the App has this permission, the rules pins and release-scripts wait, and the pull request and the summary tell "The change waits for the Workflows permission of the App." The other rules work. The run does not fail.

The train workflow

train.yml starts every 30 minutes, on a manual start and on the released event of repository_dispatch. It makes a token of the GitHub App mark1russell7-release-train with actions/create-github-app-token. Pull requests with that token start CI in the repositories.