From 11d705ab0b888c99a0f21bceb77185d8aa96771a Mon Sep 17 00:00:00 2001 From: Igor Dobryn Date: Mon, 9 Nov 2026 16:37:04 +0200 Subject: [PATCH 1/5] Define release workflow --- .github/workflows/release.yml | 112 ++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..58764b2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,112 @@ +# Publishes the release drafted by draft-release.yml. Runs when the release PR lands on main +# (or manually, as a fallback / dry run): detects the pending draft from CHANGELOG.md, runs the +# tests without any write access, then un-drafts the GitHub Release from the `release` +# environment. Un-drafting creates the tag, and Packagist picks the version up from it. +name: Release +on: + push: + branches: [main] + paths: [CHANGELOG.md] + workflow_dispatch: + inputs: + dry_run: + description: "Dry run: detect the release and run the tests, but do not publish" + required: false + type: boolean + default: false + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + detect: + name: Detect pending release + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.pending.outputs.tag }} + go: ${{ steps.decide.outputs.go }} + steps: + - name: Refuse to release from a non-default branch + env: + REF: ${{ github.ref_name }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + run: | + if [ "$REF" != "$DEFAULT_BRANCH" ]; then + echo "::error::Releases must run from $DEFAULT_BRANCH, got $REF" + exit 1 + fi + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Resolve pending release + id: pending + uses: railsware/github-actions/resolve-pending-release@08ad3c39a7ff8276d1f0d815e862458f72c435cd + with: + tag-prefix: "" # tags are bare X.Y.Z + + # push: skip quietly when nothing is pending (an ordinary CHANGELOG edit). + # dispatch: a real run needs a pending draft; a dry run doesn't. + - name: Decide + id: decide + env: + EVENT: ${{ github.event_name }} + PENDING: ${{ steps.pending.outputs.pending }} + DRY_RUN: ${{ inputs.dry_run }} + run: | + if [ "$PENDING" = "true" ] || [ "$DRY_RUN" = "true" ]; then + echo "go=true" >> "$GITHUB_OUTPUT" + elif [ "$EVENT" = "workflow_dispatch" ]; then + echo "::error::No draft release for the top CHANGELOG.md version; run Draft PHP Release first" + exit 1 + else + echo "Nothing to release" + echo "go=false" >> "$GITHUB_OUTPUT" + fi + + test: + name: Test + needs: detect + if: needs.detect.outputs.go == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - uses: shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962 # 2.40.0 + with: + php-version: '8.4' + extensions: mbstring, intl, curl + coverage: none + + - name: Validate, install, test + run: | + composer validate + composer install --prefer-dist --no-progress + composer test + + release: + name: Publish + needs: [detect, test] + if: ${{ !inputs.dry_run }} + runs-on: ubuntu-latest + environment: release + permissions: + contents: write # edit and un-draft the release + steps: + - name: Check GitHub release + uses: railsware/github-actions/check-sdk-github-release@08ad3c39a7ff8276d1f0d815e862458f72c435cd + with: + tag: ${{ needs.detect.outputs.tag }} + + - name: Publish GitHub release + uses: railsware/github-actions/publish-github-release@08ad3c39a7ff8276d1f0d815e862458f72c435cd + with: + tag: ${{ needs.detect.outputs.tag }} + target: ${{ github.sha }} From 360145335aedec98f5c7e0be5614aad2413099d1 Mon Sep 17 00:00:00 2001 From: Igor Dobryn Date: Mon, 9 Nov 2026 16:44:04 +0200 Subject: [PATCH 2/5] Use master branch for shared github actions --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 58764b2..cb6fb53 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,7 +46,7 @@ jobs: - name: Resolve pending release id: pending - uses: railsware/github-actions/resolve-pending-release@08ad3c39a7ff8276d1f0d815e862458f72c435cd + uses: railsware/github-actions/resolve-pending-release@master with: tag-prefix: "" # tags are bare X.Y.Z @@ -101,12 +101,12 @@ jobs: contents: write # edit and un-draft the release steps: - name: Check GitHub release - uses: railsware/github-actions/check-sdk-github-release@08ad3c39a7ff8276d1f0d815e862458f72c435cd + uses: railsware/github-actions/check-sdk-github-release@master with: tag: ${{ needs.detect.outputs.tag }} - name: Publish GitHub release - uses: railsware/github-actions/publish-github-release@08ad3c39a7ff8276d1f0d815e862458f72c435cd + uses: railsware/github-actions/publish-github-release@master with: tag: ${{ needs.detect.outputs.tag }} target: ${{ github.sha }} From 0f802ec0d6bcab44244768335bb87efdaa74fe89 Mon Sep 17 00:00:00 2001 From: Igor Dobryn Date: Wed, 11 Nov 2026 15:20:56 +0200 Subject: [PATCH 3/5] Treat an unreleased CHANGELOG version as pending detect runs with a read-only token, which can't see draft releases, so resolve-pending-release now reports the newest CHANGELOG.md version as pending until it has a published release. A real dispatch on a version that is already released fails; the publish job's draft check confirms the draft after approval. --- .github/workflows/release.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cb6fb53..66b7c9a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,7 +51,8 @@ jobs: tag-prefix: "" # tags are bare X.Y.Z # push: skip quietly when nothing is pending (an ordinary CHANGELOG edit). - # dispatch: a real run needs a pending draft; a dry run doesn't. + # dispatch: a real run needs an unreleased version; a dry run doesn't. detect can't see drafts + # (read-only token), so the publish job confirms the draft after approval. - name: Decide id: decide env: @@ -62,7 +63,7 @@ jobs: if [ "$PENDING" = "true" ] || [ "$DRY_RUN" = "true" ]; then echo "go=true" >> "$GITHUB_OUTPUT" elif [ "$EVENT" = "workflow_dispatch" ]; then - echo "::error::No draft release for the top CHANGELOG.md version; run Draft PHP Release first" + echo "::error::The top CHANGELOG.md version is already released; run Draft PHP Release first" exit 1 else echo "Nothing to release" From 4f645c4308b4d61763cca20c955f23a0f47ee45c Mon Sep 17 00:00:00 2001 From: Igor Dobryn Date: Wed, 11 Nov 2026 16:00:16 +0200 Subject: [PATCH 4/5] Check the full ref in the default-branch guard github.ref_name is the short name, so a manual run started from a tag named like the default branch passed the check and would build that tag's commit. Comparing github.ref with refs/heads/ accepts only the branch. --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 66b7c9a..dc45cb9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,10 +32,10 @@ jobs: steps: - name: Refuse to release from a non-default branch env: - REF: ${{ github.ref_name }} + REF: ${{ github.ref }} DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} run: | - if [ "$REF" != "$DEFAULT_BRANCH" ]; then + if [ "$REF" != "refs/heads/$DEFAULT_BRANCH" ]; then echo "::error::Releases must run from $DEFAULT_BRANCH, got $REF" exit 1 fi From c5d42539d0ebd863773597201ce338d8137357b5 Mon Sep 17 00:00:00 2001 From: Igor Dobryn Date: Wed, 11 Nov 2026 16:08:55 +0200 Subject: [PATCH 5/5] Log what detect decided and warn on a blind dry run A dry run continues whether or not the top CHANGELOG.md version is released, so it passed even when detection was wrong. Decide now logs pending and dry_run, and a dry run on an already released version gets a warning that it didn't exercise detection. --- .github/workflows/release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dc45cb9..3630477 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,6 +60,10 @@ jobs: PENDING: ${{ steps.pending.outputs.pending }} DRY_RUN: ${{ inputs.dry_run }} run: | + echo "pending=$PENDING dry_run=$DRY_RUN" + if [ "$DRY_RUN" = "true" ] && [ "$PENDING" != "true" ]; then + echo "::warning::The top CHANGELOG.md version is already released, so this dry run doesn't exercise detection" + fi if [ "$PENDING" = "true" ] || [ "$DRY_RUN" = "true" ]; then echo "go=true" >> "$GITHUB_OUTPUT" elif [ "$EVENT" = "workflow_dispatch" ]; then