Skip to content
This repository was archived by the owner on Sep 26, 2026. It is now read-only.

Release

Release #3

Workflow file for this run

# Automatically releases both @jsonbored/opennextjs-cli and @jsonbored/opennextjs-mcp
# packages to npm when a version tag is pushed. Supports both NPM_TOKEN (for first release)
# and OIDC (for subsequent releases). Both packages are released with synchronized versions.
#
# **What it does:**
# 1. Builds both packages (CLI and MCP)
# 2. Extracts version from tag (e.g., v1.0.0 → 1.0.0)
# 3. Verifies both package.json versions match tag version
# 4. Generates changelog automatically using git-cliff
# 5. Publishes both packages to npm (tries OIDC first, falls back to NPM_TOKEN)
# 6. Creates GitHub Release with changelog notes
#
# **Triggers:**
# 1. Push a version tag (automatic):
# ```bash
# git tag v1.0.0
# git push origin v1.0.0
# ```
# 2. Manual workflow_dispatch (button in GitHub Actions dashboard):
# - Go to Actions → Release → Run workflow
# - Enter version (e.g., "0.1.0") or leave empty to use package.json version
# - Click "Run workflow"
# - Workflow will: create tag → generate changelogs → commit → push tag → publish to npm
#
# **Prerequisites:**
# - package.json version must match tag version (e.g., 1.0.0)
#
# **First Release (NPM_TOKEN):**
# - Requires NPM_TOKEN secret in GitHub repository
# - Create token: npmjs.com → Account Settings → Access Tokens → Generate New Token (Automation)
# - Add secret: GitHub repo → Settings → Secrets and variables → Actions → New repository secret
# - Name: NPM_TOKEN
# - Value: Your npm automation token
#
# **Subsequent Releases (OIDC - Recommended):**
# - After first release, set up OIDC trusted publishing:
# 1. Go to npmjs.com → Account Settings → Access Tokens → Automation
# 2. Click "Add GitHub Actions" or "Configure" next to "Trusted Publishers"
# 3. Select repository: JSONbored/opennextjs-cli
# 4. Select workflow: .github/workflows/release.yml
# 5. Save
# - Once OIDC is configured, NPM_TOKEN is no longer needed
# - More secure than token-based authentication
# - Automatic token rotation
name: Release
on:
push:
tags:
- v*.*.* # Matches v1.2.3
workflow_dispatch: {}
permissions: read-all
jobs:
release:
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
actions: read
checks: read
contents: write # Required to create GitHub releases and push tags/commits
deployments: none
id-token: write # Required for npm publish (OIDC)
issues: none
packages: none
pull-requests: none
repository-projects: none
security-events: none
statuses: read
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0 # Full history for changelog generation
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '22'
registry-url: https://registry.npmjs.org
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm lint
- name: Type check
run: pnpm type-check
- name: Test
run: pnpm test
- name: Build CLI package
working-directory: packages/opennextjs-cli
run: pnpm build
- name: Build MCP package
working-directory: packages/opennextjs-mcp
run: pnpm build
- name: Extract version from tag or input
id: version
run: |
# For workflow_dispatch, use package.json version
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
# Read from CLI package.json
VERSION=$(node -p "require('./packages/opennextjs-cli/package.json').version")
# Remove 'v' prefix if present
VERSION="${VERSION#v}"
TAG="v$VERSION"
echo "TAG=$TAG" >> "$GITHUB_OUTPUT"
echo "IS_MANUAL=true" >> "$GITHUB_OUTPUT"
else
# Extract version from tag (e.g., v1.0.0 → 1.0.0)
TAG_NAME="${{ github.ref_name }}"
VERSION="${TAG_NAME#v}"
TAG="$TAG_NAME"
echo "IS_MANUAL=false" >> "$GITHUB_OUTPUT"
fi
echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT"
echo "Version: $VERSION"
echo "Tag: $TAG"
- name: Verify CLI package.json version matches tag
working-directory: packages/opennextjs-cli
run: |
PACKAGE_VERSION=$(node -p "require('./package.json').version")
TAG_VERSION="${{ steps.version.outputs.VERSION }}"
if [ "$PACKAGE_VERSION" != "$TAG_VERSION" ]; then
echo "❌ CLI version mismatch: package.json ($PACKAGE_VERSION) != tag ($TAG_VERSION)" >&2
exit 1
fi
echo "✅ CLI version match: $PACKAGE_VERSION"
- name: Verify MCP package.json version matches tag
working-directory: packages/opennextjs-mcp
run: |
PACKAGE_VERSION=$(node -p "require('./package.json').version")
TAG_VERSION="${{ steps.version.outputs.VERSION }}"
if [ "$PACKAGE_VERSION" != "$TAG_VERSION" ]; then
echo "❌ MCP version mismatch: package.json ($PACKAGE_VERSION) != tag ($TAG_VERSION)" >&2
exit 1
fi
echo "✅ MCP version match: $PACKAGE_VERSION"
- name: Setup git-cliff
uses: kenji-miyake/setup-git-cliff@e4913b34dd9c321f11f2ef3c3866f800714a1d2e # v1
with:
version: latest
- name: Check if tag exists (for manual trigger)
if: steps.version.outputs.IS_MANUAL == 'true'
id: tag_check
run: |
TAG="${{ steps.version.outputs.TAG }}"
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "EXISTS=true" >> "$GITHUB_OUTPUT"
echo "⚠️ Tag $TAG already exists - will use existing tag"
else
echo "EXISTS=false" >> "$GITHUB_OUTPUT"
echo "✅ Tag $TAG does not exist - will create it and generate changelogs first"
fi
- name: Generate changelogs and create tag (manual trigger, tag doesn't exist)
if: steps.version.outputs.IS_MANUAL == 'true' && steps.tag_check.outputs.EXISTS == 'false'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e
TAG="${{ steps.version.outputs.TAG }}"
echo "📝 Generating CLI changelog..."
pnpm changelog:package opennextjs-cli --tag "$TAG" || {
echo "⚠️ CLI changelog generation failed" >&2
exit 1
}
echo "📝 Generating MCP changelog..."
pnpm changelog:package opennextjs-mcp --tag "$TAG" || {
echo "⚠️ MCP changelog generation failed" >&2
exit 1
}
echo "✅ Changelogs generated successfully"
# Commit changelogs
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add packages/opennextjs-cli/CHANGELOG.md packages/opennextjs-mcp/CHANGELOG.md
if git diff --staged --quiet; then
echo "No changelog changes to commit"
else
git commit -m "chore: update changelogs for $TAG release"
git push origin main
echo "✅ Changelogs committed and pushed"
fi
# Create and push tag (this will trigger this workflow again with tag push event)
git tag -a "$TAG" -m "Release $TAG"
git push origin "$TAG"
echo "✅ Tag $TAG created and pushed"
echo "ℹ️ This workflow will run again automatically when the tag is pushed"
- name: Generate package-specific changelogs (when tag already exists)
if: |
(steps.version.outputs.IS_MANUAL == 'true' && steps.tag_check.outputs.EXISTS == 'true') ||
steps.version.outputs.IS_MANUAL == 'false'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e
TAG="${{ steps.version.outputs.TAG }}"
# Generate CLI changelog (writes directly to packages/opennextjs-cli/CHANGELOG.md)
echo "📝 Generating CLI changelog..."
pnpm changelog:package opennextjs-cli --tag "$TAG" || \
echo "⚠️ CLI changelog generation failed" >&2
# Generate MCP changelog (writes directly to packages/opennextjs-mcp/CHANGELOG.md)
echo "📝 Generating MCP changelog..."
pnpm changelog:package opennextjs-mcp --tag "$TAG" || \
echo "⚠️ MCP changelog generation failed" >&2
# Extract changelog sections for GitHub Release notes
# Read from package-specific CHANGELOG.md files
echo "📝 Extracting changelog sections for release notes..."
VERSION="${{ steps.version.outputs.VERSION }}"
# Start with version header
echo "## [$VERSION] - $(date +%Y-%m-%d)" > /tmp/changelog-section.md
echo "" >> /tmp/changelog-section.md
# Extract CLI section from packages/opennextjs-cli/CHANGELOG.md
if [ -f "packages/opennextjs-cli/CHANGELOG.md" ]; then
# Find the version section in CLI changelog
CLI_SECTION=$(awk "/^## \[$VERSION\]/,/^## \[|^<!-- generated/" "packages/opennextjs-cli/CHANGELOG.md" | head -n -1 || true)
if [ -n "$CLI_SECTION" ]; then
echo "### @jsonbored/opennextjs-cli" >> /tmp/changelog-section.md
echo "" >> /tmp/changelog-section.md
# Remove the version header line, keep the rest
echo "$CLI_SECTION" | sed '/^## \[/d' >> /tmp/changelog-section.md || true
echo "" >> /tmp/changelog-section.md
fi
fi
# Extract MCP section from packages/opennextjs-mcp/CHANGELOG.md
if [ -f "packages/opennextjs-mcp/CHANGELOG.md" ]; then
# Find the version section in MCP changelog
MCP_SECTION=$(awk "/^## \[$VERSION\]/,/^## \[|^<!-- generated/" "packages/opennextjs-mcp/CHANGELOG.md" | head -n -1 || true)
if [ -n "$MCP_SECTION" ]; then
echo "### @jsonbored/opennextjs-mcp" >> /tmp/changelog-section.md
echo "" >> /tmp/changelog-section.md
# Remove the version header line, keep the rest
echo "$MCP_SECTION" | sed '/^## \[/d' >> /tmp/changelog-section.md || true
echo "" >> /tmp/changelog-section.md
fi
fi
# If no package-specific sections, create a generic one
if [ ! -s /tmp/changelog-section.md ] || [ "$(wc -l < /tmp/changelog-section.md)" -lt 5 ]; then
echo "Initial release of OpenNext.js CLI and MCP server packages." >> /tmp/changelog-section.md
echo "" >> /tmp/changelog-section.md
fi
# Set changelog section for GitHub release
echo "CHANGELOG_SECTION<<EOF" >> "$GITHUB_ENV" # Trunk: Add missing newline for echo
cat /tmp/changelog-section.md >> "$GITHUB_ENV"
echo "EOF" >> "$GITHUB_ENV" # Trunk: Add missing newline
echo "✅ Changelogs generated successfully"
echo " - packages/opennextjs-cli/CHANGELOG.md"
echo " - packages/opennextjs-mcp/CHANGELOG.md"
- name: Publish CLI to npm (try OIDC first)
id: publish-cli-oidc
working-directory: packages/opennextjs-cli
run: |
# Try publishing with OIDC (if configured)
npm publish --access public && \
echo "✅ Published @jsonbored/opennextjs-cli@${{ steps.version.outputs.VERSION }} to npm (via OIDC)" || \
echo "⚠️ OIDC publish failed, will try NPM_TOKEN fallback"
continue-on-error: true
- name: Publish CLI to npm (fallback to NPM_TOKEN)
if: steps.publish-cli-oidc.outcome == 'failure'
working-directory: packages/opennextjs-cli
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
if [ -z "$NODE_AUTH_TOKEN" ]; then
echo "❌ NPM_TOKEN secret not found. For first release, you need to:" >&2
echo " 1. Create npm automation token: https://www.npmjs.com/settings/JSONbored/tokens" >&2
echo " 2. Add NPM_TOKEN secret to GitHub: Settings → Secrets and variables → Actions" >&2
echo " 3. Name the secret: NPM_TOKEN" >&2
exit 1
fi
# Configure npm to use token
echo "//registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN" > ~/.npmrc
npm publish --access public
echo "✅ Published @jsonbored/opennextjs-cli@${{ steps.version.outputs.VERSION }} to npm (via NPM_TOKEN)"
- name: Publish MCP to npm (try OIDC first)
id: publish-mcp-oidc
working-directory: packages/opennextjs-mcp
run: |
# Try publishing with OIDC (if configured)
npm publish --access public && \
echo "✅ Published @jsonbored/opennextjs-mcp@${{ steps.version.outputs.VERSION }} to npm (via OIDC)" || \
echo "⚠️ OIDC publish failed, will try NPM_TOKEN fallback"
continue-on-error: true
- name: Publish MCP to npm (fallback to NPM_TOKEN)
if: steps.publish-mcp-oidc.outcome == 'failure'
working-directory: packages/opennextjs-mcp
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
if [ -z "$NODE_AUTH_TOKEN" ]; then
echo "❌ NPM_TOKEN secret not found" >&2
exit 1
fi
# Configure npm to use token (if not already configured)
if [ ! -f ~/.npmrc ]; then
echo "//registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN" > ~/.npmrc
fi
npm publish --access public
echo "✅ Published @jsonbored/opennextjs-mcp@${{ steps.version.outputs.VERSION }} to npm (via NPM_TOKEN)"
echo "" >&2
echo "💡 After first release, set up OIDC trusted publishing for both packages:" >&2
echo " 1. Go to: https://www.npmjs.com/settings/JSONbored/automation" >&2
echo " 2. Add trusted publisher for @jsonbored/opennextjs-cli" >&2
echo " 3. Add trusted publisher for @jsonbored/opennextjs-mcp" >&2
echo " 4. Both use: repository JSONbored/opennextjs-cli, workflow .github/workflows/release.yml" >&2
echo " Then you can remove the NPM_TOKEN secret." >&2
- name: Create GitHub Release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
with:
tag_name: ${{ steps.version.outputs.TAG }}
name: Release ${{ steps.version.outputs.VERSION }}
body: |
## Packages Released
- `@jsonbored/opennextjs-cli@${{ steps.version.outputs.VERSION }}`
- `@jsonbored/opennextjs-mcp@${{ steps.version.outputs.VERSION }}`
${{ env.CHANGELOG_SECTION }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}