Skip to content

feat: add release automation workflows#344

Open
akashsinghal wants to merge 6 commits intooras-project:mainfrom
akashsinghal:akashsinghal/releaseupdates
Open

feat: add release automation workflows#344
akashsinghal wants to merge 6 commits intooras-project:mainfrom
akashsinghal:akashsinghal/releaseupdates

Conversation

@akashsinghal
Copy link
Contributor

@akashsinghal akashsinghal commented Feb 12, 2026

What this PR does / why we need it

Adds two GitHub Actions workflows to automate manual steps in the release process, replacing error-prone manual issue creation and release note drafting with automation. The release steps themselves don't change — this PR automates the execution of existing steps while preserving the same deliberate flow.

Additionally, release-nuget.yml is updated to trigger on release: published instead of push: tags: v*, ensuring NuGet packages are only published after a maintainer explicitly publishes the draft release. The workflow also uploads the .nupkg and a SHA256 checksum as release assets for transparency and offline verification.

Release process with this PR

  1. Determine version — pick a SemVer2 tag (e.g., v1.0.0-rc.1)
  2. Run release-vote workflow — from the Actions tab, provide the tag name and optional commit SHA. A vote issue is created with maintainer checklist and PR changelog.
  3. Wait for vote to pass — maintainers approve via LGTM on the issue.
  4. Push the taggit tag <tag_name> <commit_sha> && git push origin <tag_name>. This creates a draft GitHub Release with auto-generated structured notes.
  5. Review the draft — edit release notes on the Releases page if needed.
  6. Publish the release — this triggers NuGet package build, publish to nuget.org, and uploads the .nupkg + SHA256 checksum as release assets.
  7. Wait for NuGet validation and announce.

Validation

All three workflows were end-to-end tested on a personal fork. The full flow was validated multiple times across iterations:

  1. release-vote — Triggered via workflow_dispatch with v0.7.0-test. Vote issue created with correct maintainer count (4), checkbox list parsed from MAINTAINERS.md, PR changelog populated, tag name validation passed. First-release edge case (no previous tags) handled correctly.
  2. release-github — Tag push created a draft release with categorized notes (Breaking Changes, New Features, Bug Fixes, Other Changes, Detailed Commits). Pre-release detection, tag validation, and git tag --merged ancestry all verified.
  3. release-nuget — Triggered only on release publish (not on tag push). Build succeeded, .nupkg and .sha256 checksum uploaded as release assets before the NuGet push step. NuGet push failed as expected (no API key configured on fork), confirming that assets are attached even if NuGet push encounters a transient failure. --skip-duplicate ensures re-runs are safe.

What changed

release-vote.yml (workflow_dispatch):

  • Triggered manually from the Actions tab with a tag name and optional commit SHA
  • Validates tag name against SemVer2 format and commit SHA with git rev-parse --verify
  • Dynamically parses MAINTAINERS.md to build a checkbox list of approvers
  • Counts maintainers via grep -c on checkbox lines for accuracy
  • Uses git tag --merged to find previous tag reachable from the target commit
  • Extracts PR numbers from git log between the previous tag and the target commit
  • Creates a formatted vote issue with maintainer list, PR changelog, and full changelog link
  • Handles first-release edge case when no previous tag exists

release-github.yml (tag push trigger):

  • Triggered automatically when a v* tag is pushed — tag push remains a deliberate manual action by the release manager so maintainers control when a release happens
  • Validates tag name against SemVer2 format
  • Uses git tag --merged to find previous tag reachable from the current tag (correct for release branches)
  • Looks up PR metadata via gh pr view API with a fallback to commit message parsing when API lookup fails
  • Falls back to raw commit listing when no PR numbers are found (direct commits)
  • Categorizes PRs by conventional commit prefix into Breaking Changes, New Features, Bug Fixes, and Other Changes — consistent with the v0.4.0 release notes format
  • Always creates draft releases (never auto-publishes) to require manual review before making release notes public
  • Automatically detects pre-release versions (alpha, beta, rc, preview) and marks them accordingly
  • Includes NuGet package link and full changelog comparison URL
  • Permissions: contents: write for release creation, pull-requests: read for PR metadata lookup

release-nuget.yml (updated trigger + release assets):

  • Changed from push: tags: v* to release: types: [published]
  • Validates tag name against SemVer2 format before version extraction
  • NuGet package is now only published when a maintainer explicitly publishes the draft release
  • Prevents accidental package publication before release notes are reviewed
  • Uploads .nupkg and SHA256 checksum as release assets before NuGet push — assets survive transient push failures
  • Uses --skip-duplicate on NuGet push for safe re-runs
  • Permissions: contents: write for release asset uploads

RELEASE_CHECKLIST.md updated to document the new automated flow including release asset uploads.

Key design decisions

  • Draft-only releases: Ensures maintainers review notes before publishing. No accidental public releases.
  • NuGet gated on publish: NuGet package is only published after the draft release is explicitly published, not on tag push. This allows retracting a draft without any package being published.
  • Release asset uploads: The .nupkg and its SHA256 checksum are attached to the GitHub release for archival, transparency, and offline verification. Assets are uploaded before NuGet push so they persist even if the push fails. NuGet.org remains the primary distribution channel.
  • Manual tag push: Preserves the human gate between a successful vote and the actual release.
  • Tag ancestry via git tag --merged: Previous tag is resolved relative to the target commit, ensuring correct behavior for both linear releases and release branches/backports.
  • SemVer2 tag validation: All three workflows validate the tag name format, guarding against injection and malformed input.
  • PR metadata fallback: When gh pr view fails, falls back to parsing commit messages. Fallback authors use plain text (no @) to avoid tagging the wrong person. When no PRs are found at all, falls back to raw commit listing.
  • grep -xFv for tag filtering: Uses whole-line fixed-string matching to avoid regex metacharacter issues with version dots.
  • First-release handling: Both workflows guard against empty previous tag, using full git log and omitting changelog comparison URL when appropriate.
  • Minimal permissions: Each workflow declares only the permissions it needs (contents: read/write, issues: write, pull-requests: read).

Which issue(s) this PR resolves / fixes

Fixes #343

Please check the following list

  • Does the affected code have corresponding tests, e.g. unit test, E2E test?
  • Does this change require a documentation update?
  • Does this introduce breaking changes that would require an announcement or bumping the major version?
  • Do all new files have an appropriate license header?

@codecov
Copy link

codecov bot commented Feb 12, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.76%. Comparing base (6c439ff) to head (caefc3b).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #344   +/-   ##
=======================================
  Coverage   91.76%   91.76%           
=======================================
  Files          64       64           
  Lines        2755     2755           
  Branches      364      364           
=======================================
  Hits         2528     2528           
  Misses        138      138           
  Partials       89       89           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@akashsinghal akashsinghal force-pushed the akashsinghal/releaseupdates branch from 5a25ab7 to a2f76f4 Compare February 12, 2026 21:47
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces GitHub Actions automation for the ORAS .NET library release process, replacing manual issue creation and release note drafting with two new workflows while preserving the existing deliberate release flow with manual gates.

Changes:

  • Added release-vote.yml workflow that creates release vote issues with maintainer checkboxes and PR changelogs when manually triggered
  • Added release-github.yml workflow that automatically generates draft releases with categorized release notes when version tags are pushed
  • Updated RELEASE_CHECKLIST.md to document the new automated workflow steps

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 10 comments.

File Description
.github/workflows/release-vote.yml Implements manual workflow to create vote issues by parsing maintainers and extracting PR numbers from git history
.github/workflows/release-github.yml Implements tag-triggered workflow to generate draft releases with categorized PRs using conventional commit prefixes
RELEASE_CHECKLIST.md Updates release process documentation to reflect new automated workflows and command examples

@akashsinghal akashsinghal force-pushed the akashsinghal/releaseupdates branch 2 times, most recently from b5993a6 to 8c9b6b2 Compare February 12, 2026 22:17
Copilot AI review requested due to automatic review settings February 12, 2026 22:17
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

@akashsinghal akashsinghal force-pushed the akashsinghal/releaseupdates branch from fe85641 to 8a8bf0d Compare February 13, 2026 00:19
Copilot AI review requested due to automatic review settings February 13, 2026 00:27
@akashsinghal akashsinghal force-pushed the akashsinghal/releaseupdates branch from 8a8bf0d to 55fbafa Compare February 13, 2026 00:27
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

@akashsinghal akashsinghal force-pushed the akashsinghal/releaseupdates branch from 55fbafa to 45d48f0 Compare February 13, 2026 00:33
Copilot AI review requested due to automatic review settings February 13, 2026 00:52
@akashsinghal akashsinghal force-pushed the akashsinghal/releaseupdates branch from 45d48f0 to 4880393 Compare February 13, 2026 00:52
@akashsinghal akashsinghal force-pushed the akashsinghal/releaseupdates branch from 4880393 to 073f541 Compare February 13, 2026 00:55
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.

- Add release-vote.yml: workflow_dispatch to create vote issue with
  maintainer checklist and PR changelog
- Add release-github.yml: tag-push triggered draft release with
  categorized notes (Breaking/Features/Fixes/Other)
- Update RELEASE_CHECKLIST.md to document new automated flow
- PR metadata fallback from commit messages when API lookup fails
- First-release edge case handling (no previous tag)

Signed-off-by: Akash Singhal <akashsinghal@microsoft.com>
@akashsinghal akashsinghal force-pushed the akashsinghal/releaseupdates branch from 073f541 to 635125f Compare February 13, 2026 01:02
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

Copilot AI review requested due to automatic review settings February 13, 2026 01:26
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Copilot AI review requested due to automatic review settings February 13, 2026 01:32
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

Copilot AI review requested due to automatic review settings February 13, 2026 01:40
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Akash Singhal <akashksinghal98@gmail.com>
Signed-off-by: Akash Singhal <akashsinghal@microsoft.com>
@akashsinghal akashsinghal force-pushed the akashsinghal/releaseupdates branch from 2e5c827 to 78333dd Compare February 13, 2026 01:42
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Akash Singhal <akashksinghal98@gmail.com>
Copilot AI review requested due to automatic review settings February 13, 2026 01:45
akashsinghal and others added 3 commits February 12, 2026 17:46
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Akash Singhal <akashksinghal98@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Akash Singhal <akashksinghal98@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Akash Singhal <akashksinghal98@gmail.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Copy link
Contributor

@shizhMSFT shizhMSFT left a comment

Choose a reason for hiding this comment

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

Caution

These review comments are generated by GitHub Copilot using Claude Opus 4.6, mimicking shizhMSFT's code review style. This is not the real shizhMSFT — just an AI approximation. Comments are advisory only.

Overall the automation looks solid — draft-only releases and NuGet gating on publish are good design decisions. A few inline comments below, primarily around script injection in the workflow run: blocks.


- name: Validate tag name
run: |
TAG_NAME="${{ inputs.tag_name }}"
Copy link
Contributor

Choose a reason for hiding this comment

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

${{ inputs.tag_name }} is directly interpolated into the shell by GitHub's template engine before the script runs. This is vulnerable to script injection — a crafted input can execute arbitrary commands.

We should pass all user-controlled inputs via env: mapping:

      - name: Validate tag name
        env:
          TAG_NAME: ${{ inputs.tag_name }}
        run: |
          if ! echo "$TAG_NAME" ...

Same pattern applies to all ${{ inputs.* }} and ${{ steps.*.outputs.* }} expressions used directly in run: blocks across all three workflow files.

- name: Resolve commit SHA
id: resolve
run: |
if [ -n "${{ inputs.commit_sha }}" ]; then
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here for ${{ inputs.commit_sha }}.

TAG_NAME="${{ inputs.tag_name }}"
COMMIT_SHA="${{ steps.resolve.outputs.sha }}"
PREV_TAG="${{ steps.prev_tag.outputs.tag }}"
MAINTAINERS="${{ steps.maintainers.outputs.list }}"
Copy link
Contributor

Choose a reason for hiding this comment

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

Same issue. Multiline step outputs (maintainers.outputs.list, changelog.outputs.pr_list) are particularly dangerous — embedded newlines or shell metacharacters in maintainer names or PR titles can break out of the double-quoted string.

GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG_NAME="${GITHUB_REF_NAME}"
PREV_TAG="${{ steps.prev_tag.outputs.tag }}"
Copy link
Contributor

Choose a reason for hiding this comment

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

Same concern — an existing tag containing shell metacharacters could be picked up by git tag --merged and injected here.

id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
run: |
TAG_NAME="${{ github.event.release.tag_name }}"
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here for ${{ github.event.release.tag_name }}.

# commit message parsing if the API lookup fails.
if [ -n "$PREV_TAG" ]; then
PR_NUMBERS=$(git log --oneline "$PREV_TAG..$TAG_NAME" \
| grep -oP '#\K\d+' | sort -n -u || true)
Copy link
Contributor

Choose a reason for hiding this comment

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

grep -oP '#\K\d+' matches any #number in commit messages, not just PR references. Issue references like "Fixes #42" or comments like "addresses #100" would be picked up as false positives. Should we match the conventional merge commit format (#<number>) instead?

grep -oP '\(#\K\d+(?=\))'

GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NUPKG: ./src/OrasProject.Oras/bin/Release/OrasProject.Oras.${{ steps.version.outputs.version }}.nupkg
run: |
sha256sum "$NUPKG" | awk '{print $1}' > "${NUPKG}.sha256"
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: awk '{print $1}' strips the filename from the checksum. The standard sha256sum --check format expects <hash> <filename>. Should we keep the full output so users can verify with sha256sum --check *.sha256?


# Determine if this is a pre-release
PRERELEASE_FLAG=""
if echo "$TAG_NAME" | grep -qE '[-](alpha|beta|rc|preview)'; then
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we check for the presence of a hyphen after the patch version instead? Per SemVer2, any version with a pre-release identifier (i.e. a - suffix after MAJOR.MINOR.PATCH) is a pre-release. The current pattern misses identifiers like dev, nightly, etc.

- name: Validate tag name
run: |
if ! echo "${GITHUB_REF_NAME}" \
| grep -qP '^v\d+\.\d+\.\d+(-[a-zA-Z0-9.\-]+)?(\+[a-zA-Z0-9.\-]+)?$'; then
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: the SemVer validation regex is duplicated across all three workflows. Consider extracting to a reusable workflow or composite action to avoid drift.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add release automation workflows for vote issue and draft release creation

2 participants