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
49 changes: 44 additions & 5 deletions .github/workflows/publish-svn-v2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,50 @@ jobs:

github-release:
needs: publish
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: read
uses: OneSignal/sdk-shared/.github/workflows/github-release.yml@main
with:
version: ${{ needs.publish.outputs.bare_version }}
secrets:
GH_PUSH_TOKEN: ${{ secrets.GH_PUSH_TOKEN }}
steps:
- name: Create GitHub Release
uses: actions/github-script@v9
env:
TAG: ${{ github.ref_name }}
VERSION: ${{ needs.publish.outputs.bare_version }}
with:
github-token: ${{ secrets.GH_PUSH_TOKEN || github.token }}
script: |
// Tags are v-prefixed (e.g. v2.4.6); releases are titled with the bare version (e.g. 2.4.6).
const tag = process.env.TAG;
const version = process.env.VERSION;
const { owner, repo } = context.repo;

try {
await github.rest.repos.getReleaseByTag({ owner, repo, tag });
core.info(`Release for ${tag} already exists; skipping.`);
return;
} catch (e) {
if (e.status !== 404) throw e;
}

// The tagged commit is the merge commit of the "Release <version>" PR; look it up by
// SHA so this is immune to title prefix collisions (2.4.1 vs 2.4.10) and pagination.
const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner, repo, commit_sha: context.sha,
});
const releasePr =
prs.find(pr => pr.merged_at && pr.title.replace(/^chore:\s*/i, '') === `Release ${version}`) ||
prs.find(pr => pr.merged_at);
if (!releasePr) {
core.warning(`No merged release PR found for ${tag}; publishing release with empty notes.`);
}
const body = releasePr && releasePr.body ? releasePr.body.split('<!--')[0].trim() : '';

await github.rest.repos.createRelease({
owner, repo,
tag_name: tag,
name: version,
body,
draft: false,
prerelease: /alpha|beta/i.test(version),
});
49 changes: 44 additions & 5 deletions .github/workflows/publish-svn.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,50 @@

github-release:
needs: publish
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: read
uses: OneSignal/sdk-shared/.github/workflows/github-release.yml@main
with:
version: ${{ needs.publish.outputs.bare_version }}
secrets:
GH_PUSH_TOKEN: ${{ secrets.GH_PUSH_TOKEN }}
steps:
- name: Create GitHub Release
uses: actions/github-script@v9
env:
TAG: ${{ github.ref_name }}
VERSION: ${{ needs.publish.outputs.bare_version }}
with:
github-token: ${{ secrets.GH_PUSH_TOKEN || github.token }}
script: |
// Tags are v-prefixed (e.g. v3.9.1); releases are titled with the bare version (e.g. 3.9.1).
const tag = process.env.TAG;
const version = process.env.VERSION;
const { owner, repo } = context.repo;

try {
await github.rest.repos.getReleaseByTag({ owner, repo, tag });
core.info(`Release for ${tag} already exists; skipping.`);
return;
} catch (e) {
if (e.status !== 404) throw e;
}

// The tagged commit is the merge commit of the "Release <version>" PR; look it up by
// SHA so this is immune to title prefix collisions (3.9.1 vs 3.9.10) and pagination.
const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner, repo, commit_sha: context.sha,
});
const releasePr =
prs.find(pr => pr.merged_at && pr.title.replace(/^chore:\s*/i, '') === `Release ${version}`) ||
prs.find(pr => pr.merged_at);
if (!releasePr) {
core.warning(`No merged release PR found for ${tag}; publishing release with empty notes.`);
}
const body = releasePr && releasePr.body ? releasePr.body.split('<!--')[0].trim() : '';

await github.rest.repos.createRelease({
owner, repo,
tag_name: tag,
name: version,
body,
draft: false,
prerelease: /alpha|beta/i.test(version),

Check warning on line 145 in .github/workflows/publish-svn.yml

View check run for this annotation

Claude / Claude Code Review

prerelease regex misses rc/pre/dev/snapshot identifiers

⚪ Nit (same code lives in publish-svn-v2.yml:174): `prerelease: /alpha|beta/i.test(version)` only flags `alpha` and `beta` substrings, so a manually-pushed tag like `v3.9.1-rc.1` (or `-pre`/`-dev`/`-next`/`-snapshot`) would match the `v3.*` trigger and be published as a **stable** GitHub Release. Today `tag-on-release-merge.yml` only auto-tags strict `X.Y.Z`, so this only bites a manually-pushed prerelease tag — but broadening to `/-(alpha|beta|rc|pre|dev|snapshot)/i` is a one-liner while this c

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 ⚪ Nit (same code lives in publish-svn-v2.yml:174): prerelease: /alpha|beta/i.test(version) only flags alpha and beta substrings, so a manually-pushed tag like v3.9.1-rc.1 (or -pre/-dev/-next/-snapshot) would match the v3.* trigger and be published as a stable GitHub Release. Today tag-on-release-merge.yml only auto-tags strict X.Y.Z, so this only bites a manually-pushed prerelease tag — but broadening to /-(alpha|beta|rc|pre|dev|snapshot)/i is a one-liner while this code is being touched.

Extended reasoning...

What the bug is

In both publish-svn.yml:145 and publish-svn-v2.yml:174, the GitHub Release is created with:

prerelease: /alpha|beta/i.test(version),

That regex only recognises alpha and beta as prerelease markers. The other conventional identifiers — rc (release candidate, by far the most common after alpha/beta), pre, dev, next, snapshot — are not matched.

Concrete trigger

Step by step for a hypothetical v3.9.1-rc.1:

  1. A maintainer pushes v3.9.1-rc.1. It matches the workflow trigger tags: 'v3.*' and runs publish-svn.yml.
  2. The 'Strip v prefix from tag' step sets bare_version = '3.9.1-rc.1'.
  3. The github-release job runs the inline github-script. /alpha|beta/i.test('3.9.1-rc.1') evaluates to false.
  4. createRelease is called with prerelease: false — published as a stable GitHub Release.

Why existing guards don't catch it

tag-on-release-merge.yml:34 enforces ^chore: Release ([0-9]+\.[0-9]+\.[0-9]+)( \(#[0-9]+\))?$ on the commit subject before creating the v-tag, so the auto-tag flow only produces strict X.Y.Z. The only path to a prerelease v-tag is a maintainer manually pushing one — which is also the same path the PR description acknowledges was used for the 3.9.1 recovery.

Addressing the refutation

One verifier refuted this as a duplicate of a previously-refuted bug and as implausible because (a) the repo's auto-tag flow can't produce prerelease tags and (b) WP.org SVN doesn't have a prerelease concept. Both points are correct in fact but don't dispose of the issue: the prerelease: field clearly exists in the code with the intent of flagging prereleases — it just undercovers the conventional identifier set. The 'SVN trunk publishes a -rc tag as stable anyway' point is a separate, larger problem; it doesn't argue against fixing this line. And the manual-prerelease scenario, while rare, is exactly the kind of edge case that this PR exists to make self-healing.

Impact

Low — only fires when a maintainer manually pushes a non-X.Y.Z v-tag. The wrong-flag GitHub Release is also recoverable (edit the release, flip the checkbox). Hence nit, not normal.

Fix

Broaden the test to cover the standard set:

prerelease: /-(alpha|beta|rc|pre|dev|snapshot)/i.test(version),

or, if the intent is 'any SemVer prerelease', just /-/.test(version) since bare X.Y.Z never contains a hyphen. Apply in both files (publish-svn.yml:145 and `publish-svn-v2.yml:174").

});
Loading