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
26 changes: 23 additions & 3 deletions .github/workflows/issue-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ name: Issue triage
#
# bug:unconfirmed a bug report with enough to try to reproduce it
# (issue-repro.yml picks it up from this label)
# needs-info a bug report missing versions, steps or logs: the
# reply asks for them
# needs-info a bug report missing versions, steps or logs, or
# made on a version that is not current (older than
# the released one, or a development build older than
# the current pre-release): the reply asks for them,
# or to update and say whether it is still there
# by-design works as documented, or a known limitation
# pro-feature needs the paid service or add-on the roadmap names
# enhancement a feature request (+ exists-in-pro when the roadmap
Expand Down Expand Up @@ -43,6 +46,10 @@ on:
description: Where usage questions go (a forum, a help page).
type: string
required: true
dev-tag:
description: The moving tag of the repository's "Development build" pre-release (as in plugin-release-wp.yml), so the triage knows the current development version. Empty when the repository publishes none.
type: string
default: dev
roadmap:
description: The roadmap file that says what is free, paid, planned and not planned.
type: string
Expand Down Expand Up @@ -114,6 +121,7 @@ jobs:
AUTHOR: ${{ github.event.issue.user.login }}
ROADMAP: ${{ inputs.roadmap }}
SUPPORT_URL: ${{ inputs.support-url }}
DEV_TAG: ${{ inputs.dev-tag }}
run: |
set -euo pipefail
{
Expand All @@ -129,6 +137,13 @@ jobs:
for f in "$ROADMAP" README.md readme.txt; do
if [ -f "$f" ]; then echo; echo "## Repository file: $f"; echo; head -c 60000 "$f"; fi
done
echo; echo "## Current versions"; echo
released=$(grep -oP '^Stable tag:\s*\K\S+' readme.txt 2>/dev/null || true)
echo "- Released (wordpress.org, the readme's Stable tag): ${released:-unknown}"
if [ -n "$DEV_TAG" ]; then
dev=$(gh release view "$DEV_TAG" --repo "$GITHUB_REPOSITORY" --json name --jq .name 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+-dev\.[0-9]+' || true)
echo "- Development build (GitHub pre-release $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/$DEV_TAG): ${dev:-none published}"
fi
echo; echo "## Where usage questions go"; echo; echo "$SUPPORT_URL"
} > .dx-central/brief.md
echo "Brief: $(wc -c < .dx-central/brief.md) bytes."
Expand All @@ -149,7 +164,12 @@ jobs:

Pick exactly one category:
- bug_unconfirmed: a defect report with steps, versions and what was expected.
- needs_info: a defect report missing what a maintainer needs to try it (say what).
- needs_info: a defect report missing what a maintainer needs to try it (say what),
or one made on a version that is not current: a plugin version older than the
released one, or a development build (X.Y.Z-dev.N) older than the current one (see
"Current versions"). Then ask, kindly, to update to the released version or to
install the current development build (give its link) and to say whether the
problem is still there; many reports come from sites that never updated.
- by_design: works as documented, or a known limitation from the roadmap.
- pro_feature: it needs the paid service or add-on the roadmap names.
- enhancement: a request for something new (exists_in_pro when a paid product has it).
Expand Down
100 changes: 81 additions & 19 deletions .github/workflows/plugin-release-wp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,21 @@ name: WordPress plugin release
# that removes the line is the decision to release. A tag pushed by hand is
# refused the same way.
#
# Every push to main also produces a development build, whatever the labels
# Every push to main also publishes a development build, whatever the labels
# or the readme say: the tree that ships (minus .distignore) with the three
# markers (and a `= Unreleased =` heading) stamped `<next>-dev.<N>` and a `Build: <commit>` header, as the
# run's artifact `<slug>-<next>-dev.<N>`, kept 30 days, for anyone to install
# and try what is coming. Nothing is committed or tagged for it.
# markers (and a `= Unreleased =` heading) stamped `<next>-dev.<N>` and a
# `Build: <commit>` header, as ONE GitHub pre-release named "Development
# build" on the moving tag `dev-tag` (default `dev`), replaced on every push:
# the tag moves to the commit, the release notes say the version, the commit
# and the changelog that is coming, and the asset `<slug>.zip` keeps its URL,
# `…/releases/download/<dev-tag>/<slug>.zip`. There is no history of
# development builds (the commit in the `Build:` header rebuilds any of
# them); releases X.Y.Z are untouched and "Latest" stays the last of them.
# The moving tag must be left free by the repository's tag rulesets (the
# adoption guide's cover X.Y.Z only) and never publishes anything to
# wordpress.org. In a rehearsal (`dry-run: true`) the build is made and its
# notes go to the summary, but no tag moves and no pre-release is published:
# a rehearsal publishes nothing, as documented.
#
# Release tags are permanent (tag protection): a failed deploy is fixed and
# re-run, never re-tagged. `dry-run: true` rehearses everything but the SVN
Expand Down Expand Up @@ -106,12 +116,16 @@ on:
description: The ref of DiluxOne/.github whose scripts and policy to use.
type: string
default: v2
dev-tag:
description: The moving tag of the "Development build" pre-release that every push to main replaces. Empty publishes no development build.
type: string
default: dev
outputs:
version:
description: The version released, or empty when nothing was.
value: ${{ jobs.release.outputs.version }}
dev:
description: The development version of this commit, <next>-dev.<N>; the artifact of the development build is named <slug>-<dev>.
description: The development version of this commit, <next>-dev.<N>, the one the "Development build" pre-release carries.
value: ${{ jobs.version.outputs.dev }}

permissions:
Expand Down Expand Up @@ -233,19 +247,26 @@ jobs:
name: Development build
# Every push to main, whatever the labels or the readme say, and
# whatever happens to the version job: the tree that ships, stamped with
# the version that is coming and the commit it was built from, for
# anyone to install and try. It computes the development version itself
# so that it does not depend on the release's job. Never on a tag: a
# release is its own build. No secrets, no write: nothing is committed
# or tagged for it.
if: startsWith(github.ref, 'refs/heads/')
# the version that is coming and the commit it was built from, published
# as the one "Development build" pre-release for anyone to install and
# try. It computes the development version itself so that it does not
# depend on the release's job. Never on a tag: a release is its own
# build, and only from the default branch: a push there replaces the
# public pre-release. No secrets; contents: write only to move the `dev`
# tag and replace that pre-release, which publishes nothing to
# wordpress.org. In a rehearsal the build is made and nothing is published.
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && inputs.dev-tag != ''
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
contents: write
pull-requests: read # the labels of what merged
concurrency:
group: development-build-${{ github.repository }}
cancel-in-progress: false
env:
SLUG: ${{ inputs.slug }}
DEV_TAG: ${{ inputs.dev-tag }}
MAIN: ${{ inputs.main-file != '' && inputs.main-file || format('{0}.php', inputs.slug) }}
CONSTANT: ${{ inputs.version-constant }}
steps:
Expand Down Expand Up @@ -279,25 +300,66 @@ jobs:
# Plugins and wherever the plugin reads its headers. The zip holds
# the plugin folder, named after the slug, the way WordPress installs
# it.
id: pack
run: |
set -euo pipefail
build=${GITHUB_SHA:0:7}
echo "build=$build" >> "$GITHUB_OUTPUT"
bash .dx-central/scripts/stamp-version.sh . "$DEV" "$MAIN" "$CONSTANT" "$build"
# The central's checkout leaves before the tree is packed: nothing
# that is not the plugin may end up in the build.
rm -rf .dx-central
exclude=(); [ -f .distignore ] && exclude=(--exclude-from=.distignore)
mkdir -p "$RUNNER_TEMP/dist"
rsync -rc "${exclude[@]}" --exclude=.git --exclude=.github ./ "$RUNNER_TEMP/dist/$SLUG/" --delete
(cd "$RUNNER_TEMP/dist" && zip -qr "$RUNNER_TEMP/$SLUG.zip" "$SLUG")
# The notes: what this build is, and the changelog that is coming
# (the newest entry of the readme, without its `Unreleased.` line).
# shellcheck disable=SC2016 # backticks in the Markdown of the notes, not commands.
{
printf 'Development build **%s** of DiluxOne %s, from commit %s. Not a release: it is replaced on every push to `%s`, it is not on wordpress.org, and a site that installs it updates to the release when it is published.\n\n' "$DEV" "$SLUG" "[\`$build\`]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/$GITHUB_SHA)" "${GITHUB_REF#refs/heads/}"
printf '**Install:** download `%s.zip` below, then in WordPress: Plugins β†’ Add New β†’ Upload Plugin. Status β€Ί System shows the version and the build.\n\n' "$SLUG"
notes=$(awk '/^== Changelog ==$/ { c = 1; next } c && /^= .* =$/ { if (h) exit; h = 1; next } h && !/^Unreleased\.?$/ && (p || NF) { p = 1; print }' readme.txt | sed -e :a -e '/^\n*$/{$d;N;ba' -e '}')
if [ -n "$(printf '%s' "$notes" | tr -d '[:space:]')" ]; then printf '**Coming in the next version:**\n%s\n' "$notes"; fi
} > "$RUNNER_TEMP/dev-notes.md"

- name: The notes, in a rehearsal
if: inputs.dry-run
env:
DEV: ${{ steps.dev.outputs.dev }}
run: |
# shellcheck disable=SC2016 # backticks in the Markdown of the summary, not commands.
printf '## Development build %s\n\nThe tree that ships, stamped `%s` from commit `%s`, is the artifact **%s** of this run (download it from the summary of the run, unzip, install the `%s` folder as a plugin). Not a release: nothing was committed or tagged.\n' "$DEV" "$DEV" "$build" "$SLUG-$DEV" "$SLUG" >> "$GITHUB_STEP_SUMMARY"
printf '## Development build %s (rehearsal)\n\nBuilt, not published: `dry-run: true`. The pre-release would say:\n\n' "$DEV" >> "$GITHUB_STEP_SUMMARY"
cat "$RUNNER_TEMP/dev-notes.md" >> "$GITHUB_STEP_SUMMARY"

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ inputs.slug }}-${{ steps.dev.outputs.dev }}
path: ${{ runner.temp }}/dist
retention-days: 30
if-no-files-found: error
- name: Publish the pre-release on the moving tag
if: ${{ !inputs.dry-run }}
env:
GH_TOKEN: ${{ github.token }}
DEV: ${{ steps.dev.outputs.dev }}
BUILD: ${{ steps.pack.outputs.build }}
BASE: ${{ github.event.repository.default_branch }}
# Only when this commit is still the head of the default branch (a
# re-run of an old run must not move the tag back). The zip is
# uploaded before the notes, the title and the tag change, so a
# failed upload leaves the previous build whole; the tag moves last.
run: |
set -euo pipefail
head=$(gh api "repos/$GITHUB_REPOSITORY/commits/$BASE" --jq .sha)
if [ "$head" != "$GITHUB_SHA" ]; then
printf '## Development build %s\n\nNot published: %s is no longer the head of %s (a newer push publishes its own build).\n' "$DEV" "$BUILD" "$BASE" >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
title="Development build $DEV"
if ! gh release view "$DEV_TAG" --repo "$GITHUB_REPOSITORY" --json id --jq .id >/dev/null 2>&1; then
gh release create "$DEV_TAG" --repo "$GITHUB_REPOSITORY" --title "$title" --notes-file "$RUNNER_TEMP/dev-notes.md" --prerelease --latest=false --target "$GITHUB_SHA" >/dev/null
fi
gh release upload "$DEV_TAG" "$RUNNER_TEMP/$SLUG.zip" --repo "$GITHUB_REPOSITORY" --clobber
gh release edit "$DEV_TAG" --repo "$GITHUB_REPOSITORY" --title "$title" --notes-file "$RUNNER_TEMP/dev-notes.md" --prerelease --latest=false --target "$GITHUB_SHA" >/dev/null
gh api -X PATCH "repos/$GITHUB_REPOSITORY/git/refs/tags/$DEV_TAG" -f sha="$GITHUB_SHA" -F force=true --jq .object.sha
url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/$DEV_TAG"
# shellcheck disable=SC2016 # backticks in the Markdown of the summary, not commands.
printf '## Development build %s\n\nPublished as the pre-release [%s](%s), replacing the previous one: tag `%s` now points at `%s`, the asset is `%s.zip`. Not a release: nothing reached wordpress.org.\n' "$DEV" "$title" "$url" "$DEV_TAG" "$BUILD" "$SLUG" >> "$GITHUB_STEP_SUMMARY"

release:
name: Deploy to wordpress.org
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Pull requests from **forks** are not reviewed automatically: the review runs wit

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 push to `main` produces a development build**, the shipped tree stamped `<next>-dev.<N>`, as an artifact of the *Release* run in the Actions tab. Anyone can download it and try what is coming; it is not a release.
- **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.
- **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.
Expand Down
Loading
Loading