From a31b25d44c476febcb84a258390b70b27f38b13a Mon Sep 17 00:00:00 2001 From: Ryan Welcher Date: Fri, 23 Oct 2026 10:05:27 -0400 Subject: [PATCH 1/9] Add a release skill that walks RELEASING.md with stops before each public step --- .agents/skills/release/SKILL.md | 56 +++++++++++++++++++++++++++++++++ .claude/skills/release/SKILL.md | 56 +++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 .agents/skills/release/SKILL.md create mode 100644 .claude/skills/release/SKILL.md diff --git a/.agents/skills/release/SKILL.md b/.agents/skills/release/SKILL.md new file mode 100644 index 00000000..d0cdb5ae --- /dev/null +++ b/.agents/skills/release/SKILL.md @@ -0,0 +1,56 @@ +--- +name: release +description: Cut a release of the WordPress Contributor Toolkit, stable or beta, from the version bump to the published GitHub release and docs deploy. Use when the user asks to release, cut, ship or publish a version, bump the version for a release, or names a version such as 2.0.0 or 2.1.0-beta.1 to release. +--- + +# Release + +Drive a release from the version bump to the closed milestone, pausing wherever a step is public or needs a person. + +## Procedure + +**Read [`RELEASING.md`](../../../RELEASING.md) and follow it.** It is the specification for this task, not background reading: the version format, the three files to change, the i18n download, the artifact checks, the `gh release create` command, the docs dispatch and the milestone. Do not restate it here; run it. Changes to how releases work go there, not into this wrapper. + +The skill takes the version as its argument (`2.0.0`, `2.1.0-beta.1`). If none is given, ask. A version with `-beta.N` is a beta; anything else is stable. + +What this skill adds on top: + +**Check the starting state first.** Stop and report if trunk is behind `origin/trunk`, if `git status --porcelain --untracked-files=no` prints anything, or if `vX.Y.Z` is already a tag. Untracked files do not block a release. Report, without stopping, any open issues left in the `vX.Y.Z` milestone; they are moved in the last step. + +**Stop before every public or irreversible action and wait for a yes.** There are five: + +1. Creating the release branch. Name it `release/X.Y.Z` and confirm the name. +2. Pushing the branch and opening the bump pull request. +3. Pushing to the branch again, when it has to be updated with trunk before the merge. +4. `gh release create`, after showing the final notes and the exact command. +5. Dispatching the docs workflow and closing the milestone, moving any open issues to the next milestone by version. + +The merge is the person's, never this skill's. Wait to be told it is merged. + +**The bump pull request is an ordinary pull request.** Fill in `.github/pull_request_template.md` and run the review AGENTS.md requires before opening it. Put the i18n coverage table in the description. Check the diff with `git diff trunk -- . ':!src/languages'`: three lines removed, three added, all version fields. + +**The person downloads the artifacts; this skill checks them.** Ask the person to download the three files from the Buildkite build for the pull request's current head commit (`gh pr view N --json headRefOid`) into a directory they name. Then, in that directory: + +- Remove the `dist_` prefix from the Windows file and check the three names against `RELEASING.md` step 2. +- Mount the `.dmg` with `hdiutil attach -nobrowse -readonly`, then in the mounted app: + - `npx @electron/asar list Contents/Resources/app.asar` holds exactly `/node_modules`, `/package.json` and `/src` at its root, with no `.codesigning`. + - `npx @electron/asar extract-file Contents/Resources/app.asar node_modules/npm/package.json` gives a `version` starting with `11.`. + - `plutil -extract CFBundleShortVersionString raw Contents/Info.plist` reads `X.Y.Z`. +- Detach the image and report each result. Say that the asar contents were checked on the macOS artifact only. + +**Draft the release notes once the pull request is merged.** Get the squash commit with `gh pr view N --json mergeCommit`; it is the release's `--target`. The base is the previous stable tag for a stable release (`git describe --tags --abbrev=0 --exclude '*-beta*' ^`) and the previous tag of any kind for a beta. The commit count is `git rev-list --count ..`. A stable release that follows betas covers everything since the base, the betas' changes included, and says the betas came first. For the shape, read the model `RELEASING.md` names for a stable release, or the last beta's notes for a beta. Write them plainly. Save them to the scratchpad or a temporary directory, not the repository, and show them in the chat for editing. + +**Pass full paths to `gh release create`.** The notes file and the three artifacts are outside the repository, so name each by its path. A stable release adds `--latest`; a beta adds `--prerelease`. + +**Verify the end state on GitHub before reporting done.** + +- `gh release view vX.Y.Z` shows the three assets with the right names and the right prerelease flag. +- `gh api repos/WordPress/contributor-toolkit/releases/latest --jq .tag_name` names this tag for a stable release, and not for a beta. +- `gh api repos/WordPress/contributor-toolkit/git/ref/tags/vX.Y.Z --jq .object.sha` is the squash commit. +- `gh run list --workflow docs.yml -L 1` shows the docs run for the tag. +- The milestone is closed. + +## Notes + +- This is the same skill as [`.claude/skills/release/`](../../../.claude/skills/release/), placed here because agents built on the Agent Skills open standard discover skills under `.agents/skills/`. Both are thin wrappers over `RELEASING.md`. +- If a step in `RELEASING.md` turns out wrong or incomplete during a release, say so at the end. That file is meant to accumulate what each release teaches. diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md new file mode 100644 index 00000000..7d9e9da7 --- /dev/null +++ b/.claude/skills/release/SKILL.md @@ -0,0 +1,56 @@ +--- +name: release +description: Cut a release of the WordPress Contributor Toolkit, stable or beta, from the version bump to the published GitHub release and docs deploy. Use when the user asks to release, cut, ship or publish a version, bump the version for a release, or names a version such as 2.0.0 or 2.1.0-beta.1 to release. +--- + +# Release + +Drive a release from the version bump to the closed milestone, pausing wherever a step is public or needs a person. + +## Procedure + +**Read `RELEASING.md` and follow it.** It is the specification for this task, not background reading: the version format, the three files to change, the i18n download, the artifact checks, the `gh release create` command, the docs dispatch and the milestone. Do not restate it here; run it. Changes to how releases work go there, not into this wrapper. + +The skill takes the version as its argument (`2.0.0`, `2.1.0-beta.1`). If none is given, ask. A version with `-beta.N` is a beta; anything else is stable. + +What this skill adds on top: + +**Check the starting state first.** Stop and report if trunk is behind `origin/trunk`, if `git status --porcelain --untracked-files=no` prints anything, or if `vX.Y.Z` is already a tag. Untracked files do not block a release. Report, without stopping, any open issues left in the `vX.Y.Z` milestone; they are moved in the last step. + +**Stop before every public or irreversible action and wait for a yes.** There are five: + +1. Creating the release branch. Name it `release/X.Y.Z` and confirm the name. +2. Pushing the branch and opening the bump pull request. +3. Pushing to the branch again, when it has to be updated with trunk before the merge. +4. `gh release create`, after showing the final notes and the exact command. +5. Dispatching the docs workflow and closing the milestone, moving any open issues to the next milestone by version. + +The merge is the person's, never this skill's. Wait to be told it is merged. + +**The bump pull request is an ordinary pull request.** Fill in `.github/pull_request_template.md` and run the review AGENTS.md requires before opening it. Put the i18n coverage table in the description. Check the diff with `git diff trunk -- . ':!src/languages'`: three lines removed, three added, all version fields. + +**The person downloads the artifacts; this skill checks them.** Ask the person to download the three files from the Buildkite build for the pull request's current head commit (`gh pr view N --json headRefOid`) into a directory they name. Then, in that directory: + +- Remove the `dist_` prefix from the Windows file and check the three names against `RELEASING.md` step 2. +- Mount the `.dmg` with `hdiutil attach -nobrowse -readonly`, then in the mounted app: + - `npx @electron/asar list Contents/Resources/app.asar` holds exactly `/node_modules`, `/package.json` and `/src` at its root, with no `.codesigning`. + - `npx @electron/asar extract-file Contents/Resources/app.asar node_modules/npm/package.json` gives a `version` starting with `11.`. + - `plutil -extract CFBundleShortVersionString raw Contents/Info.plist` reads `X.Y.Z`. +- Detach the image and report each result. Say that the asar contents were checked on the macOS artifact only. + +**Draft the release notes once the pull request is merged.** Get the squash commit with `gh pr view N --json mergeCommit`; it is the release's `--target`. The base is the previous stable tag for a stable release (`git describe --tags --abbrev=0 --exclude '*-beta*' ^`) and the previous tag of any kind for a beta. The commit count is `git rev-list --count ..`. A stable release that follows betas covers everything since the base, the betas' changes included, and says the betas came first. For the shape, read the model `RELEASING.md` names for a stable release, or the last beta's notes for a beta. Write them plainly. Save them to the scratchpad or a temporary directory, not the repository, and show them in the chat for editing. + +**Pass full paths to `gh release create`.** The notes file and the three artifacts are outside the repository, so name each by its path. A stable release adds `--latest`; a beta adds `--prerelease`. + +**Verify the end state on GitHub before reporting done.** + +- `gh release view vX.Y.Z` shows the three assets with the right names and the right prerelease flag. +- `gh api repos/WordPress/contributor-toolkit/releases/latest --jq .tag_name` names this tag for a stable release, and not for a beta. +- `gh api repos/WordPress/contributor-toolkit/git/ref/tags/vX.Y.Z --jq .object.sha` is the squash commit. +- `gh run list --workflow docs.yml -L 1` shows the docs run for the tag. +- The milestone is closed. + +## Notes + +- This is the same skill as [`.agents/skills/release/`](../../../.agents/skills/release/), for agents that read the Agent Skills open-standard path. Both are thin wrappers over `RELEASING.md`. +- If a step in `RELEASING.md` turns out wrong or incomplete during a release, say so at the end. That file is meant to accumulate what each release teaches. From 5fb565e7a5b34923915e3dcba49f2d40fa08cf30 Mon Sep 17 00:00:00 2001 From: Ryan Welcher Date: Fri, 23 Oct 2026 10:10:11 -0400 Subject: [PATCH 2/9] Move the release commands into RELEASING.md and point AGENTS.md at the release skill --- .agents/skills/release/SKILL.md | 35 +++++----------- .claude/skills/release/SKILL.md | 35 +++++----------- AGENTS.md | 6 ++- RELEASING.md | 71 ++++++++++++++++++++++++++------- 4 files changed, 81 insertions(+), 66 deletions(-) diff --git a/.agents/skills/release/SKILL.md b/.agents/skills/release/SKILL.md index d0cdb5ae..92cbb947 100644 --- a/.agents/skills/release/SKILL.md +++ b/.agents/skills/release/SKILL.md @@ -5,50 +5,35 @@ description: Cut a release of the WordPress Contributor Toolkit, stable or beta, # Release -Drive a release from the version bump to the closed milestone, pausing wherever a step is public or needs a person. +Drive a release through `RELEASING.md`, pausing wherever a step is public or needs a person. ## Procedure -**Read [`RELEASING.md`](../../../RELEASING.md) and follow it.** It is the specification for this task, not background reading: the version format, the three files to change, the i18n download, the artifact checks, the `gh release create` command, the docs dispatch and the milestone. Do not restate it here; run it. Changes to how releases work go there, not into this wrapper. +**Read [`RELEASING.md`](../../../RELEASING.md) and follow it.** It is the specification for this task, not background reading: every step and every command lives there, including the artifact checks, how to pick the base for the release notes, and the checks after publishing. Do not restate it here; run it. Changes to how releases work go there, not into this wrapper. The skill takes the version as its argument (`2.0.0`, `2.1.0-beta.1`). If none is given, ask. A version with `-beta.N` is a beta; anything else is stable. What this skill adds on top: -**Check the starting state first.** Stop and report if trunk is behind `origin/trunk`, if `git status --porcelain --untracked-files=no` prints anything, or if `vX.Y.Z` is already a tag. Untracked files do not block a release. Report, without stopping, any open issues left in the `vX.Y.Z` milestone; they are moved in the last step. +**Check the starting state first.** Run `git fetch origin trunk --tags`, then stop and report if `git rev-parse trunk` differs from `git rev-parse origin/trunk`, if `git status --porcelain --untracked-files=no` prints anything, or if `git ls-remote --tags origin refs/tags/vX.Y.Z` prints anything. Untracked files do not block a release. If a milestone named `vX.Y.Z` exists, report any issues still open in it, without stopping. -**Stop before every public or irreversible action and wait for a yes.** There are five: +**Wait for the person's yes before each of these:** 1. Creating the release branch. Name it `release/X.Y.Z` and confirm the name. 2. Pushing the branch and opening the bump pull request. -3. Pushing to the branch again, when it has to be updated with trunk before the merge. +3. Pushing to the branch again, when it has to be updated with trunk before the merge. Then repeat the artifact checks on the new head's build. 4. `gh release create`, after showing the final notes and the exact command. -5. Dispatching the docs workflow and closing the milestone, moving any open issues to the next milestone by version. +5. Dispatching the docs workflow, and closing the milestone if the release has one. The merge is the person's, never this skill's. Wait to be told it is merged. -**The bump pull request is an ordinary pull request.** Fill in `.github/pull_request_template.md` and run the review AGENTS.md requires before opening it. Put the i18n coverage table in the description. Check the diff with `git diff trunk -- . ':!src/languages'`: three lines removed, three added, all version fields. +**The bump pull request is an ordinary pull request.** Fill in `.github/pull_request_template.md` and run the review AGENTS.md requires before opening it. -**The person downloads the artifacts; this skill checks them.** Ask the person to download the three files from the Buildkite build for the pull request's current head commit (`gh pr view N --json headRefOid`) into a directory they name. Then, in that directory: +**The person downloads the artifacts; this skill checks them.** Ask the person to download the three files from the build for the pull request's current head into a directory they name, then run the checks in `RELEASING.md` step 2 on them and report each result. -- Remove the `dist_` prefix from the Windows file and check the three names against `RELEASING.md` step 2. -- Mount the `.dmg` with `hdiutil attach -nobrowse -readonly`, then in the mounted app: - - `npx @electron/asar list Contents/Resources/app.asar` holds exactly `/node_modules`, `/package.json` and `/src` at its root, with no `.codesigning`. - - `npx @electron/asar extract-file Contents/Resources/app.asar node_modules/npm/package.json` gives a `version` starting with `11.`. - - `plutil -extract CFBundleShortVersionString raw Contents/Info.plist` reads `X.Y.Z`. -- Detach the image and report each result. Say that the asar contents were checked on the macOS artifact only. +**Draft the release notes once the pull request is merged.** Write them plainly, save them to the scratchpad or a temporary directory, not the repository, and show them in the chat for editing before step 4. -**Draft the release notes once the pull request is merged.** Get the squash commit with `gh pr view N --json mergeCommit`; it is the release's `--target`. The base is the previous stable tag for a stable release (`git describe --tags --abbrev=0 --exclude '*-beta*' ^`) and the previous tag of any kind for a beta. The commit count is `git rev-list --count ..`. A stable release that follows betas covers everything since the base, the betas' changes included, and says the betas came first. For the shape, read the model `RELEASING.md` names for a stable release, or the last beta's notes for a beta. Write them plainly. Save them to the scratchpad or a temporary directory, not the repository, and show them in the chat for editing. - -**Pass full paths to `gh release create`.** The notes file and the three artifacts are outside the repository, so name each by its path. A stable release adds `--latest`; a beta adds `--prerelease`. - -**Verify the end state on GitHub before reporting done.** - -- `gh release view vX.Y.Z` shows the three assets with the right names and the right prerelease flag. -- `gh api repos/WordPress/contributor-toolkit/releases/latest --jq .tag_name` names this tag for a stable release, and not for a beta. -- `gh api repos/WordPress/contributor-toolkit/git/ref/tags/vX.Y.Z --jq .object.sha` is the squash commit. -- `gh run list --workflow docs.yml -L 1` shows the docs run for the tag. -- The milestone is closed. +**Run the checks in `RELEASING.md` step 7 before reporting done**, and report each one. ## Notes diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 7d9e9da7..b952ad8e 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -5,50 +5,35 @@ description: Cut a release of the WordPress Contributor Toolkit, stable or beta, # Release -Drive a release from the version bump to the closed milestone, pausing wherever a step is public or needs a person. +Drive a release through `RELEASING.md`, pausing wherever a step is public or needs a person. ## Procedure -**Read `RELEASING.md` and follow it.** It is the specification for this task, not background reading: the version format, the three files to change, the i18n download, the artifact checks, the `gh release create` command, the docs dispatch and the milestone. Do not restate it here; run it. Changes to how releases work go there, not into this wrapper. +**Read `RELEASING.md` and follow it.** It is the specification for this task, not background reading: every step and every command lives there, including the artifact checks, how to pick the base for the release notes, and the checks after publishing. Do not restate it here; run it. Changes to how releases work go there, not into this wrapper. The skill takes the version as its argument (`2.0.0`, `2.1.0-beta.1`). If none is given, ask. A version with `-beta.N` is a beta; anything else is stable. What this skill adds on top: -**Check the starting state first.** Stop and report if trunk is behind `origin/trunk`, if `git status --porcelain --untracked-files=no` prints anything, or if `vX.Y.Z` is already a tag. Untracked files do not block a release. Report, without stopping, any open issues left in the `vX.Y.Z` milestone; they are moved in the last step. +**Check the starting state first.** Run `git fetch origin trunk --tags`, then stop and report if `git rev-parse trunk` differs from `git rev-parse origin/trunk`, if `git status --porcelain --untracked-files=no` prints anything, or if `git ls-remote --tags origin refs/tags/vX.Y.Z` prints anything. Untracked files do not block a release. If a milestone named `vX.Y.Z` exists, report any issues still open in it, without stopping. -**Stop before every public or irreversible action and wait for a yes.** There are five: +**Wait for the person's yes before each of these:** 1. Creating the release branch. Name it `release/X.Y.Z` and confirm the name. 2. Pushing the branch and opening the bump pull request. -3. Pushing to the branch again, when it has to be updated with trunk before the merge. +3. Pushing to the branch again, when it has to be updated with trunk before the merge. Then repeat the artifact checks on the new head's build. 4. `gh release create`, after showing the final notes and the exact command. -5. Dispatching the docs workflow and closing the milestone, moving any open issues to the next milestone by version. +5. Dispatching the docs workflow, and closing the milestone if the release has one. The merge is the person's, never this skill's. Wait to be told it is merged. -**The bump pull request is an ordinary pull request.** Fill in `.github/pull_request_template.md` and run the review AGENTS.md requires before opening it. Put the i18n coverage table in the description. Check the diff with `git diff trunk -- . ':!src/languages'`: three lines removed, three added, all version fields. +**The bump pull request is an ordinary pull request.** Fill in `.github/pull_request_template.md` and run the review AGENTS.md requires before opening it. -**The person downloads the artifacts; this skill checks them.** Ask the person to download the three files from the Buildkite build for the pull request's current head commit (`gh pr view N --json headRefOid`) into a directory they name. Then, in that directory: +**The person downloads the artifacts; this skill checks them.** Ask the person to download the three files from the build for the pull request's current head into a directory they name, then run the checks in `RELEASING.md` step 2 on them and report each result. -- Remove the `dist_` prefix from the Windows file and check the three names against `RELEASING.md` step 2. -- Mount the `.dmg` with `hdiutil attach -nobrowse -readonly`, then in the mounted app: - - `npx @electron/asar list Contents/Resources/app.asar` holds exactly `/node_modules`, `/package.json` and `/src` at its root, with no `.codesigning`. - - `npx @electron/asar extract-file Contents/Resources/app.asar node_modules/npm/package.json` gives a `version` starting with `11.`. - - `plutil -extract CFBundleShortVersionString raw Contents/Info.plist` reads `X.Y.Z`. -- Detach the image and report each result. Say that the asar contents were checked on the macOS artifact only. +**Draft the release notes once the pull request is merged.** Write them plainly, save them to the scratchpad or a temporary directory, not the repository, and show them in the chat for editing before step 4. -**Draft the release notes once the pull request is merged.** Get the squash commit with `gh pr view N --json mergeCommit`; it is the release's `--target`. The base is the previous stable tag for a stable release (`git describe --tags --abbrev=0 --exclude '*-beta*' ^`) and the previous tag of any kind for a beta. The commit count is `git rev-list --count ..`. A stable release that follows betas covers everything since the base, the betas' changes included, and says the betas came first. For the shape, read the model `RELEASING.md` names for a stable release, or the last beta's notes for a beta. Write them plainly. Save them to the scratchpad or a temporary directory, not the repository, and show them in the chat for editing. - -**Pass full paths to `gh release create`.** The notes file and the three artifacts are outside the repository, so name each by its path. A stable release adds `--latest`; a beta adds `--prerelease`. - -**Verify the end state on GitHub before reporting done.** - -- `gh release view vX.Y.Z` shows the three assets with the right names and the right prerelease flag. -- `gh api repos/WordPress/contributor-toolkit/releases/latest --jq .tag_name` names this tag for a stable release, and not for a beta. -- `gh api repos/WordPress/contributor-toolkit/git/ref/tags/vX.Y.Z --jq .object.sha` is the squash commit. -- `gh run list --workflow docs.yml -L 1` shows the docs run for the tag. -- The milestone is closed. +**Run the checks in `RELEASING.md` step 7 before reporting done**, and report each one. ## Notes diff --git a/AGENTS.md b/AGENTS.md index e5fb3df5..2130a033 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ Instructions for AI coding agents working in this repository. Tool-neutral and c ## Where things are, whatever agent you use -Two files, and neither is specific to one tool despite what their paths suggest: +The review standard and its skill, and neither is specific to one tool despite what their paths suggest: **The review standard** — [`.github/instructions/code-review.instructions.md`](.github/instructions/code-review.instructions.md). The five dimensions, this project's invariants, the procedure to run them, and the reporting format. Read it directly if your agent has not already. @@ -15,12 +15,14 @@ It sits under `.github/instructions/` because Copilot code review reads that dir - [`.claude/skills/self-review/`](.claude/skills/self-review/) — Claude Code and Copilot. In Claude Code it is `/self-review`. - [`.agents/skills/self-review/`](.agents/skills/self-review/) — the Agent Skills open-standard path that Command Code and others scan. -Both are pointers to the one instructions file, not copies of the standard. If your agent looks somewhere else again (`.github/skills/`, `.cursor/skills/`, `.codex/skills/`, or its own convention), add a wrapper there or skip it entirely and follow the instructions file — that is where all the content lives. The wrapper only adds two things: run the judgement pass in a fresh context, and report without touching GitHub. +Both are pointers to the one instructions file, not copies of the standard. If your agent looks somewhere else again (`.github/skills/`, `.cursor/skills/`, `.codex/skills/`, or its own convention), add a wrapper there or skip it entirely and follow the instructions file — that is where all the content lives. The review wrapper only adds two things: run the judgement pass in a fresh context, and report without touching GitHub. In Codex, `/review` is the built-in user-facing review command. Whenever `/review` runs, read and follow [`.github/instructions/code-review.instructions.md`](.github/instructions/code-review.instructions.md) in full as the review standard. `self-review` is the local skill name, not a Codex command; do not tell users to invoke `self-review`. There is deliberately no per-tool copy of the *standard*. A wrapper is a few lines that defer to it; duplicating the standard itself is the thing to avoid — if you find yourself doing that, fix the pointer, not the number of copies. +**Releasing** — [`RELEASING.md`](RELEASING.md) is the procedure, wrapped the same way as the `release` skill in [`.claude/skills/release/`](.claude/skills/release/) (`/release X.Y.Z` in Claude Code) and [`.agents/skills/release/`](.agents/skills/release/). The wrapper adds the stops before each public step and leaves the merge to the person. + ## What this is An Electron desktop app ("WordPress Contributor Toolkit") that sets up a full WordPress core (`wordpress-develop`) dev environment with zero prerequisites — no Git, Node, npm, or Docker required on the host. Everything is bundled and run as JS/WASM inside the Electron process. Built to fix a Contributor Day problem: newcomers burning the whole session on local setup instead of contributing. Still labeled "experimental." diff --git a/RELEASING.md b/RELEASING.md index 5a10b611..a28df363 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -19,42 +19,70 @@ Versions are `X.Y.Z` for a stable release and `X.Y.Z-beta.N` for a beta. Tags ad 4. Run `npm run i18n:download` and commit `src/languages/`. It writes a catalog for every locale at least 80% translated on translate.wordpress.org, removes every other catalog in `src/languages/` (an untracked one included), and prints a coverage table. Right-to-left locales are held back even at 80%, and the output lists them after the table under `Not shipped:`. When no locale reaches 80% and there is no catalog to remove, it prints `No locales downloaded.` instead of a table. If it exits with an error, nothing in `src/languages/` changed; run it again. Skipping this step ships the previous release's translations, or none, and nothing fails. See [Translatable strings](CONTRIBUTING.md#translatable-strings) in `CONTRIBUTING.md`. 5. Run `npm run lint` and `npm test`. -6. Open the pull request, titled `Bump version to X.Y.Z`, and paste the coverage table into its description, or the script's message if it printed no table. [#533](https://github.com/WordPress/contributor-toolkit/pull/533) is a good model. `git diff trunk` must show exactly three changed lines outside `src/languages/`. +6. Open the pull request, titled `Bump version to X.Y.Z`, and paste the coverage table into its description, or the script's message if it printed no table. [#533](https://github.com/WordPress/contributor-toolkit/pull/533) is a good model. Outside `src/languages/`, the diff must be the three version lines and nothing else: + + ```bash + git diff trunk -- . ':!src/languages' + ``` ## 2. Check the signed artifacts -Buildkite builds signed Windows, macOS and Linux artifacts for the pull request's branch. Before merging, open the build for the pull request's **current head commit** and check: +Buildkite builds signed Windows, macOS and Linux artifacts for the pull request's branch. Before merging, open the build for the pull request's **current head commit** (`gh pr view --json headRefOid --jq .headRefOid`), download the three files into one directory, and check: -- The files are named `wordpress-contributor-toolkit-X.Y.Z-win-x64.exe`, `wordpress-contributor-toolkit-X.Y.Z-mac-arm64.dmg` and `wordpress-contributor-toolkit-X.Y.Z-linux-x86_64.AppImage`. +- The files are named `wordpress-contributor-toolkit-X.Y.Z-win-x64.exe`, `wordpress-contributor-toolkit-X.Y.Z-mac-arm64.dmg` and `wordpress-contributor-toolkit-X.Y.Z-linux-x86_64.AppImage`. The Windows file may download as `dist_wordpress-contributor-toolkit-X.Y.Z-win-x64.exe`, because the Windows build uploads it from `dist\`. Rename it to remove the `dist_` prefix, so its name matches the other two. - The app's `app.asar` root holds exactly `node_modules`, `package.json` and `src`, with npm 11 under `node_modules/npm` and no `.codesigning` directory. - On macOS, `CFBundleShortVersionString` in the app's `Info.plist` reads `X.Y.Z`. +The last two checks run on the macOS artifact, on a Mac, from the repository root (so `@electron/asar` resolves from `node_modules`). Replace `` with the directory holding the downloads: + +```bash +mnt="$(mktemp -d)" +hdiutil attach -nobrowse -readonly -mountpoint "$mnt" "/wordpress-contributor-toolkit-X.Y.Z-mac-arm64.dmg" +app="$mnt/WordPress Contributor Toolkit.app" +npx asar list "$app/Contents/Resources/app.asar" | grep -E '^/[^/]+$' +node -p "JSON.parse(require('@electron/asar').extractFile(process.argv[1], 'node_modules/npm/package.json')).version" "$app/Contents/Resources/app.asar" +plutil -extract CFBundleShortVersionString raw "$app/Contents/Info.plist" +hdiutil detach "$mnt" +``` + +The first command prints `/node_modules`, `/package.json` and `/src`, and nothing else. The second prints a version starting with `11.`. The third prints `X.Y.Z`. + ## 3. Merge -Update the branch with trunk first if it has fallen behind, and check the artifacts again on the new head. Download the three files from the build you checked last. They are what the release ships. +Update the branch with trunk first if it has fallen behind, wait for the new head's build, and download and check the artifacts again from it. The files from the build you checked last are what the release ships. -The Windows file may download as `dist_wordpress-contributor-toolkit-X.Y.Z-win-x64.exe`, because the Windows build uploads it from `dist\`. Rename it to remove the `dist_` prefix before you attach it to the release, so its name matches the other two and the one in step 2. +Then squash-merge. The merged commit has the same content as that build, and it is the one you tag. Find it with: -Then squash-merge. The merged commit has the same content as that build, and it is the one you tag. +```bash +gh pr view --json mergeCommit --jq .mergeCommit.oid +``` ## 4. Publish the GitHub release -Create the tag and the release together, on the bump commit: +Create the tag and the release together, on the bump commit. Every file is named by its path, so the command can run from anywhere: ```bash gh release create vX.Y.Z \ --target \ --title vX.Y.Z \ - --notes-file notes.md \ - wordpress-contributor-toolkit-X.Y.Z-win-x64.exe \ - wordpress-contributor-toolkit-X.Y.Z-mac-arm64.dmg \ - wordpress-contributor-toolkit-X.Y.Z-linux-x86_64.AppImage + --notes-file /notes.md \ + /wordpress-contributor-toolkit-X.Y.Z-win-x64.exe \ + /wordpress-contributor-toolkit-X.Y.Z-mac-arm64.dmg \ + /wordpress-contributor-toolkit-X.Y.Z-linux-x86_64.AppImage ``` - **A beta** adds `--prerelease`, so `releases/latest` stays on the last stable release. -- **A stable release** takes `releases/latest`. +- **A stable release** adds `--latest`, so it takes `releases/latest`. -The notes open with what changed for a contributor, grouped by theme, with the pull request number on each line, and end with a **Downloads** section naming the file for each platform. The [v1.2.0 notes](https://github.com/WordPress/contributor-toolkit/releases/tag/v1.2.0) are a good model. +The notes open with what changed for a contributor, grouped by theme, with the pull request number on each line, and end with a **Downloads** section naming the file for each platform. The [v1.2.0 notes](https://github.com/WordPress/contributor-toolkit/releases/tag/v1.2.0) are a good model for a stable release, and the previous beta's notes for a beta. + +They cover everything since the previous stable release for a stable release, the betas' changes included, and since the previous tag of any kind for a beta. Fetch first, then find the base and the commit count, which includes the bump commit: + +```bash +git fetch origin trunk --tags +git describe --tags --abbrev=0 --exclude '*-beta*' ^ # stable; drop --exclude for a beta +git rev-list --count .. +``` ## 5. Deploy the docs @@ -64,8 +92,23 @@ The docs site tracks the latest release, not trunk, so publish it for the new ta gh workflow run docs.yml --ref trunk -f ref=vX.Y.Z ``` -The dispatch has to be on trunk; the `ref` input is the tag. See [the documentation site](CONTRIBUTING.md#the-documentation-site) in `CONTRIBUTING.md`. +The dispatch has to be on trunk; the `ref` input is the tag. A dispatch on any other branch finishes green without deploying, so check that the run's **deploy to Pages** job ran: + +```bash +gh run list --workflow docs.yml --event workflow_dispatch -L 1 +gh run watch +``` + +See [the documentation site](CONTRIBUTING.md#the-documentation-site) in `CONTRIBUTING.md`. ## 6. Close the milestone If the release has a milestone, close it and move anything still open in it to the next one. A beta often has none: leave the stable release's milestone open until the stable release ships. + +## 7. Check the result + +- `gh release view vX.Y.Z` lists the three files under the names from step 2, and shows the release as a pre-release only for a beta. +- `gh api repos/WordPress/contributor-toolkit/releases/latest --jq .tag_name` prints `vX.Y.Z` for a stable release, and the previous stable tag for a beta. +- `git ls-remote origin refs/tags/vX.Y.Z` points at the bump commit. +- The docs run from step 5 finished with its deploy job. +- The milestone, if the release has one, is closed. From fb32bc62f4c055e4de2fe013c9c1d56e96af8804 Mon Sep 17 00:00:00 2001 From: Ryan Welcher Date: Fri, 23 Oct 2026 10:13:42 -0400 Subject: [PATCH 3/9] Put the start checks, milestone and docs-run commands in RELEASING.md, and order the notes before the release --- .agents/skills/release/SKILL.md | 10 +++--- .claude/skills/release/SKILL.md | 10 +++--- AGENTS.md | 4 +-- RELEASING.md | 56 ++++++++++++++++++++++----------- 4 files changed, 50 insertions(+), 30 deletions(-) diff --git a/.agents/skills/release/SKILL.md b/.agents/skills/release/SKILL.md index 92cbb947..2d0f7e32 100644 --- a/.agents/skills/release/SKILL.md +++ b/.agents/skills/release/SKILL.md @@ -15,11 +15,11 @@ The skill takes the version as its argument (`2.0.0`, `2.1.0-beta.1`). If none i What this skill adds on top: -**Check the starting state first.** Run `git fetch origin trunk --tags`, then stop and report if `git rev-parse trunk` differs from `git rev-parse origin/trunk`, if `git status --porcelain --untracked-files=no` prints anything, or if `git ls-remote --tags origin refs/tags/vX.Y.Z` prints anything. Untracked files do not block a release. If a milestone named `vX.Y.Z` exists, report any issues still open in it, without stopping. +**Check the starting state first.** Run the checks in section 1, item 1 of `RELEASING.md`, and stop and report if any fails. If a milestone named for the full version exists, report any issues still open in it, without stopping. **Wait for the person's yes before each of these:** -1. Creating the release branch. Name it `release/X.Y.Z` and confirm the name. +1. Creating the release branch, after confirming its name. 2. Pushing the branch and opening the bump pull request. 3. Pushing to the branch again, when it has to be updated with trunk before the merge. Then repeat the artifact checks on the new head's build. 4. `gh release create`, after showing the final notes and the exact command. @@ -29,11 +29,11 @@ The merge is the person's, never this skill's. Wait to be told it is merged. **The bump pull request is an ordinary pull request.** Fill in `.github/pull_request_template.md` and run the review AGENTS.md requires before opening it. -**The person downloads the artifacts; this skill checks them.** Ask the person to download the three files from the build for the pull request's current head into a directory they name, then run the checks in `RELEASING.md` step 2 on them and report each result. +**The person downloads the artifacts; this skill checks them.** Ask the person to download the three files from the build for the pull request's current head into a directory they name, then run the checks in section 2 of `RELEASING.md`, *Check the signed artifacts*, and report each result. -**Draft the release notes once the pull request is merged.** Write them plainly, save them to the scratchpad or a temporary directory, not the repository, and show them in the chat for editing before step 4. +**Draft the release notes once the pull request is merged.** Write them plainly, save them to a temporary directory outside the repository, and show them in the chat for editing before `gh release create` (item 4 above). -**Run the checks in `RELEASING.md` step 7 before reporting done**, and report each one. +**Run the checks in section 7 of `RELEASING.md`, *Check the result*, before reporting done**, and report each one. ## Notes diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index b952ad8e..090a041f 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -15,11 +15,11 @@ The skill takes the version as its argument (`2.0.0`, `2.1.0-beta.1`). If none i What this skill adds on top: -**Check the starting state first.** Run `git fetch origin trunk --tags`, then stop and report if `git rev-parse trunk` differs from `git rev-parse origin/trunk`, if `git status --porcelain --untracked-files=no` prints anything, or if `git ls-remote --tags origin refs/tags/vX.Y.Z` prints anything. Untracked files do not block a release. If a milestone named `vX.Y.Z` exists, report any issues still open in it, without stopping. +**Check the starting state first.** Run the checks in section 1, item 1 of `RELEASING.md`, and stop and report if any fails. If a milestone named for the full version exists, report any issues still open in it, without stopping. **Wait for the person's yes before each of these:** -1. Creating the release branch. Name it `release/X.Y.Z` and confirm the name. +1. Creating the release branch, after confirming its name. 2. Pushing the branch and opening the bump pull request. 3. Pushing to the branch again, when it has to be updated with trunk before the merge. Then repeat the artifact checks on the new head's build. 4. `gh release create`, after showing the final notes and the exact command. @@ -29,11 +29,11 @@ The merge is the person's, never this skill's. Wait to be told it is merged. **The bump pull request is an ordinary pull request.** Fill in `.github/pull_request_template.md` and run the review AGENTS.md requires before opening it. -**The person downloads the artifacts; this skill checks them.** Ask the person to download the three files from the build for the pull request's current head into a directory they name, then run the checks in `RELEASING.md` step 2 on them and report each result. +**The person downloads the artifacts; this skill checks them.** Ask the person to download the three files from the build for the pull request's current head into a directory they name, then run the checks in section 2 of `RELEASING.md`, *Check the signed artifacts*, and report each result. -**Draft the release notes once the pull request is merged.** Write them plainly, save them to the scratchpad or a temporary directory, not the repository, and show them in the chat for editing before step 4. +**Draft the release notes once the pull request is merged.** Write them plainly, save them to a temporary directory outside the repository, and show them in the chat for editing before `gh release create` (item 4 above). -**Run the checks in `RELEASING.md` step 7 before reporting done**, and report each one. +**Run the checks in section 7 of `RELEASING.md`, *Check the result*, before reporting done**, and report each one. ## Notes diff --git a/AGENTS.md b/AGENTS.md index 2130a033..34506100 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ Instructions for AI coding agents working in this repository. Tool-neutral and c ## Where things are, whatever agent you use -The review standard and its skill, and neither is specific to one tool despite what their paths suggest: +Three things, none of them specific to one tool despite what their paths suggest: **The review standard** — [`.github/instructions/code-review.instructions.md`](.github/instructions/code-review.instructions.md). The five dimensions, this project's invariants, the procedure to run them, and the reporting format. Read it directly if your agent has not already. @@ -21,7 +21,7 @@ In Codex, `/review` is the built-in user-facing review command. Whenever `/revie There is deliberately no per-tool copy of the *standard*. A wrapper is a few lines that defer to it; duplicating the standard itself is the thing to avoid — if you find yourself doing that, fix the pointer, not the number of copies. -**Releasing** — [`RELEASING.md`](RELEASING.md) is the procedure, wrapped the same way as the `release` skill in [`.claude/skills/release/`](.claude/skills/release/) (`/release X.Y.Z` in Claude Code) and [`.agents/skills/release/`](.agents/skills/release/). The wrapper adds the stops before each public step and leaves the merge to the person. +**Releasing** — [`RELEASING.md`](RELEASING.md) is the procedure. The `release` skill wraps it the same way, in [`.claude/skills/release/`](.claude/skills/release/) (`/release X.Y.Z` in Claude Code) and [`.agents/skills/release/`](.agents/skills/release/). The wrapper adds the stops before each public step and leaves the merge to the person. ## What this is diff --git a/RELEASING.md b/RELEASING.md index a28df363..b5ae3d41 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -2,13 +2,20 @@ How to cut a release of the WordPress Contributor Toolkit. Run every command from the repository root unless a step says otherwise. -Versions are `X.Y.Z` for a stable release and `X.Y.Z-beta.N` for a beta. Tags add a `v`: `v1.2.0`, `v1.2.0-beta.2`. +Versions are `X.Y.Z` for a stable release and `X.Y.Z-beta.N` for a beta. Tags add a `v`: `v1.2.0`, `v1.2.0-beta.2`. In the commands below, `X.Y.Z` stands for the full version, `-beta.N` included. ## 1. Open the version-bump pull request `electron-builder` names the artifacts after the version in `package.json`, so the version moves before anything is built. -1. Branch from an up-to-date trunk. +1. Branch `release/X.Y.Z` from an up-to-date trunk. Check first that trunk matches `origin`, that nothing tracked has changed (untracked files are fine), and that the tag does not exist yet. The two `rev-parse` lines print the same commit, and the last two commands print nothing: + + ```bash + git fetch origin trunk --tags + git rev-parse trunk origin/trunk + git status --porcelain --untracked-files=no + git ls-remote --tags origin refs/tags/vX.Y.Z + ``` 2. Change the version in three places: `"version"` in `package.json`, and the two root `"version"` fields at the top of `package-lock.json` (the top-level one and `packages[""]`). No dependency version changes. 3. Check the two files agree: @@ -42,7 +49,7 @@ app="$mnt/WordPress Contributor Toolkit.app" npx asar list "$app/Contents/Resources/app.asar" | grep -E '^/[^/]+$' node -p "JSON.parse(require('@electron/asar').extractFile(process.argv[1], 'node_modules/npm/package.json')).version" "$app/Contents/Resources/app.asar" plutil -extract CFBundleShortVersionString raw "$app/Contents/Info.plist" -hdiutil detach "$mnt" +hdiutil detach "$mnt" && rmdir "$mnt" ``` The first command prints `/node_modules`, `/package.json` and `/src`, and nothing else. The second prints a version starting with `11.`. The third prints `X.Y.Z`. @@ -57,9 +64,21 @@ Then squash-merge. The merged commit has the same content as that build, and it gh pr view --json mergeCommit --jq .mergeCommit.oid ``` +Check that it matches the build: after `git fetch origin trunk`, `git diff --stat ` prints nothing. If it prints anything, trunk moved before the merge, and the artifacts are not the merged code. + ## 4. Publish the GitHub release -Create the tag and the release together, on the bump commit. Every file is named by its path, so the command can run from anywhere: +Write the notes first. They open with what changed for a contributor, grouped by theme, with the pull request number on each line, and end with a **Downloads** section naming the file for each platform. The [v1.2.0 notes](https://github.com/WordPress/contributor-toolkit/releases/tag/v1.2.0) are a good model for a stable release, and the previous beta's notes for a beta. + +They cover everything since the previous stable release for a stable release, the betas' changes included, and since the previous tag of any kind for a beta. Find the base tag, and the commit count since it that the opening paragraph gives (it includes the bump commit): + +```bash +git fetch origin trunk --tags +git describe --tags --abbrev=0 --exclude '*-beta*' ^ # stable; drop --exclude for a beta +git rev-list --count .. +``` + +Then create the tag and the release together, on the bump commit. Every file is named by its path, so the command can run from anywhere: ```bash gh release create vX.Y.Z \ @@ -74,16 +93,6 @@ gh release create vX.Y.Z \ - **A beta** adds `--prerelease`, so `releases/latest` stays on the last stable release. - **A stable release** adds `--latest`, so it takes `releases/latest`. -The notes open with what changed for a contributor, grouped by theme, with the pull request number on each line, and end with a **Downloads** section naming the file for each platform. The [v1.2.0 notes](https://github.com/WordPress/contributor-toolkit/releases/tag/v1.2.0) are a good model for a stable release, and the previous beta's notes for a beta. - -They cover everything since the previous stable release for a stable release, the betas' changes included, and since the previous tag of any kind for a beta. Fetch first, then find the base and the commit count, which includes the bump commit: - -```bash -git fetch origin trunk --tags -git describe --tags --abbrev=0 --exclude '*-beta*' ^ # stable; drop --exclude for a beta -git rev-list --count .. -``` - ## 5. Deploy the docs The docs site tracks the latest release, not trunk, so publish it for the new tag: @@ -92,18 +101,29 @@ The docs site tracks the latest release, not trunk, so publish it for the new ta gh workflow run docs.yml --ref trunk -f ref=vX.Y.Z ``` -The dispatch has to be on trunk; the `ref` input is the tag. A dispatch on any other branch finishes green without deploying, so check that the run's **deploy to Pages** job ran: +The dispatch has to be on trunk; the `ref` input is the tag. A dispatch on any other branch finishes green without deploying, so check that the run's **deploy to Pages** job succeeded. A new run takes a few seconds to appear, so check that the run listed was created after the dispatch, not the previous release's: ```bash -gh run list --workflow docs.yml --event workflow_dispatch -L 1 -gh run watch +gh run list --workflow docs.yml --event workflow_dispatch --branch trunk -L 1 --json databaseId,createdAt +gh run watch --exit-status +gh run view --json jobs --jq '.jobs[] | select(.name=="deploy to Pages") | .conclusion' ``` +The last command prints `success`. + See [the documentation site](CONTRIBUTING.md#the-documentation-site) in `CONTRIBUTING.md`. ## 6. Close the milestone -If the release has a milestone, close it and move anything still open in it to the next one. A beta often has none: leave the stable release's milestone open until the stable release ships. +If the release has a milestone named `vX.Y.Z`, close it and move anything still open in it to the next one. A beta often has none; close the beta's own milestone if it has one, and leave the stable release's milestone open until the stable release ships. + +```bash +gh issue list --milestone vX.Y.Z --state open +gh api 'repos/WordPress/contributor-toolkit/milestones?state=open' --jq '.[] | "\(.number) \(.title)"' +gh api -X PATCH repos/WordPress/contributor-toolkit/milestones/ -f state=closed +``` + +The first lists what is still open, the second finds the milestone's number, and the third closes it. ## 7. Check the result From c5ef5319a3c5f60fcafbc8fb5783beb5a70eb8f7 Mon Sep 17 00:00:00 2001 From: Ryan Welcher Date: Fri, 23 Oct 2026 12:05:23 -0400 Subject: [PATCH 4/9] Add what the 2.0.0 release taught to RELEASING.md and the release skill - Allow the build.electronLanguages lines in the bump diff, which i18n:download rewrites since #707. - Check the signatures in section 2: codesign, spctl and stapler on macOS, and the Authenticode certificate of the Windows installer. - Say that Buildkite reports nothing to the pull request, so the person confirms the build's commit. - Merge with --match-head-commit, and let the skill merge when the person asks. - Check the docs site's Download button from a new tab, since it keeps the release it read for the tab's session. --- .agents/skills/release/SKILL.md | 4 ++-- .claude/skills/release/SKILL.md | 4 ++-- AGENTS.md | 2 +- RELEASING.md | 34 ++++++++++++++++++++++++++++----- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/.agents/skills/release/SKILL.md b/.agents/skills/release/SKILL.md index 2d0f7e32..12085f87 100644 --- a/.agents/skills/release/SKILL.md +++ b/.agents/skills/release/SKILL.md @@ -25,11 +25,11 @@ What this skill adds on top: 4. `gh release create`, after showing the final notes and the exact command. 5. Dispatching the docs workflow, and closing the milestone if the release has one. -The merge is the person's, never this skill's. Wait to be told it is merged. +The merge is the person's. Wait to be told it is merged, unless the person asks this skill to merge: then merge with the command in section 3 of `RELEASING.md`, guarded by the head commit the artifacts were checked on, and run the checks that follow it there. **The bump pull request is an ordinary pull request.** Fill in `.github/pull_request_template.md` and run the review AGENTS.md requires before opening it. -**The person downloads the artifacts; this skill checks them.** Ask the person to download the three files from the build for the pull request's current head into a directory they name, then run the checks in section 2 of `RELEASING.md`, *Check the signed artifacts*, and report each result. +**The person downloads the artifacts; this skill checks them.** This skill cannot see Buildkite: the build reports nothing to the pull request. Ask the person to confirm that the build's commit matches the pull request's current head, and to download the three files from it into a directory they name. Then run the checks in section 2 of `RELEASING.md`, *Check the signed artifacts*, and report each result. Do not report the build as matching the head until the person has said it does. **Draft the release notes once the pull request is merged.** Write them plainly, save them to a temporary directory outside the repository, and show them in the chat for editing before `gh release create` (item 4 above). diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 090a041f..47f575ed 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -25,11 +25,11 @@ What this skill adds on top: 4. `gh release create`, after showing the final notes and the exact command. 5. Dispatching the docs workflow, and closing the milestone if the release has one. -The merge is the person's, never this skill's. Wait to be told it is merged. +The merge is the person's. Wait to be told it is merged, unless the person asks this skill to merge: then merge with the command in section 3 of `RELEASING.md`, guarded by the head commit the artifacts were checked on, and run the checks that follow it there. **The bump pull request is an ordinary pull request.** Fill in `.github/pull_request_template.md` and run the review AGENTS.md requires before opening it. -**The person downloads the artifacts; this skill checks them.** Ask the person to download the three files from the build for the pull request's current head into a directory they name, then run the checks in section 2 of `RELEASING.md`, *Check the signed artifacts*, and report each result. +**The person downloads the artifacts; this skill checks them.** This skill cannot see Buildkite: the build reports nothing to the pull request. Ask the person to confirm that the build's commit matches the pull request's current head, and to download the three files from it into a directory they name. Then run the checks in section 2 of `RELEASING.md`, *Check the signed artifacts*, and report each result. Do not report the build as matching the head until the person has said it does. **Draft the release notes once the pull request is merged.** Write them plainly, save them to a temporary directory outside the repository, and show them in the chat for editing before `gh release create` (item 4 above). diff --git a/AGENTS.md b/AGENTS.md index 34506100..e5dbc851 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,7 +21,7 @@ In Codex, `/review` is the built-in user-facing review command. Whenever `/revie There is deliberately no per-tool copy of the *standard*. A wrapper is a few lines that defer to it; duplicating the standard itself is the thing to avoid — if you find yourself doing that, fix the pointer, not the number of copies. -**Releasing** — [`RELEASING.md`](RELEASING.md) is the procedure. The `release` skill wraps it the same way, in [`.claude/skills/release/`](.claude/skills/release/) (`/release X.Y.Z` in Claude Code) and [`.agents/skills/release/`](.agents/skills/release/). The wrapper adds the stops before each public step and leaves the merge to the person. +**Releasing** — [`RELEASING.md`](RELEASING.md) is the procedure. The `release` skill wraps it the same way, in [`.claude/skills/release/`](.claude/skills/release/) (`/release X.Y.Z` in Claude Code) and [`.agents/skills/release/`](.agents/skills/release/). The wrapper adds the stops before each public step and leaves the merge to the person unless they ask it to merge. ## What this is diff --git a/RELEASING.md b/RELEASING.md index b5ae3d41..c03ea8e6 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -26,39 +26,62 @@ Versions are `X.Y.Z` for a stable release and `X.Y.Z-beta.N` for a beta. Tags ad 4. Run `npm run i18n:download` and commit `src/languages/`. It writes a catalog for every locale at least 80% translated on translate.wordpress.org, removes every other catalog in `src/languages/` (an untracked one included), and prints a coverage table. Right-to-left locales are held back even at 80%, and the output lists them after the table under `Not shipped:`. When no locale reaches 80% and there is no catalog to remove, it prints `No locales downloaded.` instead of a table. If it exits with an error, nothing in `src/languages/` changed; run it again. Skipping this step ships the previous release's translations, or none, and nothing fails. See [Translatable strings](CONTRIBUTING.md#translatable-strings) in `CONTRIBUTING.md`. 5. Run `npm run lint` and `npm test`. -6. Open the pull request, titled `Bump version to X.Y.Z`, and paste the coverage table into its description, or the script's message if it printed no table. [#533](https://github.com/WordPress/contributor-toolkit/pull/533) is a good model. Outside `src/languages/`, the diff must be the three version lines and nothing else: +6. Open the pull request, titled `Bump version to X.Y.Z`, and paste the coverage table into its description, or the script's message if it printed no table. [#533](https://github.com/WordPress/contributor-toolkit/pull/533) is a good model. Outside `src/languages/`, the diff must be the three version lines, plus any change to `build.electronLanguages` in `package.json`, and nothing else: ```bash git diff trunk -- . ':!src/languages' ``` + `npm run i18n:download` rewrites `build.electronLanguages` whenever it adds or removes a catalog, and prints the resulting list as `Chromium locales kept`. Name the locales it added or removed in the pull request description. + ## 2. Check the signed artifacts -Buildkite builds signed Windows, macOS and Linux artifacts for the pull request's branch. Before merging, open the build for the pull request's **current head commit** (`gh pr view --json headRefOid --jq .headRefOid`), download the three files into one directory, and check: +Buildkite builds signed Windows, macOS and Linux artifacts for the pull request's branch. Before merging, open the build for the pull request's **current head commit** (`gh pr view --json headRefOid --jq .headRefOid`), download the three files into one directory, and check them. Buildkite reports nothing back to the pull request, so the commit can only be read on the build's page in Buildkite: compare it with `headRefOid` there, by eye. - The files are named `wordpress-contributor-toolkit-X.Y.Z-win-x64.exe`, `wordpress-contributor-toolkit-X.Y.Z-mac-arm64.dmg` and `wordpress-contributor-toolkit-X.Y.Z-linux-x86_64.AppImage`. The Windows file may download as `dist_wordpress-contributor-toolkit-X.Y.Z-win-x64.exe`, because the Windows build uploads it from `dist\`. Rename it to remove the `dist_` prefix, so its name matches the other two. +- The macOS app is signed with Automattic's Developer ID, notarized, and has its ticket stapled. +- The Windows installer carries an Authenticode signature from Automattic Inc. - The app's `app.asar` root holds exactly `node_modules`, `package.json` and `src`, with npm 11 under `node_modules/npm` and no `.codesigning` directory. - On macOS, `CFBundleShortVersionString` in the app's `Info.plist` reads `X.Y.Z`. -The last two checks run on the macOS artifact, on a Mac, from the repository root (so `@electron/asar` resolves from `node_modules`). Replace `` with the directory holding the downloads: +All but the name and Windows checks run on the macOS artifact, on a Mac, from the repository root (so `@electron/asar` resolves from `node_modules`). Replace `` with the directory holding the downloads: ```bash mnt="$(mktemp -d)" hdiutil attach -nobrowse -readonly -mountpoint "$mnt" "/wordpress-contributor-toolkit-X.Y.Z-mac-arm64.dmg" app="$mnt/WordPress Contributor Toolkit.app" +codesign --verify --deep --strict "$app" && echo signed +spctl -a -vv -t exec "$app" +xcrun stapler validate "$app" npx asar list "$app/Contents/Resources/app.asar" | grep -E '^/[^/]+$' node -p "JSON.parse(require('@electron/asar').extractFile(process.argv[1], 'node_modules/npm/package.json')).version" "$app/Contents/Resources/app.asar" plutil -extract CFBundleShortVersionString raw "$app/Contents/Info.plist" hdiutil detach "$mnt" && rmdir "$mnt" ``` -The first command prints `/node_modules`, `/package.json` and `/src`, and nothing else. The second prints a version starting with `11.`. The third prints `X.Y.Z`. +`codesign` prints `signed`. `spctl` prints `accepted`, `source=Notarized Developer ID` and an `origin` naming `Automattic, Inc.`. `stapler` prints `The validate action worked!`. The `asar list` command prints `/node_modules`, `/package.json` and `/src`, and nothing else. The `node` command prints a version starting with `11.`. `plutil` prints `X.Y.Z`. + +Read the npm version with `extractFile` as above, in memory. `npx asar extract-file` writes the file into the current directory, which from the repository root replaces its own `package.json`. + +The Windows signature can be checked on Windows, with `Get-AuthenticodeSignature \wordpress-contributor-toolkit-X.Y.Z-win-x64.exe` in PowerShell (`Status` is `Valid` and the signer names Automattic Inc.), or on a Mac with `openssl`, which reads the certificates out of the installer's signature: + +```bash +node -e 'const fs=require("fs");const b=fs.readFileSync(process.argv[1]);const o=b.readUInt32LE(0x3c)+24;const d=o+(b.readUInt16LE(o)===0x20b?112:96)+32;const at=b.readUInt32LE(d),n=b.readUInt32LE(d+4);if(!n)throw new Error("not signed");process.stdout.write(b.subarray(at+8,at+n))' "/wordpress-contributor-toolkit-X.Y.Z-win-x64.exe" | openssl pkcs7 -inform DER -print_certs -noout | grep subject +``` + +It prints a `subject` line naming `O=Automattic Inc.`, and fails with `not signed` when the installer has no signature. ## 3. Merge Update the branch with trunk first if it has fallen behind, wait for the new head's build, and download and check the artifacts again from it. The files from the build you checked last are what the release ships. -Then squash-merge. The merged commit has the same content as that build, and it is the one you tag. Find it with: +Then squash-merge, with the head you checked as a guard, so the merge is refused if anything was pushed after the check: + +```bash +gh pr merge --squash --match-head-commit +``` + +The merged commit has the same content as that build, and it is the one you tag. Find it with: ```bash gh pr view --json mergeCommit --jq .mergeCommit.oid @@ -131,4 +154,5 @@ The first lists what is still open, the second finds the milestone's number, and - `gh api repos/WordPress/contributor-toolkit/releases/latest --jq .tag_name` prints `vX.Y.Z` for a stable release, and the previous stable tag for a beta. - `git ls-remote origin refs/tags/vX.Y.Z` points at the bump commit. - The docs run from step 5 finished with its deploy job. +- The Download button on [the docs site](https://wordpress.github.io/contributor-toolkit/) reads `vX.Y.Z` for a stable release, and the previous stable tag for a beta. Open the site in a new tab or a private window: the button keeps the release it read in the tab's session storage (`docs/.vitepress/theme/DownloadButton.vue`), so a tab opened before the release goes on showing the old one, reloads included. - The milestone, if the release has one, is closed. From 43fdd1b25726535cd49693f491fb6d3ac8a10fc3 Mon Sep 17 00:00:00 2001 From: Ryan Welcher Date: Fri, 23 Oct 2026 13:47:33 -0400 Subject: [PATCH 5/9] Guard the release steps against rc versions, a stale branch and beta docs deploys --- .agents/skills/release/SKILL.md | 6 +++--- .claude/skills/release/SKILL.md | 6 +++--- RELEASING.md | 25 +++++++++++++++++-------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/.agents/skills/release/SKILL.md b/.agents/skills/release/SKILL.md index 12085f87..50e03a8e 100644 --- a/.agents/skills/release/SKILL.md +++ b/.agents/skills/release/SKILL.md @@ -11,7 +11,7 @@ Drive a release through `RELEASING.md`, pausing wherever a step is public or nee **Read [`RELEASING.md`](../../../RELEASING.md) and follow it.** It is the specification for this task, not background reading: every step and every command lives there, including the artifact checks, how to pick the base for the release notes, and the checks after publishing. Do not restate it here; run it. Changes to how releases work go there, not into this wrapper. -The skill takes the version as its argument (`2.0.0`, `2.1.0-beta.1`). If none is given, ask. A version with `-beta.N` is a beta; anything else is stable. +The skill takes the version as its argument (`2.0.0`, `2.1.0-beta.1`). If none is given, ask. A version of the form `X.Y.Z` is stable and `X.Y.Z-beta.N` is a beta. Stop and ask about any other form, such as `2.1.0-rc.1`, rather than treating it as stable. What this skill adds on top: @@ -23,9 +23,9 @@ What this skill adds on top: 2. Pushing the branch and opening the bump pull request. 3. Pushing to the branch again, when it has to be updated with trunk before the merge. Then repeat the artifact checks on the new head's build. 4. `gh release create`, after showing the final notes and the exact command. -5. Dispatching the docs workflow, and closing the milestone if the release has one. +5. Dispatching the docs workflow for a stable release (a beta skips it), and closing the milestone if the release has one. -The merge is the person's. Wait to be told it is merged, unless the person asks this skill to merge: then merge with the command in section 3 of `RELEASING.md`, guarded by the head commit the artifacts were checked on, and run the checks that follow it there. +The merge is the person's. Wait to be told it is merged, unless the person asks this skill to merge: then merge with the command in section 3 of `RELEASING.md`, guarded by the head commit the artifacts were checked on, and run the checks that follow it there. If the check after the merge prints anything, stop and report it; do not tag. **The bump pull request is an ordinary pull request.** Fill in `.github/pull_request_template.md` and run the review AGENTS.md requires before opening it. diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 47f575ed..355cd2e6 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -11,7 +11,7 @@ Drive a release through `RELEASING.md`, pausing wherever a step is public or nee **Read `RELEASING.md` and follow it.** It is the specification for this task, not background reading: every step and every command lives there, including the artifact checks, how to pick the base for the release notes, and the checks after publishing. Do not restate it here; run it. Changes to how releases work go there, not into this wrapper. -The skill takes the version as its argument (`2.0.0`, `2.1.0-beta.1`). If none is given, ask. A version with `-beta.N` is a beta; anything else is stable. +The skill takes the version as its argument (`2.0.0`, `2.1.0-beta.1`). If none is given, ask. A version of the form `X.Y.Z` is stable and `X.Y.Z-beta.N` is a beta. Stop and ask about any other form, such as `2.1.0-rc.1`, rather than treating it as stable. What this skill adds on top: @@ -23,9 +23,9 @@ What this skill adds on top: 2. Pushing the branch and opening the bump pull request. 3. Pushing to the branch again, when it has to be updated with trunk before the merge. Then repeat the artifact checks on the new head's build. 4. `gh release create`, after showing the final notes and the exact command. -5. Dispatching the docs workflow, and closing the milestone if the release has one. +5. Dispatching the docs workflow for a stable release (a beta skips it), and closing the milestone if the release has one. -The merge is the person's. Wait to be told it is merged, unless the person asks this skill to merge: then merge with the command in section 3 of `RELEASING.md`, guarded by the head commit the artifacts were checked on, and run the checks that follow it there. +The merge is the person's. Wait to be told it is merged, unless the person asks this skill to merge: then merge with the command in section 3 of `RELEASING.md`, guarded by the head commit the artifacts were checked on, and run the checks that follow it there. If the check after the merge prints anything, stop and report it; do not tag. **The bump pull request is an ordinary pull request.** Fill in `.github/pull_request_template.md` and run the review AGENTS.md requires before opening it. diff --git a/RELEASING.md b/RELEASING.md index c03ea8e6..e4da45ea 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -2,7 +2,7 @@ How to cut a release of the WordPress Contributor Toolkit. Run every command from the repository root unless a step says otherwise. -Versions are `X.Y.Z` for a stable release and `X.Y.Z-beta.N` for a beta. Tags add a `v`: `v1.2.0`, `v1.2.0-beta.2`. In the commands below, `X.Y.Z` stands for the full version, `-beta.N` included. +Versions are `X.Y.Z` for a stable release and `X.Y.Z-beta.N` for a beta. Tags add a `v`: `v1.2.0`, `v1.2.0-beta.2`. Any other suffix, such as `-rc.1`, is not covered here: stop and decide how to release it before using these steps. In the commands below, `X.Y.Z` stands for the full version, `-beta.N` included. ## 1. Open the version-bump pull request @@ -32,11 +32,11 @@ Versions are `X.Y.Z` for a stable release and `X.Y.Z-beta.N` for a beta. Tags ad git diff trunk -- . ':!src/languages' ``` - `npm run i18n:download` rewrites `build.electronLanguages` whenever it adds or removes a catalog, and prints the resulting list as `Chromium locales kept`. Name the locales it added or removed in the pull request description. + `npm run i18n:download` rewrites `build.electronLanguages` whenever the Chromium locales the catalogs need change, and prints the resulting list as `Chromium locales kept`. Name the locales it added or removed in the pull request description. ## 2. Check the signed artifacts -Buildkite builds signed Windows, macOS and Linux artifacts for the pull request's branch. Before merging, open the build for the pull request's **current head commit** (`gh pr view --json headRefOid --jq .headRefOid`), download the three files into one directory, and check them. Buildkite reports nothing back to the pull request, so the commit can only be read on the build's page in Buildkite: compare it with `headRefOid` there, by eye. +Buildkite builds signed Windows, macOS and Linux artifacts for the pull request's branch. Before merging, open the build for the pull request's **current head commit** (`gh pr view --json headRefOid --jq .headRefOid`), download the three files into one directory, and check them. Buildkite reports nothing back to the pull request (`.buildkite/pipeline.yml` asks for a commit status per platform, but none reached the 2.0.0 bump pull request), so the commit can only be read on the build's page in Buildkite: compare it with `headRefOid` there, by eye. - The files are named `wordpress-contributor-toolkit-X.Y.Z-win-x64.exe`, `wordpress-contributor-toolkit-X.Y.Z-mac-arm64.dmg` and `wordpress-contributor-toolkit-X.Y.Z-linux-x86_64.AppImage`. The Windows file may download as `dist_wordpress-contributor-toolkit-X.Y.Z-win-x64.exe`, because the Windows build uploads it from `dist\`. Rename it to remove the `dist_` prefix, so its name matches the other two. - The macOS app is signed with Automattic's Developer ID, notarized, and has its ticket stapled. @@ -73,7 +73,14 @@ It prints a `subject` line naming `O=Automattic Inc.`, and fails with `not signe ## 3. Merge -Update the branch with trunk first if it has fallen behind, wait for the new head's build, and download and check the artifacts again from it. The files from the build you checked last are what the release ships. +Check that the head you checked already contains trunk. The second command prints `up to date`; if it prints nothing, update the branch with trunk, wait for the new head's build, and download and check the artifacts again from it: + +```bash +git fetch origin trunk "pull//head" +git merge-base --is-ancestor origin/trunk && echo up to date +``` + +The files from the build you checked last are what the release ships. Then squash-merge, with the head you checked as a guard, so the merge is refused if anything was pushed after the check: @@ -87,7 +94,7 @@ The merged commit has the same content as that build, and it is the one you tag. gh pr view --json mergeCommit --jq .mergeCommit.oid ``` -Check that it matches the build: after `git fetch origin trunk`, `git diff --stat ` prints nothing. If it prints anything, trunk moved before the merge, and the artifacts are not the merged code. +Check that it matches the build: after `git fetch origin trunk`, `git diff --stat ` prints nothing. If it prints anything, trunk moved before the merge, and the artifacts are not the merged code. Stop there and do not tag: the release needs artifacts built from the merged commit. ## 4. Publish the GitHub release @@ -97,7 +104,7 @@ They cover everything since the previous stable release for a stable release, th ```bash git fetch origin trunk --tags -git describe --tags --abbrev=0 --exclude '*-beta*' ^ # stable; drop --exclude for a beta +git describe --tags --abbrev=0 --exclude '*-*' ^ # stable; drop --exclude for a beta git rev-list --count .. ``` @@ -118,7 +125,9 @@ gh release create vX.Y.Z \ ## 5. Deploy the docs -The docs site tracks the latest release, not trunk, so publish it for the new tag: +Skip this step for a beta. The docs site tracks the latest release, not trunk, and a beta does not take `releases/latest`, so its docs would describe UI the Download button's build does not have. + +For a stable release, publish the docs for the new tag: ```bash gh workflow run docs.yml --ref trunk -f ref=vX.Y.Z @@ -153,6 +162,6 @@ The first lists what is still open, the second finds the milestone's number, and - `gh release view vX.Y.Z` lists the three files under the names from step 2, and shows the release as a pre-release only for a beta. - `gh api repos/WordPress/contributor-toolkit/releases/latest --jq .tag_name` prints `vX.Y.Z` for a stable release, and the previous stable tag for a beta. - `git ls-remote origin refs/tags/vX.Y.Z` points at the bump commit. -- The docs run from step 5 finished with its deploy job. +- For a stable release, the docs run from step 5 finished with its deploy job. - The Download button on [the docs site](https://wordpress.github.io/contributor-toolkit/) reads `vX.Y.Z` for a stable release, and the previous stable tag for a beta. Open the site in a new tab or a private window: the button keeps the release it read in the tab's session storage (`docs/.vitepress/theme/DownloadButton.vue`), so a tab opened before the release goes on showing the old one, reloads included. - The milestone, if the release has one, is closed. From 72c3f6946ccf6122695fbcaa24dbdf16d61c42ce Mon Sep 17 00:00:00 2001 From: Ryan Welcher Date: Fri, 23 Oct 2026 16:32:43 -0400 Subject: [PATCH 6/9] Run the post-merge check however the bump was merged, say how to recover from a mismatch, and list open pull requests in the milestone --- .agents/skills/release/SKILL.md | 4 ++-- .claude/skills/release/SKILL.md | 4 ++-- RELEASING.md | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.agents/skills/release/SKILL.md b/.agents/skills/release/SKILL.md index 50e03a8e..92057772 100644 --- a/.agents/skills/release/SKILL.md +++ b/.agents/skills/release/SKILL.md @@ -15,7 +15,7 @@ The skill takes the version as its argument (`2.0.0`, `2.1.0-beta.1`). If none i What this skill adds on top: -**Check the starting state first.** Run the checks in section 1, item 1 of `RELEASING.md`, and stop and report if any fails. If a milestone named for the full version exists, report any issues still open in it, without stopping. +**Check the starting state first.** Run the checks in section 1, item 1 of `RELEASING.md`, and stop and report if any fails. If a milestone named for the full version exists, report any issues and pull requests still open in it, without stopping. **Wait for the person's yes before each of these:** @@ -25,7 +25,7 @@ What this skill adds on top: 4. `gh release create`, after showing the final notes and the exact command. 5. Dispatching the docs workflow for a stable release (a beta skips it), and closing the milestone if the release has one. -The merge is the person's. Wait to be told it is merged, unless the person asks this skill to merge: then merge with the command in section 3 of `RELEASING.md`, guarded by the head commit the artifacts were checked on, and run the checks that follow it there. If the check after the merge prints anything, stop and report it; do not tag. +The merge is the person's. Wait to be told it is merged, unless the person asks this skill to merge: then merge with the command in section 3 of `RELEASING.md`, guarded by the head commit the artifacts were checked on. However it was merged, run the checks that follow the merge in section 3 before anything else. If the check after the merge prints anything, stop and report it; do not tag until the artifacts have been checked again as that section says. **The bump pull request is an ordinary pull request.** Fill in `.github/pull_request_template.md` and run the review AGENTS.md requires before opening it. diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 355cd2e6..a547e5b4 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -15,7 +15,7 @@ The skill takes the version as its argument (`2.0.0`, `2.1.0-beta.1`). If none i What this skill adds on top: -**Check the starting state first.** Run the checks in section 1, item 1 of `RELEASING.md`, and stop and report if any fails. If a milestone named for the full version exists, report any issues still open in it, without stopping. +**Check the starting state first.** Run the checks in section 1, item 1 of `RELEASING.md`, and stop and report if any fails. If a milestone named for the full version exists, report any issues and pull requests still open in it, without stopping. **Wait for the person's yes before each of these:** @@ -25,7 +25,7 @@ What this skill adds on top: 4. `gh release create`, after showing the final notes and the exact command. 5. Dispatching the docs workflow for a stable release (a beta skips it), and closing the milestone if the release has one. -The merge is the person's. Wait to be told it is merged, unless the person asks this skill to merge: then merge with the command in section 3 of `RELEASING.md`, guarded by the head commit the artifacts were checked on, and run the checks that follow it there. If the check after the merge prints anything, stop and report it; do not tag. +The merge is the person's. Wait to be told it is merged, unless the person asks this skill to merge: then merge with the command in section 3 of `RELEASING.md`, guarded by the head commit the artifacts were checked on. However it was merged, run the checks that follow the merge in section 3 before anything else. If the check after the merge prints anything, stop and report it; do not tag until the artifacts have been checked again as that section says. **The bump pull request is an ordinary pull request.** Fill in `.github/pull_request_template.md` and run the review AGENTS.md requires before opening it. diff --git a/RELEASING.md b/RELEASING.md index e4da45ea..cf684e86 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -94,7 +94,7 @@ The merged commit has the same content as that build, and it is the one you tag. gh pr view --json mergeCommit --jq .mergeCommit.oid ``` -Check that it matches the build: after `git fetch origin trunk`, `git diff --stat ` prints nothing. If it prints anything, trunk moved before the merge, and the artifacts are not the merged code. Stop there and do not tag: the release needs artifacts built from the merged commit. +Check that it matches the build: after `git fetch origin trunk`, `git diff --stat ` prints nothing. If it prints anything, trunk moved before the merge, and the artifacts are not the merged code. Do not tag yet: the release needs artifacts built from the merged commit. Buildkite builds every push to trunk, so open its trunk build for the merged commit, download its three files and repeat section 2 on them, comparing the build's commit with the merged commit instead of `headRefOid`. Tag only once they pass. ## 4. Publish the GitHub release @@ -151,11 +151,12 @@ If the release has a milestone named `vX.Y.Z`, close it and move anything still ```bash gh issue list --milestone vX.Y.Z --state open +gh pr list --search 'milestone:vX.Y.Z' --state open gh api 'repos/WordPress/contributor-toolkit/milestones?state=open' --jq '.[] | "\(.number) \(.title)"' gh api -X PATCH repos/WordPress/contributor-toolkit/milestones/ -f state=closed ``` -The first lists what is still open, the second finds the milestone's number, and the third closes it. +The first two list the issues and pull requests still open, the third finds the milestone's number, and the fourth closes it. ## 7. Check the result From 140a866bd5c16f5db23e9528234c9aceaadb1f43 Mon Sep 17 00:00:00 2001 From: Ryan Welcher Date: Fri, 23 Oct 2026 16:40:18 -0400 Subject: [PATCH 7/9] Say what major, minor and patch mean for this app in RELEASING.md --- RELEASING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASING.md b/RELEASING.md index cf684e86..d34d519c 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -4,6 +4,8 @@ How to cut a release of the WordPress Contributor Toolkit. Run every command fro Versions are `X.Y.Z` for a stable release and `X.Y.Z-beta.N` for a beta. Tags add a `v`: `v1.2.0`, `v1.2.0-beta.2`. Any other suffix, such as `-rc.1`, is not covered here: stop and decide how to release it before using these steps. In the commands below, `X.Y.Z` stands for the full version, `-beta.N` included. +Versions follow [Semantic Versioning](https://semver.org/). The app has no public API, so the three numbers are about what a person who already uses it notices. A **major** release changes something they have to deal with: a site or setting from an earlier version that no longer works the way it did, a flow they have to learn again, or a platform that is no longer supported. A **minor** release adds something without any of that, such as a new feature or a new kind of site. A **patch** release only fixes what an earlier release shipped. + ## 1. Open the version-bump pull request `electron-builder` names the artifacts after the version in `package.json`, so the version moves before anything is built. From ef49b34447ec7cc659fc362cddd2aaf33c2fc7fb Mon Sep 17 00:00:00 2001 From: Ryan Welcher Date: Mon, 26 Oct 2026 09:29:59 -0400 Subject: [PATCH 8/9] Make the Windows check the signature verification, name the repository in gh release create, and quote its paths --- RELEASING.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index d34d519c..af3700cf 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -65,13 +65,13 @@ hdiutil detach "$mnt" && rmdir "$mnt" Read the npm version with `extractFile` as above, in memory. `npx asar extract-file` writes the file into the current directory, which from the repository root replaces its own `package.json`. -The Windows signature can be checked on Windows, with `Get-AuthenticodeSignature \wordpress-contributor-toolkit-X.Y.Z-win-x64.exe` in PowerShell (`Status` is `Valid` and the signer names Automattic Inc.), or on a Mac with `openssl`, which reads the certificates out of the installer's signature: +Verify the Windows signature on Windows, in PowerShell: `Get-AuthenticodeSignature "\wordpress-contributor-toolkit-X.Y.Z-win-x64.exe"` prints `Status` `Valid` and a signer naming Automattic Inc. This is the check that counts, because it confirms the signature matches the file. On a Mac, `openssl` can show who signed the installer before that check, but it does not verify the signature: ```bash node -e 'const fs=require("fs");const b=fs.readFileSync(process.argv[1]);const o=b.readUInt32LE(0x3c)+24;const d=o+(b.readUInt16LE(o)===0x20b?112:96)+32;const at=b.readUInt32LE(d),n=b.readUInt32LE(d+4);if(!n)throw new Error("not signed");process.stdout.write(b.subarray(at+8,at+n))' "/wordpress-contributor-toolkit-X.Y.Z-win-x64.exe" | openssl pkcs7 -inform DER -print_certs -noout | grep subject ``` -It prints a `subject` line naming `O=Automattic Inc.`, and fails with `not signed` when the installer has no signature. +It prints a `subject` line naming `O=Automattic Inc.`, and fails with `not signed` when the installer has no signature. A tampered installer that kept the certificate prints the same, so this does not replace the Windows check. ## 3. Merge @@ -110,16 +110,17 @@ git describe --tags --abbrev=0 --exclude '*-*' ^ # stable; dr git rev-list --count .. ``` -Then create the tag and the release together, on the bump commit. Every file is named by its path, so the command can run from anywhere: +Then create the tag and the release together, on the bump commit. Every file is named by its path and `--repo` names the repository, so the command can run from anywhere: ```bash gh release create vX.Y.Z \ + --repo WordPress/contributor-toolkit \ --target \ --title vX.Y.Z \ - --notes-file /notes.md \ - /wordpress-contributor-toolkit-X.Y.Z-win-x64.exe \ - /wordpress-contributor-toolkit-X.Y.Z-mac-arm64.dmg \ - /wordpress-contributor-toolkit-X.Y.Z-linux-x86_64.AppImage + --notes-file "/notes.md" \ + "/wordpress-contributor-toolkit-X.Y.Z-win-x64.exe" \ + "/wordpress-contributor-toolkit-X.Y.Z-mac-arm64.dmg" \ + "/wordpress-contributor-toolkit-X.Y.Z-linux-x86_64.AppImage" ``` - **A beta** adds `--prerelease`, so `releases/latest` stays on the last stable release. From 89aeadc030a46e808f344e3f2b481595691e6ca1 Mon Sep 17 00:00:00 2001 From: Ryan Welcher Date: Mon, 26 Oct 2026 09:31:49 -0400 Subject: [PATCH 9/9] Remove the Semantic Versioning paragraph from RELEASING.md --- RELEASING.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index af3700cf..17c21e8f 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -4,8 +4,6 @@ How to cut a release of the WordPress Contributor Toolkit. Run every command fro Versions are `X.Y.Z` for a stable release and `X.Y.Z-beta.N` for a beta. Tags add a `v`: `v1.2.0`, `v1.2.0-beta.2`. Any other suffix, such as `-rc.1`, is not covered here: stop and decide how to release it before using these steps. In the commands below, `X.Y.Z` stands for the full version, `-beta.N` included. -Versions follow [Semantic Versioning](https://semver.org/). The app has no public API, so the three numbers are about what a person who already uses it notices. A **major** release changes something they have to deal with: a site or setting from an earlier version that no longer works the way it did, a flow they have to learn again, or a platform that is no longer supported. A **minor** release adds something without any of that, such as a new feature or a new kind of site. A **patch** release only fixes what an earlier release shipped. - ## 1. Open the version-bump pull request `electron-builder` names the artifacts after the version in `package.json`, so the version moves before anything is built.