chore: update publish#93
Conversation
🤖 Augment PR SummarySummary: This PR replaces the old manual release automation with a new unified publish workflow for npm and GitHub releases. Changes:
Technical Notes: Stable releases use a GitHub App token for pushing commit/tag; prereleases don’t modify the repository state. 🤖 Was this summary useful? React with 👍 or 👎 |
|
|
||
| concurrency: | ||
| group: publish | ||
| cancel-in-progress: ${{ github.event_name == 'push' }} |
There was a problem hiding this comment.
concurrency.group: publish combined with cancel-in-progress for push runs means a new push can cancel an in-flight manual channel=latest release run, which can leave a partially completed release sequence.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version-file: ".nvmrc" | ||
| cache: "npm" |
There was a problem hiding this comment.
|
|
||
| - name: Publish stable release to npm | ||
| if: github.event_name == 'workflow_dispatch' && inputs.channel == 'latest' | ||
| run: npm publish --tag latest --provenance |
There was a problem hiding this comment.
The stable path publishes to npm before the version bump commit/tag is created and pushed; if the later git push/tag/release steps fail, npm can contain a version that isn’t represented in git and reruns may hit “version already exists”.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
📝 WalkthroughWalkthroughThe PR replaces the manual release workflow with a new CI/CD pipeline supporting two release channels: prerelease ( Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 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/publish.yaml:
- Around line 131-145: The workflow publishes to npm before pushing the version
commit and tag, risking an irreversible publish without a corresponding
commit/tag on main; change the sequence so the steps "Commit version bump"
(creates v${VERSION}) and "Push version commit and tag" (git push origin main
--follow-tags) run before the "Publish stable release to npm" (npm publish --tag
latest --provenance), or alternatively add a guard step at the top that verifies
the runner/bot can push to main (e.g., attempt a dry-run git push or check
required permissions) and fail fast if push will be blocked; ensure the step
names "Commit version bump", "Push version commit and tag", and the npm publish
invocation are updated accordingly so publish is the last irreversible action.
- Around line 152-165: The awk matcher that builds BODY (the multi-line awk
block using VERSION) is too narrow and only matches "## [ver]" or "## vver";
update that awk pattern to accept common header variants (e.g., "## ver", "##
[ver]", "## vver" and headers with an optional date or suffix like " -
YYYY-MM-DD") so the block correctly sets found when encountering headers for
TAG/VERSION; change the regex inside the awk script that checks "^## \\[" ver
"\\]" || "^## v" ver to a single broader test that allows optional brackets,
optional leading "v", optional surrounding whitespace, and optional trailing
date/suffix, and keep using the same BODY, VERSION, TAG variables for subsequent
gh release create invocation.
- Around line 3-9: The push trigger for the publish workflow currently watches
package.json and package-lock.json and will retrigger when the release job
commits the version bump; update the workflow to skip release-commit pushes by
adding a guard: in the publish workflow's jobs add an `if` check that ignores
runs when the commit author or actor is the GitHub Actions bot (e.g.,
github.actor == 'github-actions[bot]') or when the head commit message contains
a skip token (e.g., "[skip ci]"); modify the release step that performs the
version bump (the job that pushes the new version) to either use a skip token in
its commit message or set the actor so the guard matches, ensuring the stable
release push does not re-enter the prerelease path.
- Around line 30-32: The current concurrency setup uses a single group named
"publish" with cancel-in-progress true for pushes, which allows a push run to
cancel an in-progress workflow_dispatch release; change the concurrency grouping
so runs are isolated by event/channel (e.g., include the event and
inputs.channel in concurrency.group) or disable cancel-in-progress for
workflow_dispatch releases so a manually-triggered "latest" publish isn't
cancelled by a push; update the concurrency.group and cancel-in-progress
expression (the concurrency.group and cancel-in-progress keys in the workflow)
to reflect this separation.
- Around line 80-100: Replace the semver comparison in the inline Node script
that computes BASE so it uses semver.gte instead of semver.gt; specifically, in
the Node block that defines next, latest and nextBase (the code that currently
does console.log(semver.gt(latest, nextBase) ? latest : next)), change that call
to semver.gte to ensure BASE becomes latest when latest === nextBase, then keep
the existing npm version "$BASE" and prerelease steps unchanged.
🪄 Autofix (Beta)
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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 09bacff4-4624-4b0a-abba-b5d8cc7f2b6c
📒 Files selected for processing (4)
.github/workflows/manual-release.yaml.github/workflows/publish.yamlREADME.mdpackage.json
💤 Files with no reviewable changes (1)
- .github/workflows/manual-release.yaml
| on: | ||
| push: | ||
| branches: [main] | ||
| paths: | ||
| - "src/**" | ||
| - "package.json" | ||
| - "package-lock.json" |
There was a problem hiding this comment.
Stable release commit will re-trigger the workflow as a next prerelease.
The push trigger watches package.json and package-lock.json. After a latest run commits the version bump and pushes to main (line 145), the resulting push matches this filter and spawns a new run that goes down the prerelease path — immediately publishing e.g. 1.13.2-next.0 right after 1.13.2 stable. If that’s intentional (auto‑resume next track), consider documenting it; otherwise, guard the push trigger to skip release commits, e.g. by checking the commit author/message in the job if, or use [skip ci] in the commit message on line 140.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/publish.yaml around lines 3 - 9, The push trigger for the
publish workflow currently watches package.json and package-lock.json and will
retrigger when the release job commits the version bump; update the workflow to
skip release-commit pushes by adding a guard: in the publish workflow's jobs add
an `if` check that ignores runs when the commit author or actor is the GitHub
Actions bot (e.g., github.actor == 'github-actions[bot]') or when the head
commit message contains a skip token (e.g., "[skip ci]"); modify the release
step that performs the version bump (the job that pushes the new version) to
either use a skip token in its commit message or set the actor so the guard
matches, ensuring the stable release push does not re-enter the prerelease path.
| concurrency: | ||
| group: publish | ||
| cancel-in-progress: ${{ github.event_name == 'push' }} |
There was a problem hiding this comment.
Push events can cancel an in-progress stable release.
concurrency.group is a single publish group and cancel-in-progress evaluates to true whenever the new run is a push. If a maintainer triggers a workflow_dispatch with channel=latest and, while it is running, any push to main that touches src/**, package.json, or package-lock.json lands (including the release commit from another branch or a concurrent merge), the incoming push run will cancel the in-progress stable release — potentially between npm publish --tag latest and the git push/gh release create steps, leaving npm and git out of sync.
Consider either separating the groups by channel or never cancelling when a latest release is in flight:
| concurrency: | |
| group: publish | |
| cancel-in-progress: ${{ github.event_name == 'push' }} | |
| concurrency: | |
| group: publish-${{ github.event_name == 'workflow_dispatch' && inputs.channel || 'next' }} | |
| cancel-in-progress: ${{ github.event_name == 'push' }} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/publish.yaml around lines 30 - 32, The current concurrency
setup uses a single group named "publish" with cancel-in-progress true for
pushes, which allows a push run to cancel an in-progress workflow_dispatch
release; change the concurrency grouping so runs are isolated by event/channel
(e.g., include the event and inputs.channel in concurrency.group) or disable
cancel-in-progress for workflow_dispatch releases so a manually-triggered
"latest" publish isn't cancelled by a push; update the concurrency.group and
cancel-in-progress expression (the concurrency.group and cancel-in-progress keys
in the workflow) to reflect this separation.
| - name: Derive next prerelease version | ||
| if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.channel == 'next') | ||
| run: | | ||
| NEXT_VER=$(npm view bmad-method-test-architecture-enterprise@next version 2>/dev/null || echo "") | ||
| LATEST_VER=$(npm view bmad-method-test-architecture-enterprise@latest version 2>/dev/null || echo "") | ||
|
|
||
| BASE=$(node -e " | ||
| const semver = require('semver'); | ||
| const next = process.argv[1] || null; | ||
| const latest = process.argv[2] || null; | ||
| if (!next && !latest) process.exit(0); | ||
| if (!next) { console.log(latest); process.exit(0); } | ||
| if (!latest) { console.log(next); process.exit(0); } | ||
| const nextBase = next.replace(/-next\.\d+$/, ''); | ||
| console.log(semver.gt(latest, nextBase) ? latest : next); | ||
| " "$NEXT_VER" "$LATEST_VER") | ||
|
|
||
| if [ -n "$BASE" ]; then | ||
| npm version "$BASE" --no-git-tag-version --allow-same-version | ||
| fi | ||
| npm version prerelease --preid=next --no-git-tag-version |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify semver comparison logic
# Check if 1.13.1-next.6 < 1.13.1
python3 <<'EOF'
import subprocess
import json
# Test semver comparisons in Node.js
test_script = """
const semver = require('semver');
// Test 1: Is 1.13.1-next.6 less than 1.13.1?
console.log('1.13.1-next.6 < 1.13.1:', semver.lt('1.13.1-next.6', '1.13.1'));
// Test 2: Verify gt vs gte with equal versions
const latest = '1.13.1';
const nextBase = '1.13.1';
console.log('semver.gt("1.13.1", "1.13.1"):', semver.gt(latest, nextBase));
console.log('semver.gte("1.13.1", "1.13.1"):', semver.gte(latest, nextBase));
// Test 3: Verify the logic flow when latest equals nextBase
if (!semver.gt(latest, nextBase)) {
console.log('With gt: BASE would be set to next (1.13.1-next.5)');
}
if (semver.gte(latest, nextBase)) {
console.log('With gte: BASE would be set to latest (1.13.1)');
}
// Test 4: Check other comparisons
console.log('semver.gt("1.13.2", "1.13.1"):', semver.gt('1.13.2', '1.13.1'));
console.log('semver.lt("1.13.0", "1.13.1"):', semver.lt('1.13.0', '1.13.1'));
"""
console.log("=== Testing semver comparison logic ===")
print(test_script)
EOFRepository: bmad-code-org/bmad-method-test-architecture-enterprise
Length of output: 212
🏁 Script executed:
#!/bin/bash
# Run the semver tests
node -e "
const semver = require('semver');
console.log('=== Semver Comparison Tests ===');
console.log('');
// Test 1: Is prerelease older than stable?
console.log('Test 1: Prerelease vs stable');
console.log('semver.lt(\"1.13.1-next.6\", \"1.13.1\"):', semver.lt('1.13.1-next.6', '1.13.1'));
console.log('');
// Test 2: gt vs gte with equal base versions
console.log('Test 2: gt vs gte comparison');
const latest = '1.13.1';
const nextBase = '1.13.1';
console.log('semver.gt(\"' + latest + '\", \"' + nextBase + '\"):', semver.gt(latest, nextBase));
console.log('semver.gte(\"' + latest + '\", \"' + nextBase + '\"):', semver.gte(latest, nextBase));
console.log('');
// Test 3: Impact on BASE selection
console.log('Test 3: Impact on BASE selection logic');
console.log('When latest=\"1.13.1\" and nextBase=\"1.13.1\":');
console.log(' gt(latest, nextBase) returns:', semver.gt(latest, nextBase), '-> BASE would be next');
console.log(' gte(latest, nextBase) returns:', semver.gte(latest, nextBase), '-> BASE would be latest');
"Repository: bmad-code-org/bmad-method-test-architecture-enterprise
Length of output: 949
🏁 Script executed:
#!/bin/bash
# Test npm version prerelease behavior
# We'll create a test to understand the behavior
npm --version
node -e "console.log('Node:', process.version)"
# Create a temporary directory to test npm version behavior
TMPDIR=$(mktemp -d)
cd "$TMPDIR"
# Initialize a test package
npm init -y > /dev/null 2>&1
echo "=== Testing npm version prerelease behavior ==="
echo ""
# Test 1: Starting from a stable version
echo "Test 1: npm version prerelease --preid=next on a clean stable"
npm pkg set version="1.13.1"
npm version prerelease --preid=next --no-git-tag-version 2>&1 | grep -oP '(?<=>)[^<]+' || npm pkg get version
# Test 2: Starting from a prerelease
npm pkg set version="1.13.1-next.5"
echo ""
echo "Test 2: npm version prerelease --preid=next on 1.13.1-next.5"
npm version prerelease --preid=next --no-git-tag-version 2>&1 | grep -oP '(?<=>)[^<]+' || npm pkg get version
# Clean up
cd /
rm -rf "$TMPDIR"Repository: bmad-code-org/bmad-method-test-architecture-enterprise
Length of output: 325
🏁 Script executed:
#!/bin/bash
# Simpler test for npm version behavior
TMPDIR=$(mktemp -d)
trap "rm -rf $TMPDIR" EXIT
cd "$TMPDIR"
npm init -y > /dev/null 2>&1
echo "=== npm version prerelease Tests ==="
echo ""
# Test 1: Clean stable to prerelease
echo "Test 1: From clean stable (1.13.1) to prerelease"
npm pkg set version="1.13.1"
echo "Before: $(npm pkg get version)"
npm version prerelease --preid=next --no-git-tag-version > /dev/null 2>&1
echo "After prerelease: $(npm pkg get version)"
echo ""
# Test 2: From prerelease to next prerelease
npm pkg set version="1.13.1-next.5"
echo "Test 2: From prerelease (1.13.1-next.5) to next prerelease"
echo "Before: $(npm pkg get version)"
npm version prerelease --preid=next --no-git-tag-version > /dev/null 2>&1
echo "After prerelease: $(npm pkg get version)"
echo ""
# Test 3: From 1.13.2-next.0 (simulating after patch bump)
npm pkg set version="1.13.2-next.0"
echo "Test 3: From patch-bumped prerelease (1.13.2-next.0)"
echo "Before: $(npm pkg get version)"
npm version prerelease --preid=next --no-git-tag-version > /dev/null 2>&1
echo "After prerelease: $(npm pkg get version)"Repository: bmad-code-org/bmad-method-test-architecture-enterprise
Length of output: 465
🏁 Script executed:
npm install semver --no-save && node -e "
const semver = require('semver');
console.log('=== Semver Comparison Verification ===');
console.log('');
// Core claim: prerelease is older than stable
console.log('Prerelease ordering:');
console.log('semver.lt(\"1.13.1-next.6\", \"1.13.1\"):', semver.lt('1.13.1-next.6', '1.13.1'));
console.log('');
// Test the specific condition: gt vs gte when versions are equal
console.log('Comparison when latest == nextBase:');
const latest = '1.13.1';
const nextBase = '1.13.1';
console.log('latest=\"' + latest + '\", nextBase=\"' + nextBase + '\"');
console.log('semver.gt(latest, nextBase):', semver.gt(latest, nextBase), '-> BASE would be: next');
console.log('semver.gte(latest, nextBase):', semver.gte(latest, nextBase), '-> BASE would be: latest');
console.log('');
// Show impact: what happens with each choice
console.log('Impact analysis:');
console.log('If BASE=next (gt returns false):');
console.log(' Starting: 1.13.1-next.5');
console.log(' After npm version prerelease --preid=next: 1.13.1-next.6');
console.log(' Semver comparison: 1.13.1-next.6 < 1.13.1:', semver.lt('1.13.1-next.6', '1.13.1'));
console.log('');
console.log('If BASE=latest (gte returns true):');
console.log(' Starting: 1.13.1');
console.log(' After npm version prerelease --preid=next: 1.13.2-next.0');
console.log(' Semver comparison: 1.13.2-next.0 >= 1.13.1:', semver.gte('1.13.2-next.0', '1.13.1'));
"Repository: bmad-code-org/bmad-method-test-architecture-enterprise
Length of output: 3284
Change semver.gt to semver.gte to prevent prerelease regression after stable promotion.
When latest equals nextBase (the common case right after promoting a stable release), semver.gt(latest, nextBase) returns false, causing BASE=next (e.g., 1.13.1-next.5). Then npm version prerelease --preid=next yields 1.13.1-next.6, which semver considers older than the now-published stable 1.13.1. Consumers on @next who have @latest installed can end up pinned to an older build, and the next channel silently stops tracking forward progress.
Using gte ensures that when latest equals nextBase, BASE becomes latest, so npm version prerelease --preid=next on 1.13.1 produces 1.13.2-next.0, correctly advancing the prerelease.
Suggested change
| - name: Derive next prerelease version | |
| if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.channel == 'next') | |
| run: | | |
| NEXT_VER=$(npm view bmad-method-test-architecture-enterprise@next version 2>/dev/null || echo "") | |
| LATEST_VER=$(npm view bmad-method-test-architecture-enterprise@latest version 2>/dev/null || echo "") | |
| BASE=$(node -e " | |
| const semver = require('semver'); | |
| const next = process.argv[1] || null; | |
| const latest = process.argv[2] || null; | |
| if (!next && !latest) process.exit(0); | |
| if (!next) { console.log(latest); process.exit(0); } | |
| if (!latest) { console.log(next); process.exit(0); } | |
| const nextBase = next.replace(/-next\.\d+$/, ''); | |
| console.log(semver.gt(latest, nextBase) ? latest : next); | |
| " "$NEXT_VER" "$LATEST_VER") | |
| if [ -n "$BASE" ]; then | |
| npm version "$BASE" --no-git-tag-version --allow-same-version | |
| fi | |
| npm version prerelease --preid=next --no-git-tag-version | |
| BASE=$(node -e " | |
| const semver = require('semver'); | |
| const next = process.argv[1] || null; | |
| const latest = process.argv[2] || null; | |
| if (!next && !latest) process.exit(0); | |
| if (!next) { console.log(latest); process.exit(0); } | |
| if (!latest) { console.log(next); process.exit(0); } | |
| const nextBase = next.replace(/-next\.\d+$/, ''); | |
| console.log(semver.gte(latest, nextBase) ? latest : next); | |
| " "$NEXT_VER" "$LATEST_VER") | |
| if [ -n "$BASE" ]; then | |
| npm version "$BASE" --no-git-tag-version --allow-same-version | |
| fi | |
| npm version prerelease --preid=next --no-git-tag-version |
Note: when BASE=latest (stable), npm version prerelease --preid=next on 1.13.1 produces 1.13.2-next.0 — please verify this matches your expectations for the first prerelease after a stable release.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/publish.yaml around lines 80 - 100, Replace the semver
comparison in the inline Node script that computes BASE so it uses semver.gte
instead of semver.gt; specifically, in the Node block that defines next, latest
and nextBase (the code that currently does console.log(semver.gt(latest,
nextBase) ? latest : next)), change that call to semver.gte to ensure BASE
becomes latest when latest === nextBase, then keep the existing npm version
"$BASE" and prerelease steps unchanged.
| - name: Publish stable release to npm | ||
| if: github.event_name == 'workflow_dispatch' && inputs.channel == 'latest' | ||
| run: npm publish --tag latest --provenance | ||
|
|
||
| - name: Commit version bump | ||
| if: github.event_name == 'workflow_dispatch' && inputs.channel == 'latest' | ||
| run: | | ||
| VERSION=$(node -p 'require("./package.json").version') | ||
| git add package.json package-lock.json .claude-plugin/marketplace.json | ||
| git commit -m "release: bump to v${VERSION}" | ||
| git tag -a "v${VERSION}" -m "Release v${VERSION}" | ||
|
|
||
| - name: Push version commit and tag | ||
| if: github.event_name == 'workflow_dispatch' && inputs.channel == 'latest' | ||
| run: git push origin main --follow-tags |
There was a problem hiding this comment.
Stable publish happens before the git push — failure leaves npm ahead of main.
For the latest channel, the order is:
npm publish --tag latest --provenance(line 133)git commit+git tag(lines 140‑141)git push origin main --follow-tags(line 145)
If step 3 fails (branch protection, missing app permissions, race with another push, etc.), the version is already live on npm with no corresponding commit/tag on main. A subsequent run will re-bump from the stale package.json version and attempt to publish a version that’s already taken, and the v${VERSION} tag never exists.
Prefer committing, tagging, and pushing first, then publishing to npm. The npm publish step is the hardest to reverse (unpublish has a 72‑hour window and deprecation is permanent), so it should be the last irreversible action. Alternatively, keep the current order but add a guard at the top of the workflow that fails fast if the bot/app cannot push to main.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/publish.yaml around lines 131 - 145, The workflow
publishes to npm before pushing the version commit and tag, risking an
irreversible publish without a corresponding commit/tag on main; change the
sequence so the steps "Commit version bump" (creates v${VERSION}) and "Push
version commit and tag" (git push origin main --follow-tags) run before the
"Publish stable release to npm" (npm publish --tag latest --provenance), or
alternatively add a guard step at the top that verifies the runner/bot can push
to main (e.g., attempt a dry-run git push or check required permissions) and
fail fast if push will be blocked; ensure the step names "Commit version bump",
"Push version commit and tag", and the npm publish invocation are updated
accordingly so publish is the last irreversible action.
| BODY=$(awk -v ver="$VERSION" ' | ||
| /^## / { | ||
| if (found) exit | ||
| if ($0 ~ "^## \\[" ver "\\]" || $0 ~ "^## v" ver) found=1 | ||
| next | ||
| } | ||
| found { print } | ||
| ' CHANGELOG.md) | ||
| if [ -z "$BODY" ]; then | ||
| echo "::warning::No CHANGELOG.md entry for $TAG — falling back to auto-generated notes" | ||
| gh release create "$TAG" --generate-notes | ||
| else | ||
| gh release create "$TAG" --notes "$BODY" | ||
| fi |
There was a problem hiding this comment.
CHANGELOG header matcher is narrow.
The awk pattern matches only ## [1.2.3] or ## v1.2.3. Common Keep-a-Changelog variants like ## 1.2.3 or ## [1.2.3] - 2026-04-23 with surrounding whitespace work for the second form but a bare ## 1.2.3 (no brackets, no v) silently falls through to the auto-generated notes path. If the project’s CHANGELOG.md style doesn’t include brackets/v, every release will use fallback notes without any warning that a matching entry actually existed in a slightly different format.
Consider broadening the pattern, or at minimum documenting the required header format in CHANGELOG.md / the release guide.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/publish.yaml around lines 152 - 165, The awk matcher that
builds BODY (the multi-line awk block using VERSION) is too narrow and only
matches "## [ver]" or "## vver"; update that awk pattern to accept common header
variants (e.g., "## ver", "## [ver]", "## vver" and headers with an optional
date or suffix like " - YYYY-MM-DD") so the block correctly sets found when
encountering headers for TAG/VERSION; change the regex inside the awk script
that checks "^## \\[" ver "\\]" || "^## v" ver to a single broader test that
allows optional brackets, optional leading "v", optional surrounding whitespace,
and optional trailing date/suffix, and keep using the same BODY, VERSION, TAG
variables for subsequent gh release create invocation.
Summary
Aligns TEA’s release flow with the current BMAD-METHOD publish model. This replaces the old Manual Release workflow that
depended on NPM_TOKEN with the shared Publish workflow pattern using npm trusted publishing and app-based stable releases.
What Changed
Notes