-
-
Notifications
You must be signed in to change notification settings - Fork 11
430 lines (360 loc) · 17.7 KB
/
release-status-sync.yml
File metadata and controls
430 lines (360 loc) · 17.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# ─────────────────────────────────────────────────────────────────
# 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@v6
- 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@v8
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@v8
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@v6
- name: Update issues to Done status
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(`📊 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@v6
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