Repository navigation
Add a release-merge action that merges the release into the base and develop, then publishes it - #10
glaubersilva wants to merge 12 commits into
Conversation
release-prep's three-or-four-parts pattern moves to .github/lib/versions.js, where the other version rules live, so the release-merge action applies the same rule from the same place. The wrapper around the real @actions/exec moves from the release-prep test into tests/support, for the tests that run git for real.
Merges the release branch into the base branch and the base back into develop, then publishes the GitHub release for the version. Every step is safe to rerun: a branch that already holds the other merges as a no-op and an existing release is skipped. A conflict abandons the merge, prints a MERGE_CONFLICT line the release dashboard reads, and fails with the branch named. The token reaches git through the environment of the fetch and the pushes only, never the checkout's config, so the caller can keep persist-credentials off and run third-party actions after it. The tests run the merges with real git against a bare repository on disk.
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Action contract and shared helpers .github/lib/versions.js, .github/lib/exec.js, .github/lib/github.js, .github/lib/puprc.js, .github/actions/release-merge/action.yml, .github/actions/release-merge/release-merge.js, .github/actions/release-prep/release-prep.js, tests/support/actions.js, tests/actions/release-prep/release-prep.test.js |
Adds a shared three- or four-part numeric version pattern and helpers for command execution, repository and tag lookups, and .puprc version data. The action declares its inputs and outputs and validates version, branch, publish, and release-state conditions. release-prep uses the shared version pattern and helpers; its test uses shared recorded-execution support. |
Branch merge and conflict handling .github/actions/release-merge/release-merge.js, .github/actions/release-merge/action.yml, tests/actions/release-merge/release-merge.test.js |
Fetches and validates the release, base, and develop branches. Merges and pushes release into base, then base into develop. Reports conflicts and sets merge outputs. Tests cover validation, retries, merge behavior, conflicts, and failure cases. |
GitHub release publishing .github/actions/release-merge/release-merge.js, tests/actions/release-merge/release-merge.test.js |
Skips publishing when disabled or when the release already exists. Otherwise, generates release notes and creates a release against the base branch. |
Manual workflow and caller documentation templates/workflows/release-merge.yml, .github/actions/release-merge/README.md, README.md |
Adds a manually dispatched workflow that invokes the action with the release branch and version. Documents the action’s inputs, outputs, behavior, and caller requirements. The Phase 2 checklist marks the action complete and points to the workflow template. |
Estimated code review effort: 4 (Complex) | ~45 minutes
Change: Feature
Sequence Diagram(s)
sequenceDiagram
participant Workflow as release-merge workflow
participant Action as composite action
participant Merge as runMerge
participant Git as Git
participant Publish as runPublish
participant GitHub as GitHub API
Workflow->>Action: pass release branch and version
Action->>Merge: pass inputs and token
Merge->>GitHub: check tag and release
Merge->>Git: fetch branches and push merges
Action->>Publish: pass validated version and publish setting
Publish->>GitHub: check or create release
Possibly related PRs
- stellarwp/plugin-toolbox#7: Creates the release branch consumed by this action and uses the shared release-version pattern.
Suggested labels: enhancement
Suggested reviewers: d4mation, dpanta94
Merge Risk: 
Minimal · up to b066f
-
The release-merge action and caller template are ready to merge. No concrete blocking risk was established. Publishing a release after a long-delayed retry targets the base branch name, which is a narrow edge case worth keeping in mind.
Pre-merge checks: 
5
-
Full details: Title check
-
Explanation
The title names the main change: a new release-merge action that merges the release into the base and develop branches and then publishes the release. The raw summary and objectives confirm this is the primary change. The title is 95 characters, which is long but still within a readable single sentence, and it uses no vague terms or file lists.
Full details: Docstring Coverage
-
Explanation
Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 22 functions across 9 files. (3 skipped: 3 unsupported.)
-
Finishing Touches
-
📝 Generate docstrings
-
- Commit to this branch
- Create a new PR
🧪 Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
A tag with a published release was read as a retry and the merges went on with the publish skipped. That is right when the release came from this branch, and wrong when the version went out earlier from another commit and the branch was never bumped past it: the new changes would land under a version that is already out. The tag is now fetched and the action goes on only when it contains the branch; otherwise it refuses before merging, naming the fix.
Two releases prepared with the same version and deployed at the same time would merge side by side, the second landing its changes under a version the first had just published. One concurrency group for the whole repository makes the second wait, and the action then refuses it on the published tag. The README says why the caller has to do this.
readRepository, isNotFound and tagExists move to .github/lib/github.js and runOrFail to .github/lib/exec.js, so release-prep and release-merge read GITHUB_REPOSITORY, a tag and a command's failure the same way. The docs name the base branch as main or master wherever it comes up, say which rule release-prep applies and when, and note the two step outputs the action does not expose.
The version becomes the tag and the title of the release, so it has to be the one release-prep wrote into the branch. The action now reads every file the branch's .puprc lists under paths.versions, with the regex pup uses for each, and refuses before merging when any holds a different version, when one holds none, or when the branch has no .puprc. The reader lives in .github/lib/puprc.js.
The release dashboard now sends the deploy step the branch, as it sends prepare, so the template declares branch and passes it to the action as it is.
…ex JavaScript rejects A publish value that is neither true nor false failed only in the publish step, after both merges were pushed; the merge step reads it now too, so the run stops before anything is merged. A .puprc regex pup accepts but JavaScript does not surfaced as a bare SyntaxError; the error now names the file and the regex. The README says where in the sequence the tag ancestry check runs.
Resolves SOFT-5188
Adds
release-merge, a composite action plus a caller workflow to copy: the part of a deploy that is the same for every plugin repository with a base branch and adevelopbranch. The base branch ismain, ormasterwhere that is the repository's name for it (Give); it is thebase-branchinput, and "base branch" below means that branch. It follows the shape #3 and #7 set: the logic is a CommonJS module run byactions/github-script, inputs reach it throughenv, and it is unit tested withnode:testagainst the shared harness.What it does
In order, stopping at the first failure:
origin, with the full history when the checkout is shallow.publish: falseskips it.The checkout is left on the base branch at the merged commit, so the caller builds and deploys from there. Two callers use it: the deploy workflows of the two test plugins, in place of the stub in
stellarwp/release-dashboard-test-actions, and GiveWP'srelease-dashboard-deploy.ymlin impress-org/givewp#8529, which runs the action withbase-branch: masterand then GiveWP's build, WordPress.org deploy and Slack steps.Choices worth a look
--unshallowfirst when the checkout is shallow..git/config. It goes to git throughGIT_CONFIG_COUNT/GIT_CONFIG_KEY_0/GIT_CONFIG_VALUE_0in the environment of the fetch and the two pushes only (thehttp.<server>/.extraheaderheader actions/checkout would store), and the header is registered as a secret. The caller keepspersist-credentials: false, which matters when the same job goes on to run third-party actions. Nothing with the token is on a command line, so nothing with it reaches the log.--no-ff, so a release is one merge commit in the history.MERGE_CONFLICTon a line of its own. The merge is abandoned (git merge --abort), nothing is pushed for that branch, theconflictoutput names it and the step fails. The release dashboard classifies the run from that line, anchored at the start of a timestamped log line. The marker is printed by the script, not written in the workflow, so the step's echoed inputs never carry it and it appears only when a merge did conflict. The two lines git prints are in the log as well, which is the dashboard's fallback..github/lib/versions.jsand imported by both actions. A pre-release and a leadingvare refused: the version becomes the tag and the title of a published release. The version also has to be the one the release branch holds: the action reads every file the branch's.puprclists underpaths.versions, with the regex pup uses for each, and refuses when any holds a different version, when one holds none, or when the branch has no.puprc, naming the file. The tag comes fromversion, so a value other than what release-prep wrote would publish the code under the wrong version. A tag that exists without a published release is refused before anything is merged. A tag with a published release is a retry only when it contains the release branch; when it does not, the version went out from another commit and the branch was never bumped past it, and the action refuses with the branches untouched.generateReleaseNotesthencreateRelease, through github-script's Octokit. A release created with the default token starts noreleaseworkflow, which is wanted: the run that merged the release deploys it, so a repository with anon: releasedeploy does not deploy twice. GitHub renders thehttps://github.com/…/pull/Nlinks in the generated notes as#Non the release page, so the stub's rewrite of them is gone.concurrency, so the caller template uses one group for the whole workflow, not one per release: the second deploy waits, finds the tag published from another branch, and is refused. The README has the reasoning.force_failinput. The stub had one for drills; here a failure is tested with a real cause.git check-ref-format, base and develop have to differ, and the release branch cannot be either.publishis checked in the merge step as well, so a value that is neither true nor false fails before anything is merged.Shared code
.github/lib/github.js:readRepository(owner and name fromGITHUB_REPOSITORY),isNotFoundandtagExists, which treats a 404 as "no tag" and any other API error as a failure. Both actions use them; release-prep'srunCommitdid the repository read inline before..github/lib/exec.js:runOrFail, which release-prep had as a private function; release-merge runs git through it..github/lib/puprc.js:readVersionFiles(thepaths.versionsentries of a.puprc) andversionIn(the version a file holds, by the entry's regex). release-merge reads the branch's version with them; release-prep keeps its own.puprcreader for now, since it also readschecks.tbd.dirs.STELLARWP_VERSION_PATTERNmoves fromrelease-prep.jsto.github/lib/versions.js. release-prep keeps its export.recordedRealExec, the wrapper around the real@actions/execthe release-prep tests defined for themselves, moves totests/support/actions.js: the merge tests run git for real.Tests
node --test: 257 pass (213 before, 44 added). The merge tests build a bare repository on disk withmain, adevelopahead of it and arelease/…branch, clone it as the action's checkout and run the realgitthrough@actions/exec. They assert both branches' contents on the remote afterwards, the merge commits and their author, the checkout left on the base, the token present only in the environment of the fetch and the pushes and absent from.git/config, a no-op retry, a conflict on each of the two merges (marker at the start of a log line, nothing pushed for that branch, checkout clean), a published tag that does and does not contain the branch, a version the branch does not hold (in the first file, in the second file, a file the regex does not match, a regex JavaScript rejects, no.puprc), a wrongpublishvalue refused before any git call, and a shallow clone overfile://. The preflight and the publish run against an Octokit whose network is recorded.Five mutations were checked against the suite: the marker in lower case, the merges without
--no-ff, a conflict that neither aborts nor fails, the tag ancestry check removed, and the branch-version check removed. Each one is caught.actionlint1.7.12 is clean on the template and ontest.yml. It still flagsqueue: maxinrelease-tags.yml, as it did on #7; #6 adds theactionlint.yamlthat accepts it, so this branch leaves that alone.Verified end to end
On the two private test plugins in this org. Their deploy callers point at this branch:
release.ymlof release-test-plugin-a (the dashboard's default deploy workflow name) andrelease-dashboard-deploy.ymlof release-test-plugin-b (the override name). Every run was dispatched with the release branch,versionandproduction, the inputs the NX1 release dashboard sends. The callers took the branch asrelease_nameand putrelease/in front of it until stellarwp/nexcess-one#149; they now takebranchwhole, as the last row shows, and every checkout was anactions/checkout@v6default, so shallow. The first five runs were at1027c4a, the next five at8bce125, which added the tag ancestry check, and the last two at4779f4c, the head of this branch, which added the branch-version check.maingets Merge release/M26.smoke1 into main,developgets Merge main back into develop (release/M26.smoke1), both bygithub-actions[bot]; release 2.1.36 published onmain. The onlyAUTHORIZATIONline in the log is actions/checkout's own, masked.2.1.37-beta.1developchanged on a line the release changesmaingets the release and is pushed; the merge intodevelopconflicts, is aborted, and the job fails afterMERGE_CONFLICT: merging main into develop conflicted…on a line of its own.developunchanged, no release. The release dashboard's detector, run on the raw job log, reads it as a merge conflict from the marker.developby hand and pushingM26.test40with both plugins, driven from the NX1 release dashboard: prepare, review on the PR the dashboard opens (plugin-a#49, plugin-b#13), RC build, deploymainanddevelop(a, b); the dashboard's PRs show as merged by those commits. The branches had been cut right after 2.1.36 and 1.1.10 were published, so they still carried those versions: the publish was skipped and the changes landed under versions already out. That is the case the tag ancestry check now refuses, next row.release/M26.test40at 2.1.36 again, a version published before the branch was cutrelease/M26.conflictat 1.1.10 again, the branch 1.1.10 was published fromrelease/M26.race1andrelease/M26.race2, under the template'sconcurrencygrouppendinguntil it finished, then refused: "The tag 2.1.37 already exists and does not contain release/M26.race1".mainholds race2 only. Which run goes first is up to GitHub, not the dispatch order.release/M26.test40, which holds 2.1.36, dispatched as 2.1.37release/M26.conflictat 1.1.10 again, after the branch-version checkrelease/M26.conflictat 1.1.10 again, dispatched as-f branch=release/M26.conflicton the caller that takesbranchbranch: release/M26.conflictas it is and goes on as a retry.M26.test41with both plugins, driven from the dashboard with thebranchcontract of stellarwp/nexcess-one#149, at new versions 2.1.38 and 1.1.11branch: release/M26.test41from the dashboard, merge intomainanddevelop, and publish 2.1.38 and 1.1.11 onmainwith generated notes: the first releases created end to end by the dashboard through this action.Trying it yourself
mainanddevelop, copytemplates/workflows/release-merge.ymlto.github/workflows/on the default branch, withuses:pointing at this branch's commit.release/x.y.zfrommain, change a file on it and push.gh workflow run release-merge.yml -f branch=release/x.y.z -f version=x.y.zmaingetsMerge release/x.y.z into main,developgetsMerge main back into develop (release/x.y.z), both bygithub-actions[bot], and a releasex.y.zexists with generated notes.developand on a new release branch, then run step 3 for that one: the job fails on the develop merge with aMERGE_CONFLICT:line,mainhas the release,developis unchanged.Summary by CodeRabbit