Skip to content

feat: one-click GitHub release workflow#9

Merged
znat merged 2 commits intomainfrom
feat/release-workflow
May 2, 2026
Merged

feat: one-click GitHub release workflow#9
znat merged 2 commits intomainfrom
feat/release-workflow

Conversation

@znat
Copy link
Copy Markdown
Owner

@znat znat commented May 2, 2026

Adds `.github/workflows/release.yml` — a one-click `workflow_dispatch` entry point that produces a real GitHub Release (not just a tag) with auto-generated notes.

How to release

  1. Open this repo's Actions tab → ReleaseRun workflow.
  2. Enter the version without the leading `v` (e.g. `1.0.0`, or `1.1.0-rc.1` for a pre-release).
  3. Click Run. ~1 minute later you have a tag, a moving major pointer, a GH Release with notes.

What the workflow does

# Step
1 Validates the version is semver and the `v` tag doesn't already exist
2 Gates on `yarn typecheck` + `yarn test` — red tests cancel the release
3 Bumps `version` in root + both workspace `package.json` files in lockstep (single-product monorepo)
4 Commits `release: v` and pushes to `main`
5 Creates immutable tag `v` + moves the major-version pointer (`v1`, `v2`, …)
6 Creates a real GitHub Release via `gh release create --generate-notes`. Versions with a hyphen suffix get `--prerelease` automatically

What this gives consumers

```yaml

stable, auto-upgrades on non-breaking releases (the actions/checkout@v4 pattern)

uses: znat/gitpulse/.github/workflows/publish.yaml@v1

locked to one exact release for full reproducibility

uses: znat/gitpulse/.github/workflows/publish.yaml@v1.0.0
```

README

New "Releasing (maintainers)" section walks through the flow and notes the branch-protection gotcha (default `GITHUB_TOKEN` may need to be allow-listed if `main` is protected).

Test plan

  • `yarn typecheck` clean
  • `yarn test` — 94/94 pass
  • After merge: dry-run by dispatching with version `0.0.1-test.0` to validate the workflow steps end-to-end. (Note: the version-already-exists check + auto pre-release flag mean this is a safe smoke test — easy to delete the tag/release after.)
  • Then real release: `1.0.0`

Summary by CodeRabbit

  • Chores
    • Added a manual one-click release workflow to automate version bumps, tagging, and GitHub Release creation across workspace packages.
    • Added maintainer-facing documentation describing how to trigger and validate releases, required version format, and release gating steps.

Adds .github/workflows/release.yml — a workflow_dispatch entry point
that produces a real GitHub Release (not just a tag) with auto-
generated notes.

What the workflow does on Run:
1. Validates the input version is semver and the v<version> tag does
   not already exist.
2. Gates the release on yarn typecheck + yarn test passing.
3. Bumps `version` in the root and both workspace package.json files
   in lockstep (single-product monorepo).
4. Commits the bump as 'release: v<version>' and pushes to main.
5. Creates the immutable v<version> tag plus moves the major-version
   pointer (v1, v2, …) — the moving tag is what consumers pin via @v1.
6. Creates a GitHub Release via `gh release create --generate-notes`,
   marking it as pre-release if the version contains a hyphen suffix
   (e.g. 1.0.0-rc.1).

README has a new "Releasing (maintainers)" section explaining the
flow and the branch-protection gotcha (default GITHUB_TOKEN may need
to be allow-listed in protection rules).
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

📝 Walkthrough

Walkthrough

Adds a manual GitHub Actions release workflow and README docs. The workflow validates a SemVer input, runs typecheck/tests, bumps package.json versions (root and workspaces), creates annotated + major pointer tags, pushes to main, and creates a GitHub Release (auto-notes, prerelease when hyphenated).

Changes

Release Workflow & Documentation

Layer / File(s) Summary
Dispatch & Permissions
.github/workflows/release.yml
Adds workflow_dispatch with version input and sets contents: write permissions and concurrency: release.
Input Validation & Guards
.github/workflows/release.yml
Rejects runs not from main, validates SemVer via regex, and fails if tag v$VERSION already exists.
Environment Setup & Gates
.github/workflows/release.yml
Sets up Node 22 with Yarn cache, runs yarn install --frozen-lockfile, yarn typecheck, and yarn test.
Version Bump & Tagging
.github/workflows/release.yml
Configures git identity, updates package.json in root and workspaces (@gitpulse/action, @gitpulse/site), commits bump, creates annotated tag v$VERSION, force-updates major pointer tag v<major>, and pushes commit + tags to main.
Release Publication
.github/workflows/release.yml
Creates GitHub Release with gh release create --generate-notes, adding --prerelease when the version contains a hyphen.
Documentation
README.md
Adds "Releasing (maintainers)" describing semver input, workflow steps, tagging strategy, prerelease behavior, and protected-branch push notes.
sequenceDiagram
  autonumber
  actor Maintainer
  participant GitHub as "GitHub Actions"
  participant Repo as "Repository (git)"
  participant Node as "Node/Yarn"
  participant GHCLI as "gh (GitHub CLI)"

  Maintainer->>GitHub: Manually dispatch workflow (version)
  GitHub->>Repo: Confirm ref is refs/heads/main
  GitHub->>GitHub: Validate SemVer & tag absence
  GitHub->>Node: Setup Node 22 + Yarn
  Node->>Node: yarn install, typecheck, test
  GitHub->>Repo: Update package.json versions, commit
  GitHub->>Repo: Create annotated tag v$VERSION and update v<major>
  GitHub->>Repo: Push commit and tags to main
  GitHub->>GHCLI: gh release create v$VERSION --generate-notes [--prerelease?]
  GHCLI->>GitHub: Create GitHub Release
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A one-click bell rings, releases take flight,
Bumps and tags hop along through the night,
Tests stand guard, notes grow bright,
Push and publish — a caretaked sight,
Hooray for the release, snug and right!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: introducing a one-click GitHub release workflow automation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/release-workflow

Review rate limit: 8/10 reviews remaining, refill in 11 minutes and 18 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/release.yml:
- Around line 28-33: The workflow allows workflow_dispatch from arbitrary
branches but later unconditionally pushes HEAD to main; update the push step
(the step that runs the git push/force push to main) to only run when the
dispatch came from the main branch by adding a conditional that checks
github.ref == 'refs/heads/main' (or equivalent: github.event_name ==
'workflow_dispatch' && github.ref == 'refs/heads/main') so non-main refs cannot
be pushed to main; also ensure the checkout uses fetch-depth: 0 remains but the
push step is gated so only refs matching main execute the push.
- Line 37: The SemVer regex in the if condition that tests "$VERSION" currently
disallows build metadata; update the pattern used in the if ! [[ "$VERSION" =~
... ]] check to accept an optional +build metadata segment by appending an
optional (\+[0-9A-Za-z.-]+)? group so valid versions like 1.2.3+build.7 pass
(keep the existing optional pre-release group -[0-9A-Za-z.-]+? intact and ensure
anchors ^...$ remain).
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4428ab29-2abf-4219-bd7a-46131d5bfb9c

📥 Commits

Reviewing files that changed from the base of the PR and between 61a6717 and 408c07a.

📒 Files selected for processing (2)
  • .github/workflows/release.yml
  • README.md

Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml Outdated
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

⛔ CodeRabbit does not have permission to push changes to this repository. Please ensure CodeRabbit has Contents: Read and write permission.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

⛔ CodeRabbit does not have permission to push changes to this repository. Please ensure CodeRabbit has Contents: Read and write permission.

Two CodeRabbit findings on PR #9:

1. Critical: workflow_dispatch can launch from any branch, but the
   workflow pushes HEAD to main. That would silently release a
   non-main ref's code. Add an early step that hard-fails if
   github.ref != refs/heads/main.

2. Minor: SemVer 2.0.0 allows build metadata after a + (e.g.
   1.2.3+build.7). The previous regex rejected those. Extend it to
   accept an optional +[0-9A-Za-z.-]+ suffix.

Refs:
- #9 (comment)
- #9 (comment)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/release.yml:
- Around line 105-109: The prerelease detection wrongly treats any '-' anywhere
in VERSION as prerelease (so versions like 1.2.3+build-7 are misclassified); fix
it by testing for a hyphen only in the part of VERSION before any build metadata
by first stripping anything after '+' (e.g., assign BASE from VERSION up to the
first '+' using shell parameter expansion) and then set
PRERELEASE_FLAG="--prerelease" only if that BASE contains a '-' ; update the
block that sets PRERELEASE_FLAG and reference the VERSION and PRERELEASE_FLAG
variables so the logic checks BASE (stripped of build metadata) for a prerelease
suffix.
- Around line 82-84: The git commit step can fail when there are no staged
changes; after the git add (package.json action/package.json site/package.json)
replace the unconditional git commit -m "release: v$VERSION" with a guard that
checks for staged changes (e.g., use git diff --cached --quiet || git commit -m
"release: v$VERSION" or if git diff --cached --quiet; then echo "no changes to
commit"; else git commit -m "release: v$VERSION"; fi) so the workflow continues
safely on reruns when npm pkg set produced no changes.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1f1d4a53-dc81-4761-8564-78f2c1d162a7

📥 Commits

Reviewing files that changed from the base of the PR and between 408c07a and 3f02614.

📒 Files selected for processing (1)
  • .github/workflows/release.yml

Comment on lines +82 to +84
git add package.json action/package.json site/package.json
git commit -m "release: v$VERSION"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C2 'git add package\.json action/package\.json site/package\.json|git commit -m "release: v\$VERSION"' .github/workflows/release.yml

Repository: znat/gitpulse

Length of output: 374


🏁 Script executed:

cat -n .github/workflows/release.yml | head -100

Repository: znat/gitpulse

Length of output: 4007


Guard git commit to support safe reruns.

The git commit at line 83 fails when there are no staged changes, which occurs during workflow reruns for the same version after a partial failure. When the workflow fails after the Push step but before completion (e.g., at GitHub Release), a rerun checks out code that already contains the version bump. The subsequent npm pkg set calls are idempotent and produce no changes, causing git commit to exit with code 1 and fail the step.

Wrap the commit in a conditional check:

Suggested patch
           npm pkg set version="$VERSION" -w `@gitpulse/action`
           npm pkg set version="$VERSION" -w `@gitpulse/site`
           git add package.json action/package.json site/package.json
-          git commit -m "release: v$VERSION"
+          if ! git diff --cached --quiet; then
+            git commit -m "release: v$VERSION"
+          else
+            echo "No version file changes detected; continuing without a new commit."
+          fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 82 - 84, The git commit step can
fail when there are no staged changes; after the git add (package.json
action/package.json site/package.json) replace the unconditional git commit -m
"release: v$VERSION" with a guard that checks for staged changes (e.g., use git
diff --cached --quiet || git commit -m "release: v$VERSION" or if git diff
--cached --quiet; then echo "no changes to commit"; else git commit -m "release:
v$VERSION"; fi) so the workflow continues safely on reruns when npm pkg set
produced no changes.

Comment on lines +105 to +109
# Pre-release detection: any version with a hyphen suffix is a pre-release.
PRERELEASE_FLAG=""
if [[ "$VERSION" == *-* ]]; then
PRERELEASE_FLAG="--prerelease"
fi
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

versions=("1.2.3" "1.2.3-rc.1" "1.2.3+build-7" "1.2.3-rc.1+build-7")
for v in "${versions[@]}"; do
  old=""
  [[ "$v" == *-* ]] && old="--prerelease"
  core="${v%%+*}"
  new=""
  [[ "$core" == *-* ]] && new="--prerelease"
  printf "%-20s old=%-13s new=%s\n" "$v" "${old:-<none>}" "${new:-<none>}"
done

Repository: znat/gitpulse

Length of output: 269


🏁 Script executed:

cat -n .github/workflows/release.yml | sed -n '100,115p'

Repository: znat/gitpulse

Length of output: 685


🏁 Script executed:

cat -n .github/workflows/release.yml | sed -n '40,60p'

Repository: znat/gitpulse

Length of output: 985


Fix prerelease detection to ignore build metadata.

The version validation at line 48 correctly allows SemVer build metadata (e.g., 1.2.3+build-7), but the prerelease detection at line 107 incorrectly flags any version containing - as a prerelease. This means 1.2.3+build-7 would be marked as prerelease even though it is not.

Suggested patch
          VERSION="${{ inputs.version }}"
          # Pre-release detection: any version with a hyphen suffix is a pre-release.
          PRERELEASE_FLAG=""
-         if [[ "$VERSION" == *-* ]]; then
+         CORE_VERSION="${VERSION%%+*}" # strip optional +buildmetadata
+         if [[ "$CORE_VERSION" == *-* ]]; then
            PRERELEASE_FLAG="--prerelease"
          fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 105 - 109, The prerelease
detection wrongly treats any '-' anywhere in VERSION as prerelease (so versions
like 1.2.3+build-7 are misclassified); fix it by testing for a hyphen only in
the part of VERSION before any build metadata by first stripping anything after
'+' (e.g., assign BASE from VERSION up to the first '+' using shell parameter
expansion) and then set PRERELEASE_FLAG="--prerelease" only if that BASE
contains a '-' ; update the block that sets PRERELEASE_FLAG and reference the
VERSION and PRERELEASE_FLAG variables so the logic checks BASE (stripped of
build metadata) for a prerelease suffix.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

⛔ CodeRabbit does not have permission to push changes to this repository. Please ensure CodeRabbit has Contents: Read and write permission.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 2, 2026

Greptile Summary

This PR adds a workflow_dispatch-triggered release workflow that validates semver input, runs tests, bumps all workspace package.json files, commits and pushes to main, creates versioned and major-pointer tags, and publishes a GitHub Release with auto-generated notes. The README gains a matching "Releasing (maintainers)" section.

Two P2 issues worth addressing before a real production release:

  • Shell injection: ${{ inputs.version }} is interpolated directly into every run: shell script; the regex guard runs after the expression is already expanded, so a crafted value can execute shell commands before validation fires. Use env: blocks to pass the input safely.
  • Non-atomic push: if the commit push to main succeeds but the tag pushes fail, a workflow retry will error at the commit step (nothing to commit), leaving tags and the GitHub Release missing without a clean recovery path.

Confidence Score: 4/5

Safe to merge; both findings are P2 and the workflow isn't yet used in production.

Only P2 issues found — shell injection is mitigated in practice by the write-access requirement for workflow_dispatch, and the partial-push risk is an edge case. No P0 or P1 defects identified.

.github/workflows/release.yml — address shell injection pattern and push atomicity before the workflow is used for real releases.

Security Review

  • Shell injection (.github/workflows/release.yml, lines 46, 78, 87, 94, 104): ${{ inputs.version }} is expanded by GitHub's template engine into run: shell scripts before the shell executes them. A value containing shell metacharacters can execute arbitrary commands in the runner context, which holds a contents: write GITHUB_TOKEN. Only users with write access to the repo can trigger workflow_dispatch, but the pattern is contrary to GitHub's hardening guidelines. Mitigation: pass the input via an env: block.

Important Files Changed

Filename Overview
.github/workflows/release.yml New one-click release workflow; two issues: shell injection via direct template expression interpolation in run steps, and a partial-push failure scenario that makes the workflow un-retryable without manual intervention.
README.md Adds a "Releasing (maintainers)" section documenting the workflow steps and the branch-protection gotcha; documentation looks accurate and complete.

Sequence Diagram

sequenceDiagram
    actor Maintainer
    participant GHA as GitHub Actions
    participant Repo as GitHub Repo (main)
    participant GHR as GitHub Releases

    Maintainer->>GHA: workflow_dispatch (version=1.0.0)
    GHA->>GHA: Reject if not dispatched from main
    GHA->>GHA: Validate semver + check tag doesn't exist
    GHA->>GHA: yarn typecheck + yarn test (gate)
    GHA->>GHA: npm pkg set version across workspaces
    GHA->>Repo: git push HEAD:main (bump commit)
    GHA->>Repo: git push v1.0.0 (immutable tag)
    GHA->>Repo: git push v1 --force (major pointer)
    GHA->>GHR: gh release create v1.0.0 --generate-notes
Loading

Reviews (1): Last reviewed commit: "fix(release): reject non-main dispatch +..." | Re-trigger Greptile

Comment on lines +44 to +46
- name: Validate version + ensure tag does not already exist
run: |
VERSION="${{ inputs.version }}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 security Shell injection via ${{ inputs.version }} in run: steps

GitHub's template engine expands ${{ inputs.version }} directly into the shell script text before the shell processes it. A value like 1.0.0" && malicious_cmd; echo " would cause the assignment VERSION="1.0.0" && malicious_cmd; echo "", executing malicious_cmd before the regex check can block it. The fix is to use env: blocks so the value arrives as a shell variable, never as shell text.

Apply the same env: pattern to every subsequent step that sets VERSION="${{ inputs.version }}" (Bump, Tag, Push, Release steps).

Suggested change
- name: Validate version + ensure tag does not already exist
run: |
VERSION="${{ inputs.version }}"
- name: Validate version + ensure tag does not already exist
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
VERSION="$INPUT_VERSION"

Comment on lines +92 to +98
- name: Push commit + tags
run: |
VERSION="${{ inputs.version }}"
MAJOR="v${VERSION%%.*}"
git push origin HEAD:main
git push origin "v$VERSION"
git push origin "$MAJOR" --force
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Partial push failure leaves workflow un-retryable

If git push origin HEAD:main succeeds (line 96) but the subsequent tag push on line 97 or 98 fails, the version-bump commit is already on main with no tags and no GitHub Release. A retry of the full workflow will then fail at the "Bump package.json" step because npm pkg set writes the same value, leaving nothing for git commit — error: nothing to commit. At that point the operator must manually push the two tags and create the release by hand. Consider consolidating the pushes into a single git push with --follow-tags, or adding explicit error handling / recovery instructions in the workflow.

@znat znat merged commit fdce44d into main May 2, 2026
2 checks passed
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.

1 participant