Repository navigation
trigger-release #53
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |