Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 5 additions & 2 deletions .github/workflows/claude-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,11 @@ jobs:
Two more verdicts, both about the whole pull request (not only the new commits):
`type`, the kind of change the diff really is, by the Conventional Commits meaning
(breaking when a user or a caller must change something to keep working; feat when
behaviour is added; fix when wrong behaviour is corrected, whatever the title says;
docs, test, ci, build, chore, style, refactor, perf, revert when that is all it is); and
behaviour is added, however large, since size is not breakage; fix when wrong behaviour
is corrected, whatever the title says; docs, test, ci, build, chore, style, refactor,
perf, revert when that is all it is). When a feat looks like a new capability (a new
provider, a flow the product did not have), say so in one line of the summary as a
suggestion of the version:major label; the maintainer decides it, never you. And
`description_matches`, true only if the description's "What changes" and "Why"
describe what the diff does, with nothing claimed that the code does not do and no
behaviour change left unsaid. When they disagree, say in the summary in one line
Expand Down
41 changes: 38 additions & 3 deletions .github/workflows/plugin-checks-wp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ name: WordPress plugin checks (fast)
# Reusable. The fast quality gates for a WordPress plugin published on
# wordpress.org: PHP syntax and unit tests on every supported PHP, PHPCS,
# PHPStan, Psalm taint analysis, i18n, Plugin Check on the shipped tree, and
# readme/version alignment. Each job is its own status check.
# readme/version alignment, and the version markers against what was
# released (scripts/release-markers.sh). Each job is its own status check.
#
# The slow wp-env suites (integration, E2E) are plugin-tests-wp.yml, a
# separate workflow, so a caller can start the review as soon as these pass
Expand Down Expand Up @@ -251,18 +252,28 @@ jobs:
slug: ${{ inputs.slug }}
categories: plugin_repo,security,performance,accessibility,general
include-experimental: true
# main carries a -dev Version between releases; the readme job
# enforces the relaxed rule and the release workflow the strict one.
# The shipped tree here is the checkout, whose markers the readme
# job holds to the released version (or the one being released);
# the release workflow checks them again before SVN.
ignore-codes: stable_tag_mismatch

readme:
name: readme.txt and versions
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
pull-requests: read # the labels of what merged give the version being released
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: DiluxOne/.github
ref: ${{ inputs.central-ref }}
path: .dx-central
persist-credentials: false
- name: Headers, version alignment, changelog
env:
MAIN: ${{ inputs.main-file != '' && inputs.main-file || format('{0}.php', inputs.slug) }}
Expand All @@ -287,4 +298,28 @@ jobs:
fi
grep -qE "^=\s*${stable}\s*=" readme.txt || { echo "::error file=readme.txt::No '= ${stable} =' changelog entry."; exit 1; }
echo "readme.txt OK: Stable tag $stable, Version $version."
- name: The markers name the released version
# The three markers say the last version released, or, in the pull
# request that releases the next one (it removes the `Unreleased.`
# line) and on main after it merged, that next one; never a version
# that is not one (scripts/release-markers.sh).
env:
GH_TOKEN: ${{ github.token }}
MAIN: ${{ inputs.main-file != '' && inputs.main-file || format('{0}.php', inputs.slug) }}
CONSTANT: ${{ inputs.version-constant }}
BASE: ${{ github.event.repository.default_branch }}
PULL: ${{ github.event.pull_request.number }}
# In a pull request its own labels count as if it had merged, so the
# release pull request is checked against the version it will
# release, its own version:* label included. A label alone does not
# re-run the checks: set version:* before the last push. The review's
# type:* arrives after this job ran, which is why the release pull
# request contains nothing but the hold and the markers (a chore,
# no bump of its own); anything that slips through is still refused
# by the release job before SVN.
run: |
set -euo pipefail
next=$(python3 .dx-central/scripts/next-version.py --repo "$GITHUB_REPOSITORY" --base "$BASE" ${PULL:+--with-pull "$PULL"} --json)
bash .dx-central/scripts/release-markers.sh check . "$MAIN" "$CONSTANT" \
"$(jq -r .last <<<"$next")" "$(jq -r .next <<<"$next")" "$(jq -r .pending <<<"$next")"

36 changes: 21 additions & 15 deletions .github/workflows/plugin-release-wp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ name: WordPress plugin release
# `release:` says `off` for that bump: the run ends there, green, and the
# summary says why. Something pending: the `release` job waits for the
# environment's reviewers (the summary shows the version, the bump and the
# pull requests that justify it), then stamps the three version markers to
# X.Y.Z in the checkout, validates them, commits trunk + tags/X.Y.Z +
# assets to SVN, then creates the tag and the GitHub release in one call
# with the release App's token, with the changelog and what was merged,
# grouped by type. Nothing is ever committed to main: the markers there
# stay at the last released version and every development build stamps
# pull requests that justify it), then checks that the three version markers
# already say X.Y.Z (the release pull request, the one that removed the
# `Unreleased.` line, set them: scripts/release-markers.sh prepare), commits
# trunk + tags/X.Y.Z + assets to SVN, then creates the tag and the GitHub
# release in one call with the release App's token, with the changelog and
# what was merged, grouped by type. Nothing is ever committed to main by
# this workflow: the markers there say the last released version, or the
# one being released once its pull request merged (the checks' readme job
# holds every pull request to that), and every development build stamps
# itself. After the approval the job checks again that this is still the
# release to make: the tag does not exist, the labels still give this
# version, the commit is on the default branch. A push made while a run
Expand All @@ -30,8 +33,8 @@ name: WordPress plugin release
# created itself arrives with its release already there, so that run stops.
#
# The readme's newest changelog entry names the version: `= X.Y.Z =` equal to
# the computed one, or `= Unreleased =`, which the job renames in the build. A
# different number is a disagreement between the roadmap and the labels, and
# the computed one (while it is held it may be headed `= Unreleased =`; the
# release pull request renames it with the markers). A different number is a disagreement between the roadmap and the labels, and
# the job refuses until a person settles it with a version:* label or the
# readme. The same entry says whether the version is ready: while its first
# line is `Unreleased.` (scripts/release-ready.sh), the version job ends green with "Not ready"
Expand Down Expand Up @@ -418,15 +421,18 @@ jobs:
[ "$again" = "$VERSION" ] || { echo "::error::The labels now give '${again:-nothing}' where $VERSION was approved; something merged or was relabelled meanwhile. Nothing reached wordpress.org; the next push to $BASE computes again."; exit 1; }
echo "Still $VERSION, on $BASE, no tag yet."

- name: Stamp the version markers
- name: The markers say this version
if: steps.still.outputs.go == 'true'
# The checkout, never the repository: the three markers become the
# version, a readme entry headed `= Unreleased =` takes its number and
# the readme is brought to LF (scripts/stamp-version.sh, which fails
# when a marker did not take it: nothing reaches wordpress.org from a
# file whose shape changed).
# The release pull request set the three markers and the changelog
# heading to this version, so main already says what ships: nothing
# reaches wordpress.org from a commit that names another version
# (scripts/release-markers.sh). The stamp that follows then changes
# nothing but the readme's line endings (LF), and fails when a marker
# does not hold the version: a file whose shape changed.
run: |
set -euo pipefail
last=$(git tag -l '[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | grep -vxF "$VERSION" | head -1 || true)
bash .dx-central/scripts/release-markers.sh check . "$MAIN" "$CONSTANT" "${last:-0.0.0}" "$VERSION" true
bash .dx-central/scripts/stamp-version.sh . "$VERSION" "$MAIN" "$CONSTANT"
git --no-pager diff --stat

Expand All @@ -444,7 +450,7 @@ jobs:
fi
newest=$(grep -m1 -oP '^= \K[0-9]+\.[0-9]+\.[0-9]+(?= =$)' readme.txt || true)
if [ "$newest" != "$VERSION" ]; then
echo "::error file=readme.txt::The newest changelog entry is '= ${newest:-none} =' but the labels of what merged give $VERSION. Either the readme announces a version the labels do not reach (add version:major, version:minor or version:patch to a merged pull request) or the labels went further than the readme (write the entry, or head it '= Unreleased ='). Nothing reached wordpress.org."
echo "::error file=readme.txt::The newest changelog entry is '= ${newest:-none} =' but the labels of what merged give $VERSION. Either the readme announces a version the labels do not reach (add version:major, version:minor or version:patch to a merged pull request) or the labels went further than the readme (head the entry and set the markers to $VERSION in a pull request). Nothing reached wordpress.org."
exit 1
fi
awk -v head="= ${VERSION} =" '$0 == head { found = 1; next } found && /^=/ { exit } found { print }' readme.txt > "$RUNNER_TEMP/notes.md"
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ jobs:
- run: python3 scripts/next-version.py --test
- run: bash scripts/release-ready.sh --test
- run: bash scripts/stamp-version.sh --test
- run: bash scripts/release-markers.sh --test

review:
needs: [conventions, actionlint, scripts]
Expand Down
19 changes: 17 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,26 @@ Pull requests from **forks** are not reviewed automatically: the review runs wit

## How a change becomes a version

Nobody types a version number. The `type:*` label the review sets on each merged pull request decides the next one (`type:breaking` β†’ major, `type:feat` β†’ minor, `type:fix` or `type:perf` β†’ patch; a maintainer's `version:major|minor|patch` label wins), and `main` keeps the last released version in its files between releases. In a repository that publishes (a WordPress plugin):
Every DiluxOne repository numbers its versions `X.Y.Z`, always three numbers, no suffix, by what a change means to the people who use it:

| Part | When | Who decides |
| --- | --- | --- |
| **Major** `X.0.0` | A big new capability (a new provider, a new flow the product did not have), **or** any change that breaks something that worked | The maintainer, with the `version:major` label on the pull request. A breaking change (`type:breaking`, `!` in the title) forces it on its own |
| **Minor** `x.Y.0` | Additions and improvements to what already exists, compatible with it | Automatic: `type:feat` |
| **Patch** `x.y.Z` | Bug fixes, security fixes and performance, with no new behaviour | Automatic: `type:fix`, `type:perf` |

Two rules come with it:

- **A breaking change ships only in a major**, and a minor before it announces it ("this is going away in the next major"). Breaking means a user, a site or a caller has to change something to keep working: a new minimum requirement, a removed option, a setting that changes meaning, a renamed hook, data that cannot go back to the previous version.
- **Every major says in its changelog** `Breaking changes: none`, or lists them with what to do.

"Big" is a person's call: the review suggests `version:major` when a pull request looks like a new capability, and never sets it. A big `feat` is still a `feat`, not a breaking change.

Nobody types a version number. The `type:*` label the review sets on each merged pull request decides the next one (`type:breaking` β†’ major, `type:feat` β†’ minor, `type:fix` or `type:perf` β†’ patch; a maintainer's `version:major|minor|patch` label wins). `main` names a real version in its files: the last one released, or, from the moment the pull request that releases the next one merges, that next one; the checks hold every pull request to it. In a repository that publishes (a WordPress plugin):

- **Every push to `main` publishes a development build**, the shipped tree stamped `<next>-dev.<N>`, as the one *Development build* pre-release in the repository's Releases, replaced each time (no history: the commit in its notes rebuilds any of them). Anyone can download it and try what is coming; it is not a release, and "Latest" stays the last published version.
- **The changelog is written as the changes merge.** A pull request that changes what a user sees adds its bullet to the newest entry of `readme.txt` (`= X.Y.Z =`, first line `Unreleased.`), in the same pull request.
- **The maintainer decides when it is ready** by removing the `Unreleased.` line in a pull request. That push to `main` waits for approval in the repository's `wordpress-org` environment; only its required reviewers can approve, and approving publishes (a repository's policy can set a kind of bump to `auto`, published without waiting, or `off`, never published; the organisation default is to wait). Until then, however many pull requests merge, nothing waits for anyone and nothing is published.
- **The maintainer decides when it is ready** by removing the `Unreleased.` line in a pull request, which also sets the version markers to the version being released (`scripts/release-markers.sh prepare`). That push to `main` waits for approval in the repository's `wordpress-org` environment; only its required reviewers can approve, and approving publishes (a repository's policy can set a kind of bump to `auto`, published without waiting, or `off`, never published; the organisation default is to wait). Until then, however many pull requests merge, nothing waits for anyone and nothing is published.
- **Outside contributors** need nothing more than the pull request: your change ships in the next version with its bullet in the changelog. You cannot approve a release, and you do not need to.

## AI tools
Expand Down
Loading
Loading