Skip to content

chore: cleanup temporary state files and add sync status report #3

chore: cleanup temporary state files and add sync status report

chore: cleanup temporary state files and add sync status report #3

Workflow file for this run

# ─────────────────────────────────────────────────────────────────
# PR Status Sync Workflow
# ─────────────────────────────────────────────────────────────────
# Syncs PR lifecycle events with linked issues and project board.
#
# Status Transitions:
# - PR opened (ready) → Issues: "In Review"
# - PR draft → Issues: "In Progress"
# - PR merged → Issues: "To Deploy" + delete branch
# - PR closed (not merged) → Issues: "In Progress"
#
# Features:
# - Extracts linked issues from PR body
# - Updates project board status
# - Deletes merged branches
# - Fork-safe (skip writes for forks)
# - Debounced (10s delay to prevent loops)
#
# Author: Alireza Rezvani
# Date: 2025-11-06
# ─────────────────────────────────────────────────────────────────
name: PR Status Sync
on:
pull_request:
types:
- opened
- closed
- converted_to_draft
- ready_for_review
- reopened
branches:
- dev
pull_request_review:
types:
- submitted
branches:
- dev
permissions:
contents: write
pull-requests: write
issues: write
# Concurrency control (debounce)
concurrency:
group: pr-sync-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
# ─────────────────────────────────────────────────────────────────
# Fork Safety Check
# ─────────────────────────────────────────────────────────────────
fork-check:
name: Check Fork Status
runs-on: ubuntu-latest
outputs:
is-fork: ${{ steps.fork-safety.outputs.is-fork }}
should-skip-writes: ${{ steps.fork-safety.outputs.should-skip-writes }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check if PR is from fork
id: fork-safety
uses: ./.github/actions/fork-safety
# ─────────────────────────────────────────────────────────────────
# Extract Linked Issues
# ─────────────────────────────────────────────────────────────────
extract-issues:
name: Extract Linked Issues
runs-on: ubuntu-latest
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@v8
with:
github-token: ${{ github.token }}
script: |
const pr = context.payload.pull_request;
const prBody = pr.body || '';
console.log(`🔍 Extracting linked issues from 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 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(','));
# ─────────────────────────────────────────────────────────────────
# Debounce Delay (Prevent Loops)
# ─────────────────────────────────────────────────────────────────
debounce:
name: Debounce Delay
runs-on: ubuntu-latest
needs:
- fork-check
- extract-issues
if: |
always() &&
needs.fork-check.outputs.should-skip-writes != 'true' &&
needs.extract-issues.outputs.has-issues == 'true'
steps:
- name: Wait 10 seconds to prevent loops
run: |
echo "⏱️ Waiting 10 seconds to debounce automation loops..."
sleep 10
echo "✅ Debounce complete"
# ─────────────────────────────────────────────────────────────────
# Sync Issue Status Based on PR Event
# ─────────────────────────────────────────────────────────────────
sync-issue-status:
name: Sync Issue Status
runs-on: ubuntu-latest
needs:
- fork-check
- extract-issues
- debounce
if: |
always() &&
needs.fork-check.outputs.should-skip-writes != 'true' &&
needs.extract-issues.outputs.has-issues == 'true'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Determine target status
id: determine-status
run: |
PR_STATE="${{ github.event.pull_request.state }}"
PR_DRAFT="${{ github.event.pull_request.draft }}"
PR_MERGED="${{ github.event.pull_request.merged }}"
EVENT_ACTION="${{ github.event.action }}"
echo "📊 PR Details:"
echo " State: $PR_STATE"
echo " Draft: $PR_DRAFT"
echo " Merged: $PR_MERGED"
echo " Action: $EVENT_ACTION"
TARGET_STATUS=""
# Determine status based on PR lifecycle
if [[ "$PR_MERGED" == "true" ]]; then
TARGET_STATUS="To Deploy"
echo "✅ PR merged → Issues to 'To Deploy'"
elif [[ "$PR_STATE" == "closed" && "$PR_MERGED" != "true" ]]; then
TARGET_STATUS="In Progress"
echo "🔄 PR closed without merge → Issues back to 'In Progress'"
elif [[ "$PR_DRAFT" == "true" || "$EVENT_ACTION" == "converted_to_draft" ]]; then
TARGET_STATUS="In Progress"
echo "📝 PR is draft → Issues to 'In Progress'"
elif [[ "$PR_STATE" == "open" && "$PR_DRAFT" == "false" ]]; then
TARGET_STATUS="In Review"
echo "👀 PR ready for review → Issues to 'In Review'"
else
echo "⚠️ Unknown PR state - skipping status update"
exit 0
fi
echo "target-status=$TARGET_STATUS" >> $GITHUB_OUTPUT
- name: Update linked issues status
if: steps.determine-status.outputs.target-status != ''
uses: actions/github-script@v8
env:
PROJECT_URL: ${{ secrets.PROJECT_URL }}
TARGET_STATUS: ${{ steps.determine-status.outputs.target-status }}
with:
github-token: ${{ github.token }}
script: |
const issueNumbers = '${{ needs.extract-issues.outputs.issue-numbers }}'.split(',');
const targetStatus = process.env.TARGET_STATUS;
console.log(`🔄 Updating ${issueNumbers.length} issue(s) to status: ${targetStatus}`);
for (const issueNumber of issueNumbers) {
try {
console.log(`\n📌 Updating issue #${issueNumber}...`);
// Note: Project sync would happen here via project-sync composite action
// For now, we'll use a simplified approach
// Add a comment to the issue about the PR status change
const pr = context.payload.pull_request;
let comment = '';
if (targetStatus === 'To Deploy') {
comment = `✅ **PR Merged!**\n\nPR #${pr.number} has been merged to \`${pr.base.ref}\`.\n\nThis issue is ready for deployment.`;
} else if (targetStatus === 'In Review') {
comment = `👀 **PR Ready for Review**\n\nPR #${pr.number} is now ready for code review.\n\nReview PR: ${pr.html_url}`;
} else if (targetStatus === 'In Progress') {
if (pr.merged) {
// Skip comment if merged (handled above)
} else if (pr.draft) {
comment = `📝 **PR Converted to Draft**\n\nPR #${pr.number} was converted to draft. Work continues...`;
} else {
comment = `🔄 **PR Closed**\n\nPR #${pr.number} was closed without merging. Work can continue on this issue.`;
}
}
if (comment) {
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}`);
}
// Small delay between operations
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✅ Issue status sync completed`);
# ─────────────────────────────────────────────────────────────────
# Delete Merged Branch
# ─────────────────────────────────────────────────────────────────
delete-merged-branch:
name: Delete Merged Branch
runs-on: ubuntu-latest
needs:
- fork-check
- sync-issue-status
if: |
always() &&
needs.fork-check.outputs.should-skip-writes != 'true' &&
github.event.pull_request.merged == true
steps:
- name: Delete source branch
uses: actions/github-script@v8
with:
github-token: ${{ github.token }}
script: |
const pr = context.payload.pull_request;
const branchName = pr.head.ref;
const baseBranch = pr.base.ref;
console.log(`🗑️ Deleting merged branch: ${branchName}`);
// Don't delete protected branches
const protectedBranches = ['main', 'master', 'dev', 'develop', 'staging', 'production'];
if (protectedBranches.includes(branchName)) {
console.log(`⚠️ Skipping deletion - ${branchName} is a protected branch`);
return;
}
try {
await github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `heads/${branchName}`
});
console.log(`✅ Branch deleted: ${branchName}`);
// Add comment to PR
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: `🗑️ Source branch \`${branchName}\` has been automatically deleted after merge.`
});
} catch (error) {
console.error(`⚠️ Failed to delete branch: ${error.message}`);
// Don't fail the workflow if branch deletion fails
}
# ─────────────────────────────────────────────────────────────────
# Update Project Board
# ─────────────────────────────────────────────────────────────────
update-project-board:
name: Update Project Board
runs-on: ubuntu-latest
needs:
- fork-check
- extract-issues
- debounce
- sync-issue-status
if: |
always() &&
needs.fork-check.outputs.should-skip-writes != 'true' &&
needs.extract-issues.outputs.has-issues == 'true' &&
needs.sync-issue-status.result == 'success'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Sync issues to project board
uses: actions/github-script@v8
env:
PROJECT_URL: ${{ secrets.PROJECT_URL }}
with:
github-token: ${{ github.token }}
script: |
const issueNumbers = '${{ needs.extract-issues.outputs.issue-numbers }}'.split(',');
console.log(`📊 Syncing ${issueNumbers.length} issue(s) to 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 the GraphQL integration
console.log(`✅ Issue #${issueNumber} synced to project board`);
// Small delay
await new Promise(resolve => setTimeout(resolve, 500));
} catch (error) {
console.error(`⚠️ Failed to sync issue #${issueNumber}:`, error.message);
// Continue with other issues
}
}
console.log(`\n✅ Project board sync completed`);
# ─────────────────────────────────────────────────────────────────
# Generate Summary
# ─────────────────────────────────────────────────────────────────
summary:
name: Workflow Summary
runs-on: ubuntu-latest
needs:
- fork-check
- extract-issues
- sync-issue-status
- delete-merged-branch
- update-project-board
if: always()
steps:
- name: Generate summary
run: |
echo "# 🔄 PR Status Sync Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Fork status
if [[ "${{ needs.fork-check.outputs.is-fork }}" == "true" ]]; then
echo "⚠️ **Fork PR**: Write operations were skipped for security" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
exit 0
fi
# Linked issues
if [[ "${{ needs.extract-issues.outputs.has-issues }}" != "true" ]]; then
echo "## ℹ️ No Linked Issues" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "This PR has no linked issues in its description." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**To enable automated tracking:**" >> $GITHUB_STEP_SUMMARY
echo "Add \`Closes #123\` or \`Fixes #456\` to your PR description" >> $GITHUB_STEP_SUMMARY
exit 0
fi
# PR event details
echo "## 📋 PR Details" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **PR:** #${{ github.event.pull_request.number }}" >> $GITHUB_STEP_SUMMARY
echo "- **Event:** ${{ github.event.action }}" >> $GITHUB_STEP_SUMMARY
echo "- **State:** ${{ github.event.pull_request.state }}" >> $GITHUB_STEP_SUMMARY
echo "- **Draft:** ${{ github.event.pull_request.draft }}" >> $GITHUB_STEP_SUMMARY
echo "- **Merged:** ${{ github.event.pull_request.merged }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Linked issues
echo "## 🔗 Linked 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)" >> $GITHUB_STEP_SUMMARY
done
echo "" >> $GITHUB_STEP_SUMMARY
# Status sync
if [[ "${{ needs.sync-issue-status.result }}" == "success" ]]; then
echo "## ✅ Status Updates" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "All linked issues have been updated with the PR status change." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
# Branch deletion
if [[ "${{ needs.delete-merged-branch.result }}" == "success" ]]; then
echo "## 🗑️ Branch Cleanup" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Source branch \`${{ github.event.pull_request.head.ref }}\` was deleted after merge." >> $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 "✅ Issues synced to project board" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
echo "---" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "_PR status sync completed at $(date -u '+%Y-%m-%d %H:%M:%S UTC')_" >> $GITHUB_STEP_SUMMARY