From b4aac1c6f980e8a611d0a21ad86b64a1d7129649 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+gh0stdotexe@users.noreply.github.com> Date: Sat, 27 Dec 2025 00:46:02 -0700 Subject: [PATCH] fix: update workflows and cliff.toml for improved commit message handling - Enhance cliff.toml with additional patterns for PR references and co-authorship attribution - Update CI workflows to use specific action versions for consistency - Improve error handling and debugging in publish-release.yml and version-bump.yml - Ensure proper environment configuration for trusted npm publishing --- .github/workflows/ci.yml | 8 +-- .github/workflows/publish-release.yml | 67 +++++++++++++++++--- .github/workflows/version-bump.yml | 89 ++++++++++++++++++++++----- cliff.toml | 8 +++ 4 files changed, 143 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc4dd32..3bbd2a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,21 +15,21 @@ concurrency: jobs: test: - name: Test + name: Jest Tests runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4 with: version: 10 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: '20' cache: 'pnpm' diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 22f3ec2..321239e 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -33,7 +33,9 @@ jobs: timeout-minutes: 10 # CRITICAL: Must match npm trusted publisher environment configuration - environment: production + # Environment must be created in repository settings: Settings → Environments → New environment → "production" + environment: + name: production permissions: contents: write # Required to create GitHub releases @@ -41,7 +43,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: # Full history needed for changelog extraction fetch-depth: 0 @@ -79,20 +81,28 @@ jobs: echo "TAG=$TAG" >> $GITHUB_OUTPUT echo "✅ Extracted version: $VERSION from tag: $TAG" - - name: Setup Node.js - uses: actions/setup-node@v4 + - name: Setup Node.js (with OIDC for trusted publishing) + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: '20' registry-url: 'https://registry.npmjs.org' - cache: 'pnpm' - # OIDC is automatically used when id-token: write permission is set + # CRITICAL: setup-node must be configured BEFORE pnpm setup for OIDC to work correctly + # OIDC token is automatically used when id-token: write permission is set # No NODE_AUTH_TOKEN needed for trusted publishing + # Note: cache is configured after pnpm is installed - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4 with: version: 10 + - name: Configure Node.js cache + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: '20' + cache: 'pnpm' + # Configure caching after pnpm is available + - name: Install dependencies run: pnpm install --frozen-lockfile @@ -128,14 +138,53 @@ jobs: echo "🔍 DEBUG: Publishing @jsonbored/safemocker@$VERSION to npm..." echo "🔍 DEBUG: Using trusted publishing (OIDC) - no NODE_AUTH_TOKEN needed" + echo "🔍 DEBUG: Environment: production (configured in job)" + echo "🔍 DEBUG: Repository: ${{ github.repository }}" # For trusted publishing, npm automatically uses OIDC token from GitHub Actions # The --provenance flag creates a signed provenance statement # Ensure trusted publishing is configured in npm: https://docs.npmjs.com/trusted-publishers/ - npm publish --access public --provenance + # Trusted publishing requires: + # 1. Environment name matches npm trusted publisher config (currently: production) + # 2. id-token: write permission (already set in permissions) + # 3. registry-url set in setup-node (already configured) + + if ! npm publish --access public --provenance; then + echo "❌ Error: npm publish failed" >&2 + echo "🔍 DEBUG: Check that trusted publishing is configured in npm for:" >&2 + echo " - Organization/User: JSONbored" >&2 + echo " - Repository: ${{ github.repository }}" >&2 + echo " - Workflow filename: publish-release.yml (must match exactly)" >&2 + echo " - Environment: production (must match exactly)" >&2 + echo " - See: https://docs.npmjs.com/trusted-publishers/" >&2 + echo " - Documentation: .cursor/npm-trusted-publishing-setup.md" >&2 + exit 1 + fi echo "✅ Published @jsonbored/safemocker@$VERSION to npm" + - name: Verify npm publish + run: | + set -e # Exit on error + + VERSION="${{ steps.version.outputs.VERSION }}" + + echo "🔍 DEBUG: Verifying package is published on npm..." + + # Wait a moment for npm registry to update + sleep 2 + + # Verify package exists on npm + if ! npm view "@jsonbored/safemocker@$VERSION" version > /dev/null 2>&1; then + echo "❌ Error: Package not found on npm after publish" >&2 + echo "🔍 DEBUG: Attempted to verify: @jsonbored/safemocker@$VERSION" >&2 + echo "🔍 DEBUG: This may indicate the publish failed or npm registry hasn't updated yet" >&2 + exit 1 + fi + + PUBLISHED_VERSION=$(npm view "@jsonbored/safemocker@$VERSION" version) + echo "✅ Verified: @jsonbored/safemocker@$PUBLISHED_VERSION is published on npm" + - name: Extract changelog for version id: changelog run: | @@ -196,7 +245,7 @@ jobs: head -10 /tmp/release-notes.md || echo "(empty)" - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2 with: tag_name: ${{ steps.version.outputs.TAG }} name: ${{ steps.version.outputs.TAG }} diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 8b5b76c..c16a519 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -46,21 +46,23 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: # CRITICAL: fetch-depth: 0 needed for git-cliff to access full commit history fetch-depth: 0 - # Use main branch for workflow_dispatch, otherwise use PR ref - ref: ${{ github.event_name == 'workflow_dispatch' && 'main' || github.ref }} + # Always checkout main branch (both PR merges and workflow_dispatch should use main) + # For PR merges: github.ref points to PR ref, but we want the merged state on main + # For workflow_dispatch: use main branch + ref: main token: ${{ secrets.GITHUB_TOKEN }} - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4 with: version: 10 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: '20' cache: 'pnpm' @@ -275,6 +277,19 @@ jobs: echo "🔍 DEBUG: Generating changelog for version $NEW_VERSION" echo "🔍 DEBUG: Base tag: $LAST_TAG" + # Verify GITHUB_TOKEN is set for GitHub API access + if [ -z "$GITHUB_TOKEN" ]; then + echo "⚠️ Warning: GITHUB_TOKEN is not set. GitHub integration features (PR links, usernames, contributors) will not be available." + echo "🔍 DEBUG: This is expected if running locally without GITHUB_TOKEN" + else + echo "✅ GITHUB_TOKEN is set - GitHub API integration enabled" + echo "🔍 DEBUG: GitHub API will be used for:" + echo " - PR links in commit messages" + echo " - Contributor usernames" + echo " - PR titles and labels" + echo " - First-time contributor detection" + fi + # Check if changelog already has this version (from previous failed run) if grep -q "## \[$NEW_VERSION\]" CHANGELOG.md 2>/dev/null; then echo "⚠️ Changelog already contains version $NEW_VERSION, checking if it has content..." @@ -309,14 +324,28 @@ jobs: # Use --tag with --latest --unreleased to generate versioned changelog directly # This generates a changelog entry with [NEW_VERSION] instead of [Unreleased] - # --prepend is configured in cliff.toml, so it will prepend to CHANGELOG.md + # --output CHANGELOG.md explicitly writes to the file (required when using --tag) + # --prepend is configured in cliff.toml, so it will prepend to existing CHANGELOG.md # --verbose provides debugging information # --topo-order ensures tags are sorted topologically (important for complex branching) echo "🔍 DEBUG: Running git-cliff to generate versioned changelog..." - echo "🔍 Command: pnpm exec git-cliff --config cliff.toml --tag v$NEW_VERSION --latest --unreleased --verbose --topo-order" + echo "🔍 Command: pnpm exec git-cliff --config cliff.toml --tag v$NEW_VERSION --latest --unreleased --output CHANGELOG.md --verbose --topo-order" # Generate versioned changelog (this prepends to CHANGELOG.md with [NEW_VERSION] header) - pnpm exec git-cliff --config cliff.toml --tag "v$NEW_VERSION" --latest --unreleased --verbose --topo-order + # CRITICAL: --output is required to write to file when using --tag + # GITHUB_TOKEN enables GitHub API access for PR links, usernames, and contributor data + echo "🔍 DEBUG: Executing git-cliff with GitHub integration..." + pnpm exec git-cliff --config cliff.toml --tag "v$NEW_VERSION" --latest --unreleased --output CHANGELOG.md --verbose --topo-order + + # Verify GitHub integration worked (check for PR links or usernames in changelog) + if [ -n "$GITHUB_TOKEN" ]; then + if grep -qE "\[#[0-9]+\]|@[a-zA-Z0-9-]+" CHANGELOG.md 2>/dev/null; then + echo "✅ GitHub integration successful - PR links and/or usernames found in changelog" + else + echo "⚠️ Warning: GitHub integration may not have worked - no PR links or usernames found" + echo "🔍 DEBUG: This may be normal if commits don't have associated PRs" + fi + fi # Verify git-cliff generated content if [ ! -f CHANGELOG.md ]; then @@ -325,13 +354,22 @@ jobs: fi # Verify the version entry was created - if ! grep -q "## \[$NEW_VERSION\]" CHANGELOG.md; then - echo "❌ Changelog was generated but version $NEW_VERSION entry not found" - echo "🔍 DEBUG: First 20 lines of CHANGELOG.md:" - head -20 CHANGELOG.md + # Use -F for fixed string matching (brackets are literal, not regex) + if ! grep -Fq "## [$NEW_VERSION]" CHANGELOG.md; then + echo "❌ Error: Changelog was generated but version $NEW_VERSION entry not found" >&2 + echo "🔍 DEBUG: First 20 lines of CHANGELOG.md:" >&2 + head -20 CHANGELOG.md >&2 exit 1 fi + # Validate changelog has content (not just header) + if ! grep -FA 5 "## [$NEW_VERSION]" CHANGELOG.md | grep -q "^###"; then + echo "⚠️ Warning: Changelog section for version $NEW_VERSION appears empty (no subsections found)" + echo "🔍 DEBUG: This may indicate no commits were categorized for this version" + else + echo "✅ Changelog generated with content for version $NEW_VERSION" + fi + # Check if changelog has content (more than just the header) VERSION_LINES=$(awk -v version="$NEW_VERSION" '/^## \[/ { if (index($0, "[" version "]") > 0) { found=1; next } if (found && /^## \[/) exit; if (found) count++ } END { print count+0 }' CHANGELOG.md) echo "🔍 DEBUG: Version $NEW_VERSION section has $VERSION_LINES lines of content" @@ -407,22 +445,41 @@ jobs: echo "🔍 DEBUG: Pushing commit to main..." # Push commit first (ensures commit is on remote before tag) - git push --force-with-lease origin main + # Use regular push (not force) since we're pushing to main after merge + if ! git push origin main; then + echo "❌ Error: Git push to main failed" >&2 + echo "🔍 DEBUG: This may indicate a conflict or permission issue" >&2 + exit 1 + fi echo "🔍 DEBUG: Verifying commit push succeeded..." # Verify commit push succeeded - git fetch origin main --depth=1 + git fetch origin main LOCAL_HEAD=$(git rev-parse HEAD) REMOTE_HEAD=$(git rev-parse origin/main) if [ "$LOCAL_HEAD" != "$REMOTE_HEAD" ]; then - echo "❌ Git push failed: local HEAD ($LOCAL_HEAD) does not match origin/main ($REMOTE_HEAD)" >&2 + echo "❌ Error: Git push failed - local HEAD ($LOCAL_HEAD) does not match origin/main ($REMOTE_HEAD)" >&2 + echo "🔍 DEBUG: This indicates the push did not succeed" >&2 exit 1 fi echo "✅ Commit push verified: local and remote are in sync" echo "🔍 DEBUG: Creating tag v$NEW_VERSION..." + # Extract changelog summary for tag message (first 3 commit messages or first 200 chars) + CHANGELOG_SUMMARY=$(grep -A 50 "## \[$NEW_VERSION\]" CHANGELOG.md 2>/dev/null | grep "^- " | head -3 | sed 's/^- //' | tr '\n' '; ' | cut -c1-200 || echo "") + + # Build tag message with version and summary + # Use a temporary file to handle multi-line tag messages safely + TAG_MSG_FILE=$(mktemp) + echo "Release v$NEW_VERSION" > "$TAG_MSG_FILE" + if [ -n "$CHANGELOG_SUMMARY" ]; then + echo "" >> "$TAG_MSG_FILE" + echo "$CHANGELOG_SUMMARY" >> "$TAG_MSG_FILE" + fi + # Create and push tag separately (ensures tag push event triggers publish-release.yml workflow) - git tag -a "v$NEW_VERSION" -m "Release v$NEW_VERSION" + git tag -a "v$NEW_VERSION" -F "$TAG_MSG_FILE" + rm -f "$TAG_MSG_FILE" echo "🔍 DEBUG: Pushing tag v$NEW_VERSION..." git push origin "v$NEW_VERSION" diff --git a/cliff.toml b/cliff.toml index a235275..f9783dd 100644 --- a/cliff.toml +++ b/cliff.toml @@ -92,10 +92,18 @@ commit_preprocessors = [ { pattern = '^Merged PR #\\d+: (.+)', replace = "$1" }, # Handle PR references in commit messages (e.g., "fix: issue (#123)") { pattern = '\\(#(\\d+)\\)', replace = "([#${1}](https://github.com/JSONbored/safemocker/pull/${1}))" }, + # Handle standalone PR references (space before #, followed by digits, then non-digit or end) + { pattern = ' #(\\d+)([^0-9]|$)', replace = " [#${1}](https://github.com/JSONbored/safemocker/pull/${1})${2}" }, + # Handle co-authored commits (Co-authored-by: user@example.com) + # Extract co-author information for better attribution + { pattern = 'Co-authored-by: (.+)', replace = "Co-authored-by: $1" }, # Normalize multiple spaces to single space { pattern = ' +', replace = " " }, # Remove trailing periods from commit messages for consistency { pattern = '\\.$', replace = "" }, + # Clean up common merge commit prefixes + { pattern = "^Merge branch '[^']+' into ", replace = "" }, + { pattern = '^Merge .+ into ', replace = "" }, ] [bump]