Skip to content

feat(ci): semantic-release automated publishing pipeline#22

Merged
himerus merged 2 commits intodevfrom
feature/semantic-release-automated-publish
Mar 10, 2026
Merged

feat(ci): semantic-release automated publishing pipeline#22
himerus merged 2 commits intodevfrom
feature/semantic-release-automated-publish

Conversation

@himerus
Copy link
Copy Markdown
Contributor

@himerus himerus commented Mar 10, 2026

Summary

  • Replaces manual tag-based publish.yml with full semantic-release pipeline (release.yml)
  • Adds .releaserc.json with conventional-commits analysis, CHANGELOG.md generation, npm publish, GitHub Release, and Discord webhook notification
  • Updates CONTRIBUTING.md with release automation docs and version bump rules
  • Adds release and release:dry-run npm scripts

What happens on merge to main

  1. semantic-release analyzes conventional commits since last release
  2. Determines version bump: feat → minor, fix/perf/refactor → patch, BREAKING CHANGE → major
  3. Updates CHANGELOG.md with categorized notes
  4. Bumps package.json version
  5. Publishes to npm
  6. Creates a GitHub Release with full notes
  7. Posts release embed to Discord (if DISCORD_RELEASE_WEBHOOK secret is set)

Required secrets

  • NPM_TOKEN — already configured
  • DISCORD_RELEASE_WEBHOOK — optional, for Discord notifications

Test plan

  • pnpm run type-check passes
  • All 1317 tests pass
  • semantic-release --dry-run --no-ci validates config (all plugins load)
  • CI checks pass on this PR
  • After merge to main, first feat commit triggers v0.1.1 release

Closes #21

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Implemented automated semantic versioning and release management to the main branch with optional Discord webhook notifications for new releases.
  • Documentation

    • Updated contribution guidelines with detailed information about the automated release workflow, version bump rules, and CI/CD pipeline.
  • Chores

    • Replaced the manual publish workflow with an automated release workflow using semantic versioning conventions.

…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>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 10, 2026

Warning

Rate limit exceeded

@himerus has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 25 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6a527179-edef-4969-afe9-c87dfa9196ff

📥 Commits

Reviewing files that changed from the base of the PR and between 93673a7 and 8fec4aa.

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

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'version'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

Replaced the manual publish workflow with an automated release workflow using semantic-release. The new setup analyzes conventional commits to determine version bumps, publishes to npm, creates GitHub releases, updates CHANGELOG.md, and optionally notifies Discord on successful release.

Changes

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.

Copy link
Copy Markdown

@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: 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 | 🟡 Minor

PR checklist item may be outdated with automated changelog generation.

Line 246 instructs contributors to add a CHANGELOG.md entry 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

📥 Commits

Reviewing files that changed from the base of the PR and between e75813b and 93673a7.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • .github/workflows/publish.yml
  • .github/workflows/release.yml
  • .releaserc.json
  • CONTRIBUTING.md
  • package.json
💤 Files with no reviewable changes (1)
  • .github/workflows/publish.yml

Comment on lines +52 to +56
- name: Notify Discord
if: success()
run: |
# Read version from package.json (semantic-release updates it)
VERSION=$(node -p "require('./package.json').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.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +52 to +75
- 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
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment thread .releaserc.json
Comment on lines +49 to +55
"@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}"
}
Copy link
Copy Markdown

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
# 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.json

Repository: 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

  1. Update the lockfile in prepare (without changing installed deps):
    • pnpm install --lockfile-only (this updates only pnpm-lock.yaml). [3]
  2. Commit the updated lockfile with @semantic-release/git by adding it to assets. [2]
  3. Plugin order matters: @semantic-release/npm must run before @semantic-release/git so 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).

Comment thread .releaserc.json
Comment on lines +57 to +64
[
"@semantic-release/github",
{
"successComment": false,
"failComment": false,
"releasedLabels": ["released"]
}
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 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>
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