|
| 1 | +name: Release PR Version |
| 2 | + |
| 3 | +on: |
| 4 | + # `edited` is not one of the default pull_request types, but it is the point of this |
| 5 | + # workflow: retitling the release PR is how a maintainer picks an exact version. The |
| 6 | + # other types keep the consistency check attached to the PR as it evolves. |
| 7 | + pull_request: |
| 8 | + types: [opened, reopened, edited, synchronize] |
| 9 | + |
| 10 | +# Read-only by default; only the job that pushes the Release-As commit widens this. |
| 11 | +permissions: |
| 12 | + contents: read |
| 13 | + |
| 14 | +jobs: |
| 15 | + version-consistency: |
| 16 | + # Also the check name the release PR's footer tells maintainers to wait for. |
| 17 | + name: Release PR version |
| 18 | + # Only the platform's release PR carries a version in its title; every other pull request |
| 19 | + # skips this job, which GitHub reports as neutral. The release may be presented from |
| 20 | + # scalar-next itself or from the release-please branch rendered off it, so both heads |
| 21 | + # count as the release PR. |
| 22 | + if: >- |
| 23 | + ${{ github.event.pull_request.base.ref == 'main' |
| 24 | + && (github.event.pull_request.head.ref == 'scalar-next' |
| 25 | + || startsWith(github.event.pull_request.head.ref, 'release-please--branches--scalar-next--')) }} |
| 26 | + runs-on: ubuntu-latest |
| 27 | + steps: |
| 28 | + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 |
| 29 | + with: |
| 30 | + # The version committed on the PR head is what merging would actually release. |
| 31 | + ref: ${{ github.event.pull_request.head.sha }} |
| 32 | + # Fails while the title version and the committed version disagree — the window between |
| 33 | + # a maintainer's retitle and the platform re-rendering the PR from it. Without this a |
| 34 | + # merge in that window would tag a release whose own files self-report the old version, |
| 35 | + # and a title release-please cannot parse would silently cut no release at all. |
| 36 | + - name: Compare the title version with the committed version |
| 37 | + env: |
| 38 | + # The title is human input, so it is bound through the environment (never |
| 39 | + # interpolated into the script) and matched against a strict semver pattern |
| 40 | + # before it is read. |
| 41 | + PR_TITLE: ${{ github.event.pull_request.title }} |
| 42 | + run: | |
| 43 | + pattern='^release: ((0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?)$' |
| 44 | + if [[ ! "$PR_TITLE" =~ $pattern ]]; then |
| 45 | + echo "::error::Release PR title must be \"release: X.Y.Z\" with a full semver version, got: $PR_TITLE" |
| 46 | + exit 1 |
| 47 | + fi |
| 48 | + title_version="${BASH_REMATCH[1]}" |
| 49 | + manifest_version="$(jq -r '.["."]' .release-please-manifest.json)" |
| 50 | + if [[ "$title_version" != "$manifest_version" ]]; then |
| 51 | + echo "::error::Release PR title says $title_version but the pull request is versioned $manifest_version. Wait for the release PR to be re-rendered at $title_version before merging." |
| 52 | + exit 1 |
| 53 | + fi |
| 54 | + echo "Release PR title and committed version agree on $title_version." |
| 55 | +
|
| 56 | + apply-title-version: |
| 57 | + name: Apply edited release version |
| 58 | + # A human retitle of the open release PR is bridged into the canonical explicit-version |
| 59 | + # mechanism (a Release-As commit on scalar-next, which the platform re-renders the |
| 60 | + # release PR from). `changes.title` is only set when the title itself changed, and bot |
| 61 | + # senders are ignored so the platform's own retitles cannot bounce back into another |
| 62 | + # commit. |
| 63 | + if: >- |
| 64 | + ${{ github.event.action == 'edited' |
| 65 | + && github.event.changes.title != null |
| 66 | + && github.event.sender.type != 'Bot' |
| 67 | + && github.event.pull_request.state == 'open' |
| 68 | + && github.event.pull_request.base.ref == 'main' |
| 69 | + && (github.event.pull_request.head.ref == 'scalar-next' |
| 70 | + || startsWith(github.event.pull_request.head.ref, 'release-please--branches--scalar-next--')) }} |
| 71 | + # One bridge run at a time per pull request, newest retitle wins. Two retitles in quick |
| 72 | + # succession (a version typo corrected seconds later) would otherwise start two runs that |
| 73 | + # both read the version committed on the PR head, both get past the no-op guard, and both |
| 74 | + # push — and since each fetches scalar-next immediately before committing, the second |
| 75 | + # push fast-forwards instead of failing. release-please honours the newest Release-As |
| 76 | + # footer, so the release would land on whichever run happened to push last, not on the |
| 77 | + # title the maintainer actually left behind. |
| 78 | + concurrency: |
| 79 | + group: release-title-edit-${{ github.event.pull_request.number }} |
| 80 | + cancel-in-progress: true |
| 81 | + runs-on: ubuntu-latest |
| 82 | + permissions: |
| 83 | + contents: write |
| 84 | + steps: |
| 85 | + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 |
| 86 | + with: |
| 87 | + # The PR head, so the version the pull request currently carries can be read before |
| 88 | + # deciding whether anything needs to change. |
| 89 | + ref: ${{ github.event.pull_request.head.sha }} |
| 90 | + - name: Push a Release-As commit for the edited version |
| 91 | + env: |
| 92 | + PR_TITLE: ${{ github.event.pull_request.title }} |
| 93 | + run: | |
| 94 | + set -euo pipefail |
| 95 | + pattern='^release: ((0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?)$' |
| 96 | + if [[ ! "$PR_TITLE" =~ $pattern ]]; then |
| 97 | + echo "::error::Release PR title must be \"release: X.Y.Z\" with a full semver version, got: $PR_TITLE" |
| 98 | + exit 1 |
| 99 | + fi |
| 100 | + # Only ever a validated semver from here on, so it is safe in a commit message. |
| 101 | + version="${BASH_REMATCH[1]}" |
| 102 | + manifest_version="$(jq -r '.["."]' .release-please-manifest.json)" |
| 103 | + # The re-rendered PR is retitled to the version it now carries; stopping here keeps |
| 104 | + # that from producing an endless chain of Release-As commits. |
| 105 | + if [[ "$version" == "$manifest_version" ]]; then |
| 106 | + echo "This release PR already carries $version; nothing to do." |
| 107 | + exit 0 |
| 108 | + fi |
| 109 | + git config user.name "github-actions[bot]" |
| 110 | + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" |
| 111 | + git fetch origin scalar-next |
| 112 | + git checkout -B scalar-next origin/scalar-next |
| 113 | + git commit --allow-empty -m "chore: release $version" -m "Release-As: $version" |
| 114 | + # Plain (non-force) push: losing a race against a regeneration push fails loudly |
| 115 | + # rather than discarding it, and the retitle can simply be repeated. |
| 116 | + git push origin scalar-next |
| 117 | + echo "Pushed Release-As: $version to scalar-next. The release PR will be re-rendered at that version." |
0 commit comments