Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
8c99bd6
build(release): publish per-package workspace tarballs from the flake
systemfsoftware-maker Oct 6, 2026
be31c48
build(release): keep changelogs in the repo and stop pack reaching fo…
systemfsoftware-maker Oct 6, 2026
b740f39
build(release): ship each package's changelog in its tarball
systemfsoftware-maker Oct 6, 2026
ca5411d
chore(release): record the changelog now shipped by stryker-js-cli-co…
systemfsoftware-maker Oct 6, 2026
814bdc5
build(release): refuse an unknown changelog storage in the changelog …
systemfsoftware-maker Oct 7, 2026
49105a1
docs(release): describe repository changelog storage in the changeset…
systemfsoftware-maker Oct 7, 2026
1e2bb0b
build(release): keep api-extractor out of dist and check tarballs aga…
systemfsoftware-maker Oct 7, 2026
a75e156
chore(release): fold the 17.0.1 and 17.0.2 sections into stryker-js's…
systemfsoftware-maker Oct 7, 2026
9ef2e1a
ci(release): pin the release toolchain that reads package changelogs
systemfsoftware-maker Oct 7, 2026
0f125bf
chore(repo): merge main into nix/workspace-tarballs
systemfsoftware-maker Oct 7, 2026
d260683
build(release): version through the changesets strategy and drop the …
systemfsoftware-maker Oct 7, 2026
2e8ef58
build(release): take the pnpm store from one fetch per lockfile entry
systemfsoftware-maker Oct 7, 2026
dd90206
chore(repo): merge main into nix/workspace-tarballs
systemfsoftware-maker Oct 7, 2026
ea8ad7f
chore(repo): merge main into nix/workspace-tarballs
systemfsoftware-maker Oct 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 13 additions & 17 deletions .changeset/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,22 @@ pnpm change --bump <none|patch|minor|major> --summary "<changelog entry>" [<pkg>
- This README is NOT a changeset: the gate requires a file whose frontmatter
parses as `"<pkg>": <none|patch|minor|major>`.

## Two-stage intent deletion
## Changelogs

pnpm unlinks consumed intents only after the npm registry confirms the
versions those intents produced. The cycle is:
`pnpm-workspace.yaml` sets `versioning.changelog.storage: repository`, so
`pnpm version -r` writes each release's `## <version>` section into the
package's committed `CHANGELOG.md`, and that file ships in the package's
tarball. Nothing is parked under `.changeset/changelogs/`.

1. **Version PR.** `pnpm version -r` consumes pending intents, writes
`.changeset/changelogs/<pkg>@<ver>.md`, and records stems in
`ledger.yaml`. The new versions are not on npm yet, so
`confirmPublished()` fails and the intent `.md` files stay on disk.
2. **Publish.** The version PR merges; CI publishes and pushes git tags.
3. **Next version PR.** The next `pnpm version -r` scans
`.changeset/changelogs/`, `verifyPublished()` / `confirmPublished()`
against npm, deletes confirmed changelog files, and unlinks the
intent `.md` files whose releases are all confirmed.
1. **Version PR.** `pnpm version -r` consumes pending intents, writes the
`## <version>` sections, and records the consumed stems in `ledger.yaml`.
2. **Publish.** The version PR merges; CI publishes, pushes git tags, and
creates each GitHub Release from that version's `CHANGELOG.md` section.

If `.changeset/changelogs/` is deleted out of band before that
confirmation, the matching intent files become permanent orphans: pnpm
has nothing left to verify, so it never unlinks them. Remove those
stems by hand only after the ledger already records them and npm
already serves the versions.
`scripts/guards/check-changelog-sections.ts` holds every publishable
package to exactly one section for its current version, refuses any file
under `.changeset/changelogs/`, and fails when the storage setting is
missing or misspelt.

## Interruption safety

Expand Down

This file was deleted.

7 changes: 0 additions & 7 deletions .changeset/changelogs/@systemfsoftware!stryker-js@17.0.2.md

This file was deleted.

5 changes: 5 additions & 0 deletions .changeset/tangy-parents-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@systemfsoftware/stryker-js-cli-contract": patch
---

The published package now includes its changelog.
67 changes: 67 additions & 0 deletions .github/workflows/nix.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Nix

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
name: flake outputs · ${{ matrix.system }}
strategy:
fail-fast: false
matrix:
include:
- system: x86_64-linux
runner: ubuntu-latest
- system: aarch64-linux
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
steps:
- uses: actions/checkout@v7
- uses: cachix/install-nix-action@v31
- name: Workspace tarballs rebuild bit-for-bit
run: |
nix build .#packages.${{ matrix.system }}.workspace-tarballs
nix build --rebuild .#packages.${{ matrix.system }}.workspace-tarballs

tarball-contents:
name: tarballs match pnpm pack of the source
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v7
- uses: cachix/install-nix-action@v31
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: nix build .#packages.x86_64-linux.workspace-tarballs --out-link tarballs
- run: pnpm -r --if-present run build
- name: Each tarball holds the files and CHANGELOG.md pnpm pack takes from the source
run: scripts/guards/check-tarball-contents.sh tarballs
- name: A tarball whose CHANGELOG.md differs from the source fails the check
run: |
sabotaged=$(mktemp -d)
cp -r tarballs/. "$sabotaged"
chmod -R u+w "$sabotaged"
file=$(jq -r '.[0].file' "$sabotaged/index.json")
unpacked=$(mktemp -d)
tar -xzf "$sabotaged/$file" -C "$unpacked"
echo sabotage >>"$unpacked/package/CHANGELOG.md"
tar -czf "$sabotaged/$file" -C "$unpacked" package
if scripts/guards/check-tarball-contents.sh "$sabotaged" 2>sabotage.log; then
echo "a tarball with an edited CHANGELOG.md passed the check" >&2
exit 1
fi
grep -q 'tarball CHANGELOG.md differs' sabotage.log
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,14 @@ The release planner derives a phase from two numbers: the size of the release se

The trap is the second number. `pnpm version -r` consumes an intent into the ledger on the version PR, but it unlinks the intent file only on a later `pnpm version -r`, after `confirmPublished()` sees those versions on npm. Counting `.changeset/*.md` therefore answers "how many intent files exist", never "how many are pending". Read that way, every release leaves the pipeline in the version phase forever, and each push to the default branch opens a `version-packages` pull request that deletes files the previous run already consumed.

If `.changeset/changelogs/` is removed before that confirmation, pnpm never unlinks the matching intents. Those files are orphans, not pending work.

## Guidance

Treat `.changeset/ledger.yaml` as the record of consumption, and count only intents whose stem is absent from it (`countPendingIntents`). Two properties of the ledger decide whether that count is right:

- **pnpm writes it.** `pnpm change` authors the intent; `pnpm version -r` consumes it and renders the ledger (`render_ledger` / `readLedger` in pnpm). No production code in this repository writes the file — the parser's own tests write throwaway copies in a temp directory — so a change that adds a ledger reader adds no writer.
- **A bare, null-parsing key means an empty intent list.** A ledger entry's intents appear as a mapping (`dir:` plus `intents: [...]`), a sequence, or a key whose value parses as YAML null. Null is a release that consumed nothing — not an entry the parser failed to read. Contributing no stems is exactly what "empty" means; treating it as unparsed invents intents.

`isPublished` owns the registry probe that sizes the release set. `openReleasePr` — the release-PR shell entry point — holds the second line of defence: it refuses to open a release PR unless a `packages/**/package.json` or a `.changeset/changelogs/` file was added or modified against the base branch, so a miscount cannot by itself produce an empty release PR. The changelog half matters: an intent folded into a version that is bumped but unpublished rewrites that version's changelog and leaves every `package.json` untouched, while confirming a published release only deletes changelogs.
`isPublished` owns the registry probe that sizes the release set. `openReleasePr` — the release-PR shell entry point — holds the second line of defence: it refuses to open a release PR unless a `packages/**/package.json` or a `packages/**/CHANGELOG.md` file was added or modified against the base branch, so a miscount cannot by itself produce an empty release PR. The changelog half matters: an intent folded into a version that is bumped but unpublished rewrites that version's `CHANGELOG.md` section and leaves every `package.json` untouched.

## Why This Matters

Expand All @@ -48,7 +46,7 @@ The ledger makes consumption a fact recorded beside the intent, so an intent fil

**Pending intents win over unpublished versions.** A failed publish leaves unpublished versions; a later merge can add intents. `decidePhase(owed, pending)` is `pending > 0 ? 'version' : owed > 0 ? 'publish' : 'none'`. Publishing while intents remain ships later commits under the previous changelog. `pnpm version -r` does not bump past an unpublished version: it folds the new intents into it, rewriting only that version's changelog and the ledger. Release run 36084519260 logged `@systemfsoftware/stryker-js: 11.0.0 → 11.0.0 (patch, via intent)` and then, under a `package.json`-only guard, `not opening a release PR`, so `pending` never reached zero and the versions whose publish failed in run 36075147080 were never retried.

**Unlink is registry confirmation, not consumption.** Consumption writes the ledger and a changelog file; unlink waits for `confirmPublished()` / `verifyPublished()` on a subsequent version run. Deleting those changelog files out of band strands the intents as permanent orphans.
**Unlink is registry confirmation, not consumption.** Consumption writes the ledger and the package's `CHANGELOG.md` section; unlink waits for `confirmPublished()` / `verifyPublished()` on a subsequent version run.

**Release runs do not cancel each other.** The Release workflow concurrency group for the default-branch ref sets `cancel-in-progress: false`. A killed version job only mutates `changeset-release/main`. A killed publish job leaves registry 404s in `unpublishedOf()`, so `owed` remains until npm serves the versions.

Expand Down

This file was deleted.

113 changes: 109 additions & 4 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading