ci(github): tag and publish a GitHub Release when the release PR merge - #473
Conversation
Move the <Version> read out of CheckSolutionVersions.fsx into a loadable scripts/Versioning.fsx, so the release tooling added next resolves the version through the same code path. Notes: - More than one <Version> element is now an error rather than silently taking the first, since that would mean the file has stopped being the source of truth. - The module also gains isPreRelease, deriving SemVer pre-release status from a version string. It belongs with the version semantics rather than with the release script that consumes it. Refs informedica#470
scripts/Changelog.fsx extracts the section a version's GitHub Release body is built from. It implements a small changelog grammar rather than parsing Markdown: any line starting `## ` delimits a section, and both ShipIt's `## 0.1.2-alpha.4 - <date>` and the hand-written `## [0.1.2-alpha.1] - <date>` headings that predate it are also recognised. A lookup that fails says how it failed. Missing, empty and duplicated sections are distinguished rather than collapsing into one message, and duplicates are rejected instead of silently taking the first -- 7611fe0 removed a duplicate heading from this changelog, so that is not hypothetical. scripts/ChangelogTests.fsx pins the grammar with Expecto, including a check that the shipped CHANGELOG.md still has a section for the version in Directory.Build.props, which fails if ShipIt ever changes its heading format. Refs informedica#470
Add tag-release.yml. When a ShipIt release PR merges it creates an annotated v<version> tag on the merge commit and publishes a GitHub Release carrying that version's CHANGELOG.md section, flagged pre-release for a SemVer pre-release version. Both steps are idempotent, so re-running is safe. ShipIt 3.0.1 cannot create a tag or a Release, so nothing downstream of a merged release PR produced an immutable ref; this is the missing part of informedica#234 item 2. The trigger deliberately does not use ShipIt's documented head_commit condition. All three release PRs so far merged as merge commits, so head_commit.message is always "Merge pull request #NNN from ..." and never starts with "chore: release " -- 0 for 3. The head ref is merge-method independent instead. scripts/ReleaseNotes.fsx supplies the version, tag and Release body, so CI, a local dry run and the backfill of versions that shipped before this workflow existed all run the same code rather than three duplicates. Its argument parsing fails closed: an unknown flag, a repeated flag or a stray positional is an error, $GITHUB_OUTPUT is validated before anything is written. Refs informedica#470
DEVELOPMENT.md's Release Automation section gains the tag and Release step: what it does, the trigger, why ShipIt's documented head_commit condition is not used, how to preview a Release body locally, and the GITHUB_TOKEN chaining constraint that decides the shape informedica#459 takes. That constraint was confirmed against this repo rather than taken from the documentation. None of the three ShipIt release PRs, which were opened by github-actions[bot], ran its checks automatically: informedica#455 and informedica#458 had runs created but parked at action_required until a maintainer re-ran them, and informedica#464 got no pull_request runs at all until it was closed and reopened by hand. So, an on: release or on: push: tags: workflow will not fire from a tag this workflow creates. ADR-0021 is amended with the release-artifact as design choice 7, and 0000-change-log.md gets a matching entry. Refs informedica#470
Greptile SummaryThis PR adds automation that turns merged ShipIt release PRs into annotated version tags and GitHub Releases, along with release-note extraction tooling and updated release-process documentation.
Confidence Score: 2/5The PR should not merge until release jobs are restricted to genuine repository-owned ShipIt PRs and existing tags are validated against the intended merge commit. The workflow can publish official artifacts for a merged non-ShipIt branch sharing the expected name, and its recovery path can publish a Release for an existing tag that points at the wrong revision. Files Needing Attention: .github/workflows/tag-release.yml
|
| Filename | Overview |
|---|---|
| .github/workflows/tag-release.yml | Adds the release publication workflow, but its ShipIt identity gate and existing-tag recovery path can publish unintended or incorrectly targeted release artifacts. |
| DEVELOPMENT.md | Documents the new release workflow, its trigger, local preview commands, and downstream GITHUB_TOKEN limitation. |
| docs/mdr/design-history/0021-build-system-versioning-and-release.md | Amends the accepted release architecture with explicit tag-target, pre-release, backfill, and artifact decisions. |
| docs/mdr/design-history/0000-change-log.md | Records the ADR amendment in the design-history change log. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Pull request closed against master] --> B{Merged and head.ref equals release/master?}
B -- No --> C[Skip workflow]
B -- Yes --> D[Checkout merge_commit_sha]
D --> E[Extract version and changelog section]
E --> F{Tag name already exists?}
F -- No --> G[Create and push annotated tag]
F -- Yes --> H[Accept existing tag without target validation]
G --> I{GitHub Release exists?}
H --> I
I -- No --> J[Publish GitHub Release]
I -- Yes --> K[Leave existing Release unchanged]
Reviews (1): Last reviewed commit: "docs(config): record the release-artifac..." | Re-trigger Greptile
0.1.2-alpha.2, .3 and .4 shipped before tag-release.yml existed and stay untagged rather than being tagged retroactively: a backfilled tag carries a tagger date unrelated to when the version shipped, and CHANGELOG.md plus the merge commits it links already reconstruct those three. The tag record therefore starts at the first release after this lands. ADR-0021's settled-points table, the design-history entry and DEVELOPMENT.md now say so, and the comments claiming backfill as a caller of scripts/ReleaseNotes.fsx are corrected to CI plus local dry runs. The script still accepts an explicit version, which is what a dry run against an older release uses. Refs informedica#470
Two findings from review of informedica#473. The job guard tested only merged and head.ref, and head.ref is a bare branch name. A merged pull request from a fork with a branch called release/master would have reached the tag and Release writes. The guard now also requires head.repo.full_name to be this repository, which is always true of a ShipIt release PR. The existing-tag check matched on name alone, so a tag pointing at some other commit was accepted and the Release published against it, leaving the version's immutable ref naming the wrong revision. It now compares the tag's target with the merge commit: equal means a re-run and the step is skipped, different fails the job. The comparison reads refs/tags/X^{} rather than refs/tags/X, because for an annotated tag the plain ref resolves to the tag object rather than the commit. Refs informedica#470
Overview
Closes #470, the last open part of #234 item 2
ShipIt delivered the version bump and the changelog section; it cannot produce release artifacts.
This adds
.github/workflows/tag-release.yml, which fires when a ShipIt release PR merges and:pull_request.merge_commit_sha);scripts/ReleaseNotes.fsxto get the version, tag and Release body;v<version>tag on that commit;CHANGELOG.mdsection, flagged pre-release for a SemVer pre-release version.Both write steps are idempotent, so a re-run recovers rather than duplicating.
Further information
v-prefixed, so downstream workflows can filter onv*.master, not ShipIt'schore: releasecommit on the reusedrelease/masterbranch.CHANGELOG.mdfront matter.Why not use ShipIt's trigger:
github.event.head_commit.messageis not reliable for merge commits. The workflow instead watchesrelease/master, which works regardless of merge method.Why not read the pre-release flag from the changelog:
pre_release:describes the next release, so it can drift over time.Versioning.isPreReleasederives it from the actual version.About #459: the tag and release are created with
GITHUB_TOKEN, which does not trigger workflows. The Docker publish therefore has to be in the same workflow, or useworkflow_dispatch/repository_dispatch, or a PAT/App token.Why parse in a script instead of YAML: the same version logic is used by CI, backfill, and local dry runs. Keeping it in
Versioning.fsxavoids version-resolution drift.AI usage disclosure
Claude code was used to assemble the documentation and verify some logic for ShipIt's mechanism for release tags etc.