feat(ci): semantic-release automated publishing pipeline#22
Conversation
…elogs Replaces the manual tag-based publish workflow with semantic-release. On merge to main, automatically determines version from conventional commits, updates CHANGELOG.md, publishes to npm, creates a GitHub Release, and posts to Discord. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
Note
|
| Cohort / File(s) | Summary |
|---|---|
Workflow Migration .github/workflows/publish.yml, .github/workflows/release.yml |
Removed manual publish workflow and replaced with new semantic-release-based release workflow. Introduces permissions configuration, semantic-release integration, and conditional Discord webhook notification on success. |
Semantic Release Configuration .releaserc.json, package.json |
Added semantic-release configuration file with commit analyzer, release notes generator, changelog generation, npm/git/GitHub publishing plugins. Added npm scripts (release, release:dry-run) and semantic-release devDependencies to package.json. |
Documentation CONTRIBUTING.md |
Expanded CI Pipeline section with new release workflow entry. Added Automated Releases section explaining semantic-release flow, version bump rules table, and updated testing guidance for the new automated process. |
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~22 minutes
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | The title 'feat(ci): semantic-release automated publishing pipeline' is concise, specific, and accurately describes the main change—implementing semantic-release for automated npm publishing. |
| Description check | ✅ Passed | The description comprehensively covers the semantic-release pipeline implementation, explains the workflow steps, lists required secrets, and includes a detailed test plan addressing all core requirements. |
| Linked Issues check | ✅ Passed | All primary objectives from issue #21 are met: semantic version bumping, GitHub Actions workflow on main branch, full quality gates, npm publishing, GitHub Release creation, CHANGELOG generation, and safety measures via semantic-release configuration. |
| Out of Scope Changes check | ✅ Passed | All changes are directly aligned with the linked issue requirements: workflow replacement, semantic-release configuration, documentation updates, and npm scripts addition are all within the scope of implementing automated semantic-release publishing. |
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing Touches
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
feature/semantic-release-automated-publish
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
CONTRIBUTING.md (1)
246-246:⚠️ Potential issue | 🟡 MinorPR checklist item may be outdated with automated changelog generation.
Line 246 instructs contributors to add a
CHANGELOG.mdentry under[Unreleased], but semantic-release now auto-generates changelog entries from conventional commits. Consider updating this to clarify that manual changelog entries are no longer required, or remove this checklist item.📝 Suggested update
-- [ ] `CHANGELOG.md` entry added under `[Unreleased]` +- [ ] Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) format (changelog is auto-generated)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CONTRIBUTING.md` at line 246, Update the PR checklist item "- [ ] `CHANGELOG.md` entry added under `[Unreleased]`" so it reflects current automated changelog generation: either remove the checklist line or change its wording to state that changelog entries are auto-generated from conventional commits (no manual `CHANGELOG.md` entry required), and optionally add guidance to include a conventional-commit formatted message if manual override is needed.
🤖 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 52-56: The Notify Discord step currently uses if: success() and
reads VERSION which can be unchanged when semantic-release exits 0 without a
release; modify the Release step (give it id: release) to capture OLD_VERSION
(read package.json), run pnpm exec semantic-release, read NEW_VERSION, and if
they differ write released=true and version=NEW_VERSION to GITHUB_OUTPUT; then
change the Notify Discord step to run only when steps.release.outputs.released
== 'true' (and when env.DISCORD_RELEASE_WEBHOOK is set) instead of using
success(), and read the released version from steps.release.outputs.version
rather than re-reading package.json.
- Around line 52-75: The workflow step "Notify Discord" directly interpolates
the secret with ${{ secrets.DISCORD_RELEASE_WEBHOOK }} inside the run script;
change it to use an environment variable mapping so the secret is not rendered
into the script. Update the "Notify Discord" step to set env:
DISCORD_RELEASE_WEBHOOK: ${{ secrets.DISCORD_RELEASE_WEBHOOK }} (and leave
VERSION computed in the shell), then inside the run block reference the shell
variable "$DISCORD_RELEASE_WEBHOOK" for the if check (if [ -n
"$DISCORD_RELEASE_WEBHOOK" ]) and for curl's target URL, removing all direct
uses of ${{ secrets.DISCORD_RELEASE_WEBHOOK }} from the script. Ensure quotes
around "$DISCORD_RELEASE_WEBHOOK" are kept when used.
In @.releaserc.json:
- Around line 57-64: The current semantic-release GitHub plugin config disables
release failure comments by setting "failComment": false; update the plugin
options in the array (the object containing "successComment", "failComment",
"releasedLabels") to re-enable failure visibility—either remove the
"failComment" key or set "failComment": true so failed releases post comments on
PRs/issues, or alternatively ensure an external alerting path is in place if you
intentionally keep it false.
- Around line 49-55: Insert the `@semantic-release/exec` plugin immediately after
"@semantic-release/npm" in the plugins array and configure its prepareCmd to run
"pnpm install --lockfile-only" so pnpm-lock.yaml is regenerated after the
package.json version bump; specifically add an entry for
"@semantic-release/exec" with a prepareCmd of "pnpm install --lockfile-only"
before the existing ["@semantic-release/git", {...}] block (the commit already
references "pnpm-lock.yaml" in the git plugin assets).
---
Outside diff comments:
In `@CONTRIBUTING.md`:
- Line 246: Update the PR checklist item "- [ ] `CHANGELOG.md` entry added under
`[Unreleased]`" so it reflects current automated changelog generation: either
remove the checklist line or change its wording to state that changelog entries
are auto-generated from conventional commits (no manual `CHANGELOG.md` entry
required), and optionally add guidance to include a conventional-commit
formatted message if manual override is needed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c738561a-e1a6-4faa-a948-2318cfa3d3d3
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (5)
.github/workflows/publish.yml.github/workflows/release.yml.releaserc.jsonCONTRIBUTING.mdpackage.json
💤 Files with no reviewable changes (1)
- .github/workflows/publish.yml
| - name: Notify Discord | ||
| if: success() | ||
| run: | | ||
| # Read version from package.json (semantic-release updates it) | ||
| VERSION=$(node -p "require('./package.json').version") |
There was a problem hiding this comment.
Discord notification may fire even when no release occurred.
The success() condition passes if the release step exits 0, which semantic-release does even when no release is made (e.g., no releasable commits). This would post a notification with the old version number.
Consider checking if a release actually occurred by comparing versions or using semantic-release output.
💡 Suggested approach to detect actual release
- name: Release
id: release
run: |
OLD_VERSION=$(node -p "require('./package.json').version")
pnpm exec semantic-release
NEW_VERSION=$(node -p "require('./package.json').version")
if [ "$OLD_VERSION" != "$NEW_VERSION" ]; then
echo "released=true" >> $GITHUB_OUTPUT
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Notify Discord
if: steps.release.outputs.released == 'true' && env.DISCORD_RELEASE_WEBHOOK != ''
# ... rest of step🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/release.yml around lines 52 - 56, The Notify Discord step
currently uses if: success() and reads VERSION which can be unchanged when
semantic-release exits 0 without a release; modify the Release step (give it id:
release) to capture OLD_VERSION (read package.json), run pnpm exec
semantic-release, read NEW_VERSION, and if they differ write released=true and
version=NEW_VERSION to GITHUB_OUTPUT; then change the Notify Discord step to run
only when steps.release.outputs.released == 'true' (and when
env.DISCORD_RELEASE_WEBHOOK is set) instead of using success(), and read the
released version from steps.release.outputs.version rather than re-reading
package.json.
| - name: Notify Discord | ||
| if: success() | ||
| run: | | ||
| # Read version from package.json (semantic-release updates it) | ||
| VERSION=$(node -p "require('./package.json').version") | ||
| REPO_URL="https://github.com/${{ github.repository }}" | ||
|
|
||
| # Only post if DISCORD_RELEASE_WEBHOOK is set | ||
| if [ -n "${{ secrets.DISCORD_RELEASE_WEBHOOK }}" ]; then | ||
| curl -s -H "Content-Type: application/json" \ | ||
| -d "{ | ||
| \"embeds\": [{ | ||
| \"title\": \"HELiXiR v${VERSION} Released\", | ||
| \"url\": \"${REPO_URL}/releases/tag/v${VERSION}\", | ||
| \"color\": 5814783, | ||
| \"fields\": [ | ||
| { \"name\": \"npm\", \"value\": \"\`npm install helixir@${VERSION}\`\", \"inline\": true }, | ||
| { \"name\": \"GitHub\", \"value\": \"[Release Notes](${REPO_URL}/releases/tag/v${VERSION})\", \"inline\": true } | ||
| ], | ||
| \"footer\": { \"text\": \"Automated release via semantic-release\" } | ||
| }] | ||
| }" \ | ||
| "${{ secrets.DISCORD_RELEASE_WEBHOOK }}" | ||
| fi |
There was a problem hiding this comment.
Secret value exposed in workflow logs via direct interpolation.
The secret DISCORD_RELEASE_WEBHOOK is directly interpolated into the shell script on line 60 and 74. While GitHub masks known secret values, direct interpolation in shell conditionals and curl commands can expose secrets if the masking fails or in error messages.
Use environment variables instead to avoid direct secret interpolation in shell code:
🔒 Proposed fix using environment variables
- name: Notify Discord
- if: success()
+ if: success() && env.DISCORD_RELEASE_WEBHOOK != ''
+ env:
+ DISCORD_RELEASE_WEBHOOK: ${{ secrets.DISCORD_RELEASE_WEBHOOK }}
run: |
# Read version from package.json (semantic-release updates it)
VERSION=$(node -p "require('./package.json').version")
REPO_URL="https://github.com/${{ github.repository }}"
- # Only post if DISCORD_RELEASE_WEBHOOK is set
- if [ -n "${{ secrets.DISCORD_RELEASE_WEBHOOK }}" ]; then
- curl -s -H "Content-Type: application/json" \
- -d "{
- \"embeds\": [{
- \"title\": \"HELiXiR v${VERSION} Released\",
- \"url\": \"${REPO_URL}/releases/tag/v${VERSION}\",
- \"color\": 5814783,
- \"fields\": [
- { \"name\": \"npm\", \"value\": \"\`npm install helixir@${VERSION}\`\", \"inline\": true },
- { \"name\": \"GitHub\", \"value\": \"[Release Notes](${REPO_URL}/releases/tag/v${VERSION})\", \"inline\": true }
- ],
- \"footer\": { \"text\": \"Automated release via semantic-release\" }
- }]
- }" \
- "${{ secrets.DISCORD_RELEASE_WEBHOOK }}"
- fi
+ curl -sf -H "Content-Type: application/json" \
+ -d "{
+ \"embeds\": [{
+ \"title\": \"HELiXiR v${VERSION} Released\",
+ \"url\": \"${REPO_URL}/releases/tag/v${VERSION}\",
+ \"color\": 5814783,
+ \"fields\": [
+ { \"name\": \"npm\", \"value\": \"\`npm install helixir@${VERSION}\`\", \"inline\": true },
+ { \"name\": \"GitHub\", \"value\": \"[Release Notes](${REPO_URL}/releases/tag/v${VERSION})\", \"inline\": true }
+ ],
+ \"footer\": { \"text\": \"Automated release via semantic-release\" }
+ }]
+ }" \
+ "$DISCORD_RELEASE_WEBHOOK"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/release.yml around lines 52 - 75, The workflow step
"Notify Discord" directly interpolates the secret with ${{
secrets.DISCORD_RELEASE_WEBHOOK }} inside the run script; change it to use an
environment variable mapping so the secret is not rendered into the script.
Update the "Notify Discord" step to set env: DISCORD_RELEASE_WEBHOOK: ${{
secrets.DISCORD_RELEASE_WEBHOOK }} (and leave VERSION computed in the shell),
then inside the run block reference the shell variable
"$DISCORD_RELEASE_WEBHOOK" for the if check (if [ -n "$DISCORD_RELEASE_WEBHOOK"
]) and for curl's target URL, removing all direct uses of ${{
secrets.DISCORD_RELEASE_WEBHOOK }} from the script. Ensure quotes around
"$DISCORD_RELEASE_WEBHOOK" are kept when used.
| "@semantic-release/npm", | ||
| [ | ||
| "@semantic-release/git", | ||
| { | ||
| "assets": ["package.json", "CHANGELOG.md", "pnpm-lock.yaml"], | ||
| "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if there's any custom npm plugin config that handles pnpm
cat .releaserc.json | jq '.plugins[] | select(type == "array" and .[0] == "@semantic-release/npm")'
# Check if there's a prepare script that might update the lockfile
cat package.json | jq '.scripts.prepare // empty, .scripts.prepublishOnly // empty'Repository: bookedsolidtech/helixir
Length of output: 104
🏁 Script executed:
cat .releaserc.jsonRepository: bookedsolidtech/helixir
Length of output: 2395
🏁 Script executed:
cat package.json | jq '.scripts | keys as $keys | to_entries[] | select(.value | contains("pnpm")) | {script: .key, command: .value}'Repository: bookedsolidtech/helixir
Length of output: 143
🌐 Web query:
pnpm lockfile automatic update npm publish semantic-release
💡 Result:
npm publish (and @semantic-release/npm) won’t update pnpm-lock.yaml for you—it uses the npm CLI to bump package.json and publish. If you want the pnpm lockfile to be automatically updated and committed during a semantic-release run, do it in the prepare phase, then commit it with @semantic-release/git. [1][2]
What to do
- Update the lockfile in
prepare(without changing installed deps):pnpm install --lockfile-only(this updates onlypnpm-lock.yaml). [3]
- Commit the updated lockfile with
@semantic-release/gitby adding it toassets. [2] - Plugin order matters:
@semantic-release/npmmust run before@semantic-release/gitso the git plugin can commit the versioned files. [2]
Example release.config.cjs
module.exports = {
plugins: [
["@semantic-release/npm"],
["@semantic-release/exec", {
// run after version bump in prepare
prepareCmd: "pnpm install --lockfile-only"
}],
["@semantic-release/git", {
assets: ["package.json", "pnpm-lock.yaml", "CHANGELOG.md"],
message: "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}]
]
};If you prefer npm hook scripts instead of @semantic-release/exec, semantic-release notes you can use the postversion hook to update files during @semantic-release/npm’s prepare step, then commit them via @semantic-release/git. [1]
Sources: semantic-release FAQ (prepare / hooks) [1], @semantic-release/git docs (assets + order) [2], pnpm install --lockfile-only behavior [3].
Add pnpm install --lockfile-only to prepare phase to update pnpm-lock.yaml with the new version.
The @semantic-release/npm plugin bumps package.json but does not automatically update pnpm-lock.yaml. Currently, pnpm-lock.yaml will be committed with a stale version reference. Add a prepare script using @semantic-release/exec to regenerate the lockfile after the version bump:
[
"@semantic-release/exec",
{
"prepareCmd": "pnpm install --lockfile-only"
}
]Insert this plugin between @semantic-release/npm and @semantic-release/git in the plugins array so the updated lockfile is included in the git commit.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.releaserc.json around lines 49 - 55, Insert the `@semantic-release/exec`
plugin immediately after "@semantic-release/npm" in the plugins array and
configure its prepareCmd to run "pnpm install --lockfile-only" so pnpm-lock.yaml
is regenerated after the package.json version bump; specifically add an entry
for "@semantic-release/exec" with a prepareCmd of "pnpm install --lockfile-only"
before the existing ["@semantic-release/git", {...}] block (the commit already
references "pnpm-lock.yaml" in the git plugin assets).
| [ | ||
| "@semantic-release/github", | ||
| { | ||
| "successComment": false, | ||
| "failComment": false, | ||
| "releasedLabels": ["released"] | ||
| } | ||
| ] |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider enabling failComment for release failure visibility.
Disabling failComment means release failures won't be reported on related PRs/issues. While this reduces noise, it may make debugging failed releases harder. Consider keeping failComment enabled or ensuring you have alternative alerting (e.g., GitHub Actions failure notifications).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.releaserc.json around lines 57 - 64, The current semantic-release GitHub
plugin config disables release failure comments by setting "failComment": false;
update the plugin options in the array (the object containing "successComment",
"failComment", "releasedLabels") to re-enable failure visibility—either remove
the "failComment" key or set "failComment": true so failed releases post
comments on PRs/issues, or alternatively ensure an external alerting path is in
place if you intentionally keep it false.
Adds manual trigger with optional dry-run mode so failed releases can be safely re-triggered without re-merging. Semantic-release is idempotent — re-running on the same commit is always safe. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
publish.ymlwith full semantic-release pipeline (release.yml).releaserc.jsonwith conventional-commits analysis, CHANGELOG.md generation, npm publish, GitHub Release, and Discord webhook notificationreleaseandrelease:dry-runnpm scriptsWhat happens on merge to main
feat→ minor,fix/perf/refactor→ patch,BREAKING CHANGE→ majorCHANGELOG.mdwith categorized notespackage.jsonversionDISCORD_RELEASE_WEBHOOKsecret is set)Required secrets
NPM_TOKEN— already configuredDISCORD_RELEASE_WEBHOOK— optional, for Discord notificationsTest plan
pnpm run type-checkpassessemantic-release --dry-run --no-civalidates config (all plugins load)featcommit triggers v0.1.1 releaseCloses #21
🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Documentation
Chores