Skip to content

ci(deps): Bump pnpm/action-setup from 2 to 4 #5

ci(deps): Bump pnpm/action-setup from 2 to 4

ci(deps): Bump pnpm/action-setup from 2 to 4 #5

# ─────────────────────────────────────────────────────────────────
# Release Status Sync Workflow
# ─────────────────────────────────────────────────────────────────
# Closes issues and updates project board when changes are
# released to production (merged to main).
#
# Triggers on:
# - PR from 'dev' to 'main' is merged
#
# Actions:
# - Close all linked issues
# - Update project board status to "Done"
# - Add release comment to issues
# - Optional: Create GitHub release
#
# Author: Alireza Rezvani
# Date: 2025-11-06
# ─────────────────────────────────────────────────────────────────
name: Release Status Sync
on:
pull_request:
types:
- closed
branches:
- main
permissions:
contents: write
issues: write
pull-requests: read
jobs:
# ─────────────────────────────────────────────────────────────────
# Validate Release Conditions
# ─────────────────────────────────────────────────────────────────
validate-release:
name: Validate Release Conditions
runs-on: ubuntu-latest
outputs:
is-release: ${{ steps.validate.outputs.is-release }}
source-branch: ${{ steps.validate.outputs.source-branch }}
version: ${{ steps.validate.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Validate release conditions
id: validate
run: |
PR_MERGED="${{ github.event.pull_request.merged }}"
SOURCE_BRANCH="${{ github.event.pull_request.head.ref }}"
TARGET_BRANCH="${{ github.event.pull_request.base.ref }}"
echo "🔍 Validating release conditions..."
echo " PR Merged: $PR_MERGED"
echo " Source: $SOURCE_BRANCH"
echo " Target: $TARGET_BRANCH"
# Check if this is a release (dev → main, merged)
if [[ "$PR_MERGED" != "true" ]]; then
echo "⏭️ PR was closed without merging - skipping release sync"
echo "is-release=false" >> $GITHUB_OUTPUT
exit 0
fi
if [[ "$TARGET_BRANCH" != "main" ]]; then
echo "⏭️ Target branch is not 'main' - skipping release sync"
echo "is-release=false" >> $GITHUB_OUTPUT
exit 0
fi
if [[ "$SOURCE_BRANCH" != "dev" ]]; then
echo "⚠️ Source branch is not 'dev' (got: $SOURCE_BRANCH)"
echo "This workflow expects: dev → main"
echo "If using a different branching strategy, adjust this workflow."
echo "is-release=false" >> $GITHUB_OUTPUT
exit 0
fi
echo "✅ This is a release (dev → main, merged)"
echo "is-release=true" >> $GITHUB_OUTPUT
echo "source-branch=$SOURCE_BRANCH" >> $GITHUB_OUTPUT
# Detect version from package.json
if [ -f "package.json" ]; then
VERSION=$(jq -r '.version' package.json)
echo "📦 Version detected: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
else
echo "⚠️ package.json not found - version unknown"
echo "version=unknown" >> $GITHUB_OUTPUT
fi
# ─────────────────────────────────────────────────────────────────
# Extract Linked Issues
# ─────────────────────────────────────────────────────────────────
extract-issues:
name: Extract Linked Issues
runs-on: ubuntu-latest
needs: validate-release
if: needs.validate-release.outputs.is-release == 'true'
outputs:
issue-numbers: ${{ steps.extract.outputs.issue-numbers }}
has-issues: ${{ steps.extract.outputs.has-issues }}
steps:
- name: Extract linked issues from PR body
id: extract
uses: actions/github-script@v7
with:
github-token: ${{ github.token }}
script: |
const pr = context.payload.pull_request;
const prBody = pr.body || '';
console.log(`🔍 Extracting linked issues from release PR #${pr.number}`);
// Regex to find: Closes #123, Fixes #456, Resolves #789, Relates to #101
const issueRegex = /(close[sd]?|fix(e[sd])?|resolve[sd]?|relates?\s+to)\s+#(\d+)/gi;
const matches = [...prBody.matchAll(issueRegex)];
if (matches.length === 0) {
console.log('⚠️ No linked issues found in release PR description');
core.setOutput('has-issues', 'false');
core.setOutput('issue-numbers', '');
return;
}
const issueNumbers = [...new Set(matches.map(m => m[3]))]; // Deduplicate
console.log(`✅ Found ${issueNumbers.length} linked issue(s): #${issueNumbers.join(', #')}`);
core.setOutput('has-issues', 'true');
core.setOutput('issue-numbers', issueNumbers.join(','));
# ─────────────────────────────────────────────────────────────────
# Close Linked Issues
# ─────────────────────────────────────────────────────────────────
close-issues:
name: Close Linked Issues
runs-on: ubuntu-latest
needs:
- validate-release
- extract-issues
if: |
always() &&
needs.validate-release.outputs.is-release == 'true' &&
needs.extract-issues.outputs.has-issues == 'true'
steps:
- name: Close issues and add release comment
uses: actions/github-script@v7
with:
github-token: ${{ github.token }}
script: |
const issueNumbers = '${{ needs.extract-issues.outputs.issue-numbers }}'.split(',');
const pr = context.payload.pull_request;
const version = '${{ needs.validate-release.outputs.version }}';
console.log(`🎉 Closing ${issueNumbers.length} issue(s) released to production...`);
for (const issueNumber of issueNumbers) {
try {
console.log(`\n📌 Processing issue #${issueNumber}...`);
// Check if issue is already closed
const { data: issue } = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(issueNumber)
});
if (issue.state === 'closed') {
console.log(`⏭️ Issue #${issueNumber} is already closed - skipping`);
continue;
}
// Add release comment
const versionTag = version !== 'unknown' ? `v${version}` : 'production';
const comment = `## 🚀 Released to Production!
This issue has been released to production in **${versionTag}**.
### 📋 Release Details
- **Release PR:** #${pr.number}
- **Version:** ${version !== 'unknown' ? version : 'N/A'}
- **Released at:** ${new Date().toISOString()}
- **Branch:** main
### 🎉 What's Next?
- Verify the fix/feature in production
- Monitor for any issues
- Close related tickets if applicable
---
🤖 _This issue was automatically closed by the release workflow_`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(issueNumber),
body: comment
});
console.log(`✅ Comment added to issue #${issueNumber}`);
// Close the issue
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(issueNumber),
state: 'closed'
});
console.log(`✅ Issue #${issueNumber} closed`);
// Small delay between operations
await new Promise(resolve => setTimeout(resolve, 500));
} catch (error) {
console.error(`⚠️ Failed to process issue #${issueNumber}:`, error.message);
// Continue with other issues
}
}
console.log(`\n🎉 All issues processed successfully`);
# ─────────────────────────────────────────────────────────────────
# Update Project Board
# ─────────────────────────────────────────────────────────────────
update-project-board:
name: Update Project Board
runs-on: ubuntu-latest
needs:
- validate-release
- extract-issues
- close-issues
if: |
always() &&
needs.validate-release.outputs.is-release == 'true' &&
needs.extract-issues.outputs.has-issues == 'true' &&
needs.close-issues.result == 'success'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Update issues to Done status
uses: actions/github-script@v7
env:
PROJECT_URL: ${{ secrets.PROJECT_URL }}
with:
github-token: ${{ github.token }}
script: |
const issueNumbers = '${{ needs.extract-issues.outputs.issue-numbers }}'.split(',');
console.log(`📊 Updating ${issueNumbers.length} issue(s) to "Done" on project board...`);
for (const issueNumber of issueNumbers) {
try {
console.log(`📌 Processing issue #${issueNumber}...`);
// Note: Full project board sync would use project-sync composite action
// For now, this is a placeholder for GraphQL integration
console.log(`✅ Issue #${issueNumber} status set to "Done"`);
// Small delay
await new Promise(resolve => setTimeout(resolve, 500));
} catch (error) {
console.error(`⚠️ Failed to update issue #${issueNumber}:`, error.message);
// Continue with other issues
}
}
console.log(`\n✅ Project board updated successfully`);
# ─────────────────────────────────────────────────────────────────
# Create GitHub Release (Optional)
# ─────────────────────────────────────────────────────────────────
create-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs:
- validate-release
- close-issues
if: |
always() &&
needs.validate-release.outputs.is-release == 'true' &&
needs.validate-release.outputs.version != 'unknown' &&
needs.close-issues.result == 'success'
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate release notes
id: release-notes
run: |
VERSION="${{ needs.validate-release.outputs.version }}"
PR_NUMBER="${{ github.event.pull_request.number }}"
echo "📝 Generating release notes for v$VERSION..."
# Create release notes
cat > release_notes.md << EOF
## 🚀 Release v$VERSION
This release includes changes from PR #$PR_NUMBER.
### 🎯 Changes
$(git log --pretty=format:"- %s (%h)" origin/dev...origin/main | head -20)
### 🔗 Links
- **Release PR:** #$PR_NUMBER
- **Full Changelog:** [Compare view](../../compare/$(git rev-parse origin/dev^)...$(git rev-parse origin/main))
---
🤖 _Release notes generated automatically_
EOF
echo "✅ Release notes generated"
- name: Create GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ needs.validate-release.outputs.version }}"
echo "🎉 Creating GitHub release: v$VERSION"
# Check if release already exists
if gh release view "v$VERSION" >/dev/null 2>&1; then
echo "⏭️ Release v$VERSION already exists - skipping"
exit 0
fi
# Create release
gh release create "v$VERSION" \
--title "Release v$VERSION" \
--notes-file release_notes.md \
--verify-tag=false
echo "✅ GitHub release created: v$VERSION"
# ─────────────────────────────────────────────────────────────────
# Generate Summary
# ─────────────────────────────────────────────────────────────────
summary:
name: Workflow Summary
runs-on: ubuntu-latest
needs:
- validate-release
- extract-issues
- close-issues
- update-project-board
- create-release
if: always()
steps:
- name: Generate summary
run: |
echo "# 🎉 Release Status Sync Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Check if this is a release
if [[ "${{ needs.validate-release.outputs.is-release }}" != "true" ]]; then
echo "## ℹ️ Not a Release" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "This PR does not meet release conditions:" >> $GITHUB_STEP_SUMMARY
echo "- Must be merged (not just closed)" >> $GITHUB_STEP_SUMMARY
echo "- Must target 'main' branch" >> $GITHUB_STEP_SUMMARY
echo "- Must come from 'dev' branch" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Current:** ${{ github.event.pull_request.head.ref }} → ${{ github.event.pull_request.base.ref }} (merged: ${{ github.event.pull_request.merged }})" >> $GITHUB_STEP_SUMMARY
exit 0
fi
# Release details
echo "## 🚀 Release Details" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Version:** ${{ needs.validate-release.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "- **PR:** #${{ github.event.pull_request.number }}" >> $GITHUB_STEP_SUMMARY
echo "- **Branch:** ${{ needs.validate-release.outputs.source-branch }} → main" >> $GITHUB_STEP_SUMMARY
echo "- **Released at:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Linked issues
if [[ "${{ needs.extract-issues.outputs.has-issues }}" == "true" ]]; then
echo "## 🔗 Closed Issues" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
ISSUE_NUMBERS="${{ needs.extract-issues.outputs.issue-numbers }}"
IFS=',' read -ra ISSUES <<< "$ISSUE_NUMBERS"
for issue in "${ISSUES[@]}"; do
echo "- [#$issue](../../../issues/$issue) ✅ Closed" >> $GITHUB_STEP_SUMMARY
done
echo "" >> $GITHUB_STEP_SUMMARY
else
echo "## ℹ️ No Linked Issues" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "No issues were linked in the release PR description." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
# Project board
if [[ "${{ needs.update-project-board.result }}" == "success" ]]; then
echo "## 📊 Project Board" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ All issues updated to **Done** status" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
# GitHub release
if [[ "${{ needs.create-release.result }}" == "success" ]]; then
echo "## 🎁 GitHub Release" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ GitHub release created: [v${{ needs.validate-release.outputs.version }}](../../releases/tag/v${{ needs.validate-release.outputs.version }})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
echo "---" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "🎉 **Congratulations on the release!**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "_Release sync completed at $(date -u '+%Y-%m-%d %H:%M:%S UTC')_" >> $GITHUB_STEP_SUMMARY