Skip to content

trigger-release

trigger-release #53

name: Release with SBOM and Notes
# This workflow creates GitHub releases and publishes to NPM after a version bump.
# It is triggered only by the auto-version-bump workflow or manual dispatch.
# Default permissions for all jobs
permissions:
contents: write # Needed for creating releases and tags
actions: read # Needed to read workflow runs
id-token: write # Needed for npm provenance
on:
# Allow manual triggering for specific versions
workflow_dispatch:
inputs:
force_version:
description: 'Force release of specific version (leave empty to use package.json)'
required: false
# Direct trigger from auto-version-bump workflow
repository_dispatch:
types: [trigger-release]
# Trigger on pushes to main that modify package.json
# This catches the commits made by the auto-version-bump workflow
# and serves as a fallback mechanism
push:
branches:
- main
paths:
- 'package.json'
# Also triggered when auto-version-bump workflow completes successfully
# This is a backup trigger method
workflow_run:
workflows:
- "Auto Version Bump"
types:
- completed
branches:
- main
jobs:
check-version:
runs-on: ubuntu-latest
permissions:
contents: write # Needed for checking releases and tags
actions: read # Needed to read workflow runs
outputs:
version: ${{ steps.get-version.outputs.version }}
proceed: ${{ steps.check.outputs.proceed }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for release notes
ref: ${{ github.event_name == 'workflow_run' && 'main' || github.ref }}
- name: Debug workflow metadata
run: |
echo "========== WORKFLOW DEBUG INFO =========="
echo "Event name: ${{ github.event_name }}"
echo "Event trigger: ${{ github.event_name }}"
echo "Ref: ${{ github.ref }}"
echo "SHA: ${{ github.sha }}"
echo "Repository: ${{ github.repository }}"
echo "Actor: ${{ github.actor }}"
echo "Workflow permissions:"
echo " Contents: ${{ github.token_permissions.contents }}"
echo " Actions: ${{ github.token_permissions.actions }}"
echo " ID-token: ${{ github.token_permissions.id-token }}"
if [ "${{ github.event_name }}" = "workflow_run" ]; then
echo "Workflow run details:"
echo " Source workflow: ${{ github.event.workflow_run.name }}"
echo " Workflow ID: ${{ github.event.workflow_run.id }}"
echo " Workflow conclusion: ${{ github.event.workflow_run.conclusion }}"
echo " Source HEAD SHA: ${{ github.event.workflow_run.head_sha }}"
echo " Source branch: ${{ github.event.workflow_run.head_branch }}"
echo " Source repository: ${{ github.event.workflow_run.repository.full_name }}"
echo " Created at: ${{ github.event.workflow_run.created_at }}"
echo " Updated at: ${{ github.event.workflow_run.updated_at }}"
fi
echo "Listing repository tags:"
git tag --list -n
echo "Recent commits on main:"
git log -n 5 --pretty=format:"%h %s"
# Package.json is now the sole source of version information
echo "Package.json version:"
cat package.json | grep version || echo "Could not find version in package.json"
echo "========================================"
- name: Get version from package.json
id: get-version
run: |
# First check if we're using a forced version from workflow_dispatch
FORCE_VERSION="${{ github.event.inputs.force_version }}"
if [ -n "$FORCE_VERSION" ]; then
echo "Using forced version from workflow dispatch: $FORCE_VERSION"
echo "version=$FORCE_VERSION" >> $GITHUB_OUTPUT
exit 0
fi
# Check if this is a repository_dispatch event with version in payload
if [ "${{ github.event_name }}" = "repository_dispatch" ]; then
DISPATCH_VERSION="${{ github.event.client_payload.version }}"
if [ -n "$DISPATCH_VERSION" ]; then
echo "Using version from repository dispatch: $DISPATCH_VERSION"
echo "version=$DISPATCH_VERSION" >> $GITHUB_OUTPUT
exit 0
fi
fi
# Get version from package.json
if [ -f "package.json" ]; then
VERSION=$(jq -r '.version' package.json)
echo "Current version in package.json: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
else
echo "ERROR: package.json not found!"
echo "Files in current directory:"
ls -la
exit 1
fi
- name: Check for existing GitHub release
id: check-release
env:
VERSION_INPUT: ${{ steps.get-version.outputs.version }}
run: |
VERSION="${VERSION_INPUT}"
echo "Checking if GitHub release v$VERSION already exists..."
# Set GH_TOKEN for gh CLI
export GH_TOKEN="${{ secrets.GITHUB_TOKEN }}"
if gh release view "v$VERSION" &> /dev/null; then
echo "::warning::Release v$VERSION already exists!"
echo "release_exists=true" >> $GITHUB_OUTPUT
# Get release URL
RELEASE_URL=$(gh release view "v$VERSION" --json url -q .url)
echo "Existing release: $RELEASE_URL"
else
echo "No existing release for v$VERSION found, can proceed with new release"
echo "release_exists=false" >> $GITHUB_OUTPUT
fi
- name: Check if we should proceed with release
id: check
run: |
echo "Event name: ${{ github.event_name }}"
# Always honor forced release for manual workflow dispatch
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
FORCE_VERSION="${{ github.event.inputs.force_version }}"
if [ -n "$FORCE_VERSION" ]; then
echo "Manual workflow trigger with forced version - proceeding with release"
echo "proceed=true" >> $GITHUB_OUTPUT
exit 0
fi
echo "Manual workflow trigger - proceeding with release"
echo "proceed=true" >> $GITHUB_OUTPUT
exit 0
fi
# For repository_dispatch events from auto-version-bump workflow
if [ "${{ github.event_name }}" = "repository_dispatch" ]; then
echo "Processing repository_dispatch event with type: ${{ github.event.action }}"
if [ "${{ github.event.action }}" = "trigger-release" ]; then
echo "Direct trigger from auto-version-bump workflow - proceeding with release"
echo "proceed=true" >> $GITHUB_OUTPUT
exit 0
fi
fi
# Check if release already exists
if [ "${{ steps.check-release.outputs.release_exists }}" = "true" ]; then
echo "::warning::Release already exists for this version - skipping release"
echo "proceed=false" >> $GITHUB_OUTPUT
exit 0
fi
# For workflow_run events from Auto Version Bump
if [ "${{ github.event_name }}" = "workflow_run" ]; then
echo "Processing workflow_run event from ${github.event.workflow_run.name}"
if [ "${{ github.event.workflow_run.conclusion }}" = "success" ]; then
echo "Auto Version Bump completed successfully - proceeding with release"
# Get the head SHA from the workflow run
HEAD_SHA="${{ github.event.workflow_run.head_sha }}"
echo "Source workflow HEAD SHA: $HEAD_SHA"
# Ensure we have the latest code from main
git fetch origin main
git checkout main
git pull origin main
# Get the current version from package.json
CURRENT_VERSION=$(jq -r '.version' package.json)
echo "Current version from package.json: $CURRENT_VERSION"
# Check if this version already has a release
export GH_TOKEN="${{ secrets.GITHUB_TOKEN }}"
if gh release view "v$CURRENT_VERSION" &> /dev/null; then
echo "Release v$CURRENT_VERSION already exists - skipping release creation"
echo "proceed=false" >> $GITHUB_OUTPUT
else
echo "Version $CURRENT_VERSION has no existing GitHub release - proceeding with release"
echo "proceed=true" >> $GITHUB_OUTPUT
fi
else
echo "Auto Version Bump did not complete successfully - skipping release"
echo "proceed=false" >> $GITHUB_OUTPUT
fi
exit 0
fi
# For push events - check if this is a version bump commit
if [ "${{ github.event_name }}" = "push" ]; then
echo "This is a push event - checking if it's a version bump commit"
# Check the commit message to see if it's from the auto-version-bump workflow
COMMIT_MSG=$(git log -1 --pretty=%B)
echo "Last commit message: $COMMIT_MSG"
if [[ "$COMMIT_MSG" == *"Bump version to"* ]]; then
echo "This appears to be a version bump commit"
# Check existing GitHub releases
export GH_TOKEN="${{ secrets.GITHUB_TOKEN }}"
# Get current version from package.json
CURRENT_VERSION=$(jq -r '.version' package.json)
echo "Current version in package.json: $CURRENT_VERSION"
# Check if this version already has a release
if gh release view "v$CURRENT_VERSION" &> /dev/null; then
echo "Release v$CURRENT_VERSION already exists - skipping release creation"
echo "proceed=false" >> $GITHUB_OUTPUT
exit 0
fi
# If we've reached here, we have a version that needs releasing
echo "Version $CURRENT_VERSION has no existing GitHub release - proceeding with release"
echo "proceed=true" >> $GITHUB_OUTPUT
exit 0
else
echo "This does not appear to be a version bump commit - skipping release"
echo "proceed=false" >> $GITHUB_OUTPUT
exit 0
fi
fi
# Default fallback
echo "Unknown event type - proceeding with release as fallback"
echo "proceed=true" >> $GITHUB_OUTPUT
build:
needs: check-version
if: needs.check-version.outputs.proceed == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for release notes
ref: ${{ github.event_name == 'workflow_run' && 'main' || github.ref }}
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Display version info
run: |
echo "Working with version: ${{ needs.check-version.outputs.version }}"
- name: Build project
run: |
echo "TypeScript version:"
npx tsc --version
echo "Checking for workers-types package:"
npm list @cloudflare/workers-types || true
echo "Building project with verbose TypeScript output..."
npx tsc --listEmittedFiles
npx tsc -p tsconfig.cli.json --listEmittedFiles
- name: Run tests
run: npm test || echo "Tests skipped"
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: dist/
generate-sbom:
needs: [check-version, build]
if: needs.check-version.outputs.proceed == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Install dependencies
run: npm ci
- name: Generate CycloneDX SBOM
uses: CycloneDX/gh-node-module-generatebom@v1
with:
path: ./
output: ./bom.json
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: ./bom.json
create-release-notes:
needs: [check-version, build]
if: needs.check-version.outputs.proceed == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
release_notes: ${{ steps.generate-notes.outputs.notes }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set version for release notes
id: version
run: |
echo "version=${{ needs.check-version.outputs.version }}" >> $GITHUB_OUTPUT
echo "Using version: ${{ needs.check-version.outputs.version }}"
- name: Get previous tag
id: previoustag
run: |
PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "none")
echo "tag=$PREVIOUS_TAG" >> $GITHUB_OUTPUT
- name: Generate release notes
id: generate-notes
run: |
if [ "${{ steps.previoustag.outputs.tag }}" = "none" ]; then
COMMITS=$(git log --pretty=format:"- %s (%h)" -n 20)
else
COMMITS=$(git log ${{ steps.previoustag.outputs.tag }}..HEAD --pretty=format:"- %s (%h)")
fi
PR_NUMBERS=$(echo "$COMMITS" | grep -o "#[0-9]\+" | sort -u | sed 's/#//')
echo "## What's Changed in v${{ steps.version.outputs.version }}" > RELEASE_NOTES.md
echo "" >> RELEASE_NOTES.md
echo "### Commits" >> RELEASE_NOTES.md
echo "$COMMITS" >> RELEASE_NOTES.md
echo "" >> RELEASE_NOTES.md
if [ ! -z "$PR_NUMBERS" ]; then
echo "### Pull Requests" >> RELEASE_NOTES.md
for PR in $PR_NUMBERS; do
PR_INFO=$(curl -s -H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${{ github.repository }}/pulls/$PR)
PR_TITLE=$(echo "$PR_INFO" | jq -r '.title // "Unknown PR Title"')
PR_URL=$(echo "$PR_INFO" | jq -r '.html_url // "#"')
PR_AUTHOR=$(echo "$PR_INFO" | jq -r '.user.login // "Unknown"')
echo "- #$PR: $PR_TITLE by @$PR_AUTHOR [View PR]($PR_URL)" >> RELEASE_NOTES.md
done
echo "" >> RELEASE_NOTES.md
fi
echo "### SBOM Information" >> RELEASE_NOTES.md
echo "This release includes a Software Bill of Materials (SBOM) in CycloneDX format." >> RELEASE_NOTES.md
echo "" >> RELEASE_NOTES.md
# Escape special characters for GitHub Actions output
NOTES=$(cat RELEASE_NOTES.md)
NOTES="${NOTES//'%'/'%25'}"
# Handle newlines properly - note that we need to escape \n and \r
NOTES="${NOTES//$'\n'/'%0A'}"
NOTES="${NOTES//$'\r'/'%0D'}"
echo "notes=$NOTES" >> $GITHUB_OUTPUT
- name: Upload release notes
uses: actions/upload-artifact@v4
with:
name: release-notes
path: RELEASE_NOTES.md
release:
needs: [check-version, build, generate-sbom, create-release-notes]
if: always() && needs.check-version.outputs.proceed == 'true' && !cancelled() && !failure()
runs-on: ubuntu-latest
permissions:
contents: write # Needed for creating releases
id-token: write # Needed for npm provenance
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set version for release
id: version
env:
VERSION_INPUT: ${{ needs.check-version.outputs.version }}
run: |
echo "version=${VERSION_INPUT}" >> $GITHUB_OUTPUT
echo "Using version: ${VERSION_INPUT}"
- name: Debug release information
env:
VERSION_INPUT: ${{ needs.check-version.outputs.version }}
run: |
echo "========== RELEASE DEBUG INFO =========="
echo "Release version: v${VERSION_INPUT}"
echo "Creating tag: v${VERSION_INPUT}"
echo "Checking if artifacts are available..."
echo "========================================"
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build-artifacts
path: dist/
- name: Download SBOM
uses: actions/download-artifact@v4
with:
name: sbom
path: ./
- name: Download release notes
uses: actions/download-artifact@v4
with:
name: release-notes
path: ./
- name: Verify artifacts
run: |
echo "Checking for downloaded artifacts..."
if [ -f "./RELEASE_NOTES.md" ]; then
echo "✅ Release notes found"
echo "Content preview:"
head -n 5 ./RELEASE_NOTES.md
else
echo "❌ Release notes not found!"
ls -la ./
fi
if [ -f "./bom.json" ]; then
echo "✅ SBOM file found"
else
echo "❌ SBOM file not found!"
ls -la ./
fi
echo "Build artifacts:"
ls -la dist/ || echo "Dist directory not found!"
- name: Setup Node.js for publishing
uses: actions/setup-node@v4
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- name: Create GitHub Release
id: create_release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.version.outputs.version }}
name: Release v${{ steps.version.outputs.version }}
body_path: ./RELEASE_NOTES.md
files: |
./bom.json
draft: false
prerelease: false
generate_release_notes: false # We create our own custom notes
fail_on_unmatched_files: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish to NPM
if: success()
run: |
echo "Version in package.json before publishing:"
cat package.json | grep version
# Make sure all dependencies are installed correctly (including dev dependencies)
echo "Reinstalling dependencies to ensure build works"
npm ci
# Verify TypeScript is working
echo "Checking TypeScript config"
npx tsc --version
npx tsc --listFiles
echo "Publishing to NPM with provenance (using OIDC Trusted Publisher)"
npm publish --provenance --access public
echo "NPM publish complete"
# No NODE_AUTH_TOKEN needed - using npm Trusted Publishers (OIDC)
- name: Release complete
run: |
echo "✅ Release process complete"
echo "Version: v${{ steps.version.outputs.version }}"
echo "Release URL: ${{ steps.create_release.outputs.url }}"
echo "SBOM and detailed release notes published to GitHub release"