@codyswann/lisa 1.51.5 → 1.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1593 +0,0 @@
1
- # This file is managed by Lisa.
2
- # Do not edit directly — changes will be overwritten on the next `lisa` run.
3
-
4
- name: Enhanced Release Workflow v4 (with Enterprise Features)
5
-
6
- on:
7
- workflow_call:
8
- inputs:
9
- environment:
10
- description: 'Target environment for the release'
11
- required: true
12
- type: string
13
- skip_jobs:
14
- description: 'Comma-separated list of jobs to skip (test,type-check,lint,format,audit,cdk-synth)'
15
- required: false
16
- type: string
17
- default: ''
18
- force_release:
19
- description: 'Force create a release even if no changes detected'
20
- required: false
21
- type: boolean
22
- default: false
23
- release_strategy:
24
- description: 'Version strategy: standard-version, semantic, calendar, custom'
25
- required: false
26
- type: string
27
- default: 'standard-version'
28
- prerelease:
29
- description: 'Create pre-release (alpha, beta, rc)'
30
- required: false
31
- type: string
32
- default: ''
33
- custom_version:
34
- description: 'Custom version number (only for custom strategy)'
35
- required: false
36
- type: string
37
- default: ''
38
- jira_project_key:
39
- description: 'JIRA project key for release creation'
40
- required: false
41
- type: string
42
- default: ''
43
- release_notes_template:
44
- description: 'Template for release notes (default, detailed, security)'
45
- required: false
46
- type: string
47
- default: 'default'
48
- debug:
49
- description: 'Enable debug logging'
50
- required: false
51
- type: boolean
52
- default: false
53
- require_approval:
54
- description: 'Require approval before creating release'
55
- required: false
56
- type: boolean
57
- default: false
58
- approval_environment:
59
- description: 'GitHub environment for approval (if require_approval is true)'
60
- required: false
61
- type: string
62
- default: ''
63
- require_signatures:
64
- description: 'Require signed releases (needs RELEASE_SIGNING_KEY secret)'
65
- required: false
66
- type: boolean
67
- default: false
68
- check_dependencies:
69
- description: 'Check service dependencies before release'
70
- required: false
71
- type: boolean
72
- default: false
73
- override_blackout:
74
- description: 'Override blackout period restrictions'
75
- required: false
76
- type: boolean
77
- default: false
78
- emergency_release:
79
- description: 'Mark as emergency release'
80
- required: false
81
- type: boolean
82
- default: false
83
- generate_sbom:
84
- description: 'Generate Software Bill of Materials'
85
- required: false
86
- type: boolean
87
- default: false
88
- node_version:
89
- description: 'Node.js version to use'
90
- required: true
91
- type: string
92
- package_manager:
93
- description: 'Package manager to use (npm, yarn, pnpm, bun)'
94
- required: false
95
- type: string
96
- default: 'npm'
97
- sourcemaps:
98
- description: 'Path to source maps for Sentry release (optional)'
99
- required: false
100
- type: string
101
- default: ''
102
- outputs:
103
- version:
104
- description: 'The new version number'
105
- value: ${{ jobs.version.outputs.version }}
106
- tag:
107
- description: 'The new version tag'
108
- value: ${{ jobs.version.outputs.tag }}
109
- release_url:
110
- description: 'URL of the created GitHub release'
111
- value: ${{ jobs.github_release.outputs.release_url }}
112
- release_id:
113
- description: 'Release correlation ID for tracking'
114
- value: ${{ jobs.release_init.outputs.correlation_id }}
115
- release_metrics_url:
116
- description: 'URL to release metrics dashboard'
117
- value: ${{ jobs.release_analytics.outputs.dashboard_url }}
118
- signed:
119
- description: 'Whether the release was signed'
120
- value: ${{ jobs.release_signing.outputs.signed }}
121
- attestation_url:
122
- description: 'URL to release attestation'
123
- value: ${{ jobs.release_attestation.outputs.attestation_url }}
124
- compliance_status:
125
- description: 'Compliance validation status'
126
- value: ${{ jobs.release_compliance.outputs.status }}
127
- sentry_release_url:
128
- description: 'URL to Sentry release'
129
- value: ${{ jobs.sentry_release.outputs.sentry_release_url }}
130
- jira_release_created:
131
- description: 'Whether the Jira release was created'
132
- value: ${{ jobs.jira_release.outputs.jira_release_created }}
133
- jira_release_url:
134
- description: 'URL to Jira release'
135
- value: ${{ jobs.jira_release.outputs.jira_release_url }}
136
- secrets:
137
- DEPLOY_KEY:
138
- description: 'SSH deploy key with write access to bypass branch protection (add to ruleset bypass list)'
139
- required: false
140
- RELEASE_SIGNING_KEY:
141
- required: false
142
- SIGNING_KEY_ID:
143
- required: false
144
- SIGNING_KEY_PASSPHRASE:
145
- required: false
146
- SENTRY_AUTH_TOKEN:
147
- required: false
148
- SENTRY_ORG:
149
- required: false
150
- SENTRY_PROJECT:
151
- required: false
152
-
153
- jobs:
154
- # Initialize release with correlation ID and logging
155
- release_init:
156
- name: 🚀 Initialize Release
157
- runs-on: ubuntu-latest
158
- outputs:
159
- correlation_id: ${{ steps.init.outputs.correlation_id }}
160
- start_time: ${{ steps.init.outputs.start_time }}
161
- release_logger: ${{ steps.init.outputs.logger_created }}
162
- steps:
163
- - name: Generate Release Correlation ID
164
- id: init
165
- run: |
166
- # Generate unique correlation ID
167
- CORRELATION_ID="rel-$(date +%Y%m%d%H%M%S)-${{ github.run_id }}"
168
- echo "correlation_id=$CORRELATION_ID" >> $GITHUB_OUTPUT
169
- echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT
170
- echo "logger_created=true" >> $GITHUB_OUTPUT
171
-
172
- # Initialize release context
173
- echo "## 🚀 Release Workflow Started" >> $GITHUB_STEP_SUMMARY
174
- echo "- **Correlation ID**: $CORRELATION_ID" >> $GITHUB_STEP_SUMMARY
175
- echo "- **Environment**: ${{ inputs.environment }}" >> $GITHUB_STEP_SUMMARY
176
- echo "- **Strategy**: ${{ inputs.release_strategy }}" >> $GITHUB_STEP_SUMMARY
177
- echo "- **Approval Required**: ${{ inputs.require_approval }}" >> $GITHUB_STEP_SUMMARY
178
- echo "- **Signatures Required**: ${{ inputs.require_signatures }}" >> $GITHUB_STEP_SUMMARY
179
- echo "- **Emergency Release**: ${{ inputs.emergency_release }}" >> $GITHUB_STEP_SUMMARY
180
-
181
- # Check if this is a promotion merge (env branch to env branch)
182
- check_promotion:
183
- name: 🔍 Check Promotion
184
- runs-on: ubuntu-latest
185
- outputs:
186
- is_promotion: ${{ steps.check.outputs.is_promotion }}
187
- skip_reason: ${{ steps.check.outputs.skip_reason }}
188
- steps:
189
- - uses: actions/checkout@v4
190
- with:
191
- fetch-depth: 10 # Need some history to analyze merge commits
192
-
193
- - name: Check if this is a promotion merge
194
- id: check
195
- run: |
196
- COMMIT_MSG=$(git log -1 --pretty=%B)
197
- IS_PROMOTION=false
198
- SKIP_REASON=""
199
-
200
- # Environment branches that we track
201
- ENV_BRANCHES="dev|staging|main|master|production"
202
-
203
- # Check for merge commits from environment branches to environment branches
204
- # Pattern 1: "Merge branch 'staging' into main"
205
- if echo "$COMMIT_MSG" | grep -qiE "^Merge branch ['\"]?($ENV_BRANCHES)['\"]? into ($ENV_BRANCHES)"; then
206
- IS_PROMOTION=true
207
- SKIP_REASON="Merge from environment branch detected"
208
- fi
209
-
210
- # Pattern 2: "Merge pull request #123 from org/staging" or similar
211
- if echo "$COMMIT_MSG" | grep -qiE "^Merge pull request.*from .*/($ENV_BRANCHES)$"; then
212
- IS_PROMOTION=true
213
- SKIP_REASON="PR merge from environment branch detected"
214
- fi
215
-
216
- # Note: Pattern 3 (checking parent branches) was removed because it caused false positives.
217
- # After merging a feature branch into main, `git branch -a --contains` shows main for BOTH
218
- # parents since main now contains both commits. Patterns 1 and 2 are sufficient.
219
-
220
- echo "is_promotion=$IS_PROMOTION" >> $GITHUB_OUTPUT
221
- echo "skip_reason=$SKIP_REASON" >> $GITHUB_OUTPUT
222
-
223
- if [ "$IS_PROMOTION" = "true" ]; then
224
- echo "## ⏭️ Promotion Detected" >> $GITHUB_STEP_SUMMARY
225
- echo "" >> $GITHUB_STEP_SUMMARY
226
- echo "**Reason**: $SKIP_REASON" >> $GITHUB_STEP_SUMMARY
227
- echo "" >> $GITHUB_STEP_SUMMARY
228
- echo "Version bump will be skipped to avoid duplicate versioning." >> $GITHUB_STEP_SUMMARY
229
- echo "" >> $GITHUB_STEP_SUMMARY
230
- echo "**Commit message**:" >> $GITHUB_STEP_SUMMARY
231
- echo '```' >> $GITHUB_STEP_SUMMARY
232
- echo "$COMMIT_MSG" | head -5 >> $GITHUB_STEP_SUMMARY
233
- echo '```' >> $GITHUB_STEP_SUMMARY
234
- else
235
- echo "✅ Not a promotion merge - version bump will proceed" >> $GITHUB_STEP_SUMMARY
236
- fi
237
-
238
- # Check release schedule and blackout periods
239
- release_schedule:
240
- name: 📅 Release Schedule Check
241
- needs: [release_init]
242
- runs-on: ubuntu-latest
243
- outputs:
244
- allowed: ${{ steps.schedule.outputs.allowed }}
245
- blackout_reason: ${{ steps.schedule.outputs.reason }}
246
- steps:
247
- - name: Check Release Schedule
248
- id: schedule
249
- run: |
250
- # Check if we're in a blackout period
251
- CURRENT_DATE=$(date +%Y-%m-%d)
252
- CURRENT_TIME=$(date +%H:%M)
253
- CURRENT_DAY=$(date +%A)
254
- CURRENT_HOUR=$(date +%H)
255
-
256
- IS_BLACKOUT=false
257
- BLACKOUT_REASON=""
258
-
259
- # Check environment-specific blackout rules
260
- case "${{ inputs.environment }}" in
261
- production|prod|main)
262
- # No weekend releases for production
263
- if [[ "$CURRENT_DAY" == "Saturday" || "$CURRENT_DAY" == "Sunday" ]]; then
264
- IS_BLACKOUT=true
265
- BLACKOUT_REASON="Weekend release restriction for production"
266
- fi
267
-
268
- # No late night releases (10 PM - 6 AM)
269
- if [[ $CURRENT_HOUR -ge 22 || $CURRENT_HOUR -lt 6 ]]; then
270
- IS_BLACKOUT=true
271
- BLACKOUT_REASON="Late night release restriction (10 PM - 6 AM)"
272
- fi
273
-
274
- # Holiday blackouts
275
- HOLIDAYS=(
276
- "2024-12-24:2025-01-02:Holiday Season"
277
- "2025-07-03:2025-07-05:Independence Day"
278
- "2025-11-27:2025-11-29:Thanksgiving"
279
- )
280
-
281
- for holiday in "${HOLIDAYS[@]}"; do
282
- IFS=':' read -r start end name <<< "$holiday"
283
- if [[ "$CURRENT_DATE" > "$start" && "$CURRENT_DATE" < "$end" ]]; then
284
- IS_BLACKOUT=true
285
- BLACKOUT_REASON="$name blackout period"
286
- break
287
- fi
288
- done
289
- ;;
290
-
291
- staging|stage)
292
- # Staging has fewer restrictions
293
- if [[ "$CURRENT_DAY" == "Sunday" && $CURRENT_HOUR -ge 20 ]]; then
294
- IS_BLACKOUT=true
295
- BLACKOUT_REASON="Sunday evening restriction for staging"
296
- fi
297
- ;;
298
- esac
299
-
300
- # Handle blackout
301
- if [ "$IS_BLACKOUT" == "true" ]; then
302
- if [ "${{ inputs.override_blackout }}" == "true" ]; then
303
- echo "⚠️ Blackout override enabled: $BLACKOUT_REASON"
304
- echo "allowed=true" >> $GITHUB_OUTPUT
305
- echo "reason=Override: $BLACKOUT_REASON" >> $GITHUB_OUTPUT
306
- else
307
- echo "❌ Release blocked: $BLACKOUT_REASON" >> $GITHUB_STEP_SUMMARY
308
- echo "Use override_blackout=true to force release" >> $GITHUB_STEP_SUMMARY
309
- echo "allowed=false" >> $GITHUB_OUTPUT
310
- echo "reason=$BLACKOUT_REASON" >> $GITHUB_OUTPUT
311
- fi
312
- else
313
- echo "✅ Release schedule check passed" >> $GITHUB_STEP_SUMMARY
314
- echo "allowed=true" >> $GITHUB_OUTPUT
315
- echo "reason=No blackout restrictions" >> $GITHUB_OUTPUT
316
- fi
317
-
318
- - name: Enforce Schedule
319
- if: steps.schedule.outputs.allowed != 'true'
320
- run: |
321
- echo "::error::Release blocked due to blackout period: ${{ steps.schedule.outputs.reason }}"
322
- exit 1
323
-
324
- # Optional approval gate
325
- release_approval:
326
- name: 🚦 Release Approval
327
- needs: [release_init, release_schedule]
328
- if: ${{ inputs.require_approval == true }}
329
- runs-on: ubuntu-latest
330
- environment:
331
- name: ${{ inputs.approval_environment || inputs.environment }}
332
- outputs:
333
- approved: ${{ steps.approval.outputs.approved }}
334
- approver: ${{ steps.approval.outputs.approver }}
335
- approval_time: ${{ steps.approval.outputs.time }}
336
- steps:
337
- - name: Request Approval
338
- id: approval
339
- run: |
340
- echo "## 🚦 Release Approval Required" >> $GITHUB_STEP_SUMMARY
341
- echo "" >> $GITHUB_STEP_SUMMARY
342
- echo "**Environment**: ${{ inputs.environment }}" >> $GITHUB_STEP_SUMMARY
343
- echo "**Requester**: @${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
344
- echo "**Correlation ID**: ${{ needs.release_init.outputs.correlation_id }}" >> $GITHUB_STEP_SUMMARY
345
- echo "" >> $GITHUB_STEP_SUMMARY
346
- echo "This release requires manual approval to proceed." >> $GITHUB_STEP_SUMMARY
347
-
348
- # If we reach here, approval was granted (environment protection rules)
349
- echo "approved=true" >> $GITHUB_OUTPUT
350
- echo "approver=${{ github.actor }}" >> $GITHUB_OUTPUT
351
- echo "time=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
352
-
353
- echo "✅ Release approved by environment protection rules" >> $GITHUB_STEP_SUMMARY
354
-
355
- # Run quality checks
356
- quality:
357
- name: 🔍 Quality Checks
358
- needs: [release_init, release_schedule]
359
- uses: ./.github/workflows/quality.yml
360
- with:
361
- skip_jobs: ${{ inputs.skip_jobs }}
362
- node_version: ${{ inputs.node_version }}
363
- package_manager: ${{ inputs.package_manager }}
364
- secrets: inherit
365
-
366
- # Version management with observability
367
- version:
368
- name: 📦 Version Management
369
- needs: [release_init, quality, release_approval, check_promotion]
370
- if: |
371
- always() && !cancelled() &&
372
- needs.quality.result == 'success' &&
373
- (needs.release_approval.result == 'success' || needs.release_approval.result == 'skipped') &&
374
- needs.check_promotion.outputs.is_promotion != 'true'
375
- runs-on: ubuntu-latest
376
- permissions:
377
- contents: write
378
- env:
379
- HUSKY: '0'
380
- outputs:
381
- version: ${{ steps.version.outputs.version }}
382
- tag: ${{ steps.version.outputs.tag }}
383
- prerelease: ${{ steps.version.outputs.prerelease }}
384
- bump_type: ${{ steps.version.outputs.bump_type }}
385
- strategy: ${{ inputs.release_strategy }}
386
- changelog_generated: ${{ steps.changelog.outputs.generated }}
387
- steps:
388
- # Use SSH deploy key for pushing to protected branches
389
- # Setup: Add DEPLOY_KEY secret and add the deploy key to ruleset bypass list
390
- - uses: actions/checkout@v4
391
- with:
392
- fetch-depth: 0
393
- ssh-key: ${{ secrets.DEPLOY_KEY }}
394
-
395
- - name: Setup Release Logger
396
- id: logger
397
- run: |
398
- # Create logging utilities
399
- cat > release-logger.sh << 'EOF'
400
- #!/bin/bash
401
-
402
- # Correlation ID from init job
403
- RELEASE_CORRELATION_ID="${{ needs.release_init.outputs.correlation_id }}"
404
- export RELEASE_CORRELATION_ID
405
-
406
- # Structured log function
407
- log_release_event() {
408
- local event_type="$1"
409
- local event_status="$2"
410
- local event_message="$3"
411
- shift 3
412
-
413
- # Additional key-value pairs
414
- local additional_fields=""
415
- while [ $# -gt 0 ] && [ $# -ge 2 ]; do
416
- additional_fields="$additional_fields,\"$1\":\"$2\""
417
- shift 2
418
- done
419
-
420
- # Create structured log entry
421
- local log_entry=$(jq -n \
422
- --arg correlation_id "$RELEASE_CORRELATION_ID" \
423
- --arg workflow_id "${{ github.run_id }}" \
424
- --arg event_type "$event_type" \
425
- --arg event_status "$event_status" \
426
- --arg event_message "$event_message" \
427
- --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)" \
428
- --arg environment "${{ inputs.environment }}" \
429
- --arg version "${VERSION:-unknown}" \
430
- --arg tag "${TAG:-unknown}" \
431
- --arg actor "${{ github.actor }}" \
432
- --arg repository "${{ github.repository }}" \
433
- --arg ref "${{ github.ref }}" \
434
- --arg sha "${{ github.sha }}" \
435
- "{
436
- \"correlation_id\": \$correlation_id,
437
- \"workflow_id\": \$workflow_id,
438
- \"event_type\": \$event_type,
439
- \"event_status\": \$event_status,
440
- \"event_message\": \$event_message,
441
- \"timestamp\": \$timestamp,
442
- \"release\": {
443
- \"environment\": \$environment,
444
- \"version\": \$version,
445
- \"tag\": \$tag
446
- },
447
- \"context\": {
448
- \"actor\": \$actor,
449
- \"repository\": \$repository,
450
- \"ref\": \$ref,
451
- \"sha\": \$sha
452
- }${additional_fields}
453
- }")
454
-
455
- # Output to stdout and file
456
- echo "$log_entry" | tee -a release-events.jsonl
457
-
458
- # Also log to GitHub Actions if debug enabled
459
- if [ "${{ inputs.debug }}" == "true" ]; then
460
- echo "::notice title=Release Event::$event_type - $event_status: $event_message"
461
- fi
462
- }
463
-
464
- export -f log_release_event
465
- EOF
466
-
467
- chmod +x release-logger.sh
468
- source ./release-logger.sh
469
-
470
- # Log version process start
471
- log_release_event "version_management" "started" "Version determination process started" \
472
- "strategy" "${{ inputs.release_strategy }}" \
473
- "approval_required" "${{ inputs.require_approval }}" \
474
- "signatures_required" "${{ inputs.require_signatures }}"
475
-
476
- - name: Setup Node.js
477
- uses: actions/setup-node@v4
478
- with:
479
- node-version: ${{ inputs.node_version }}
480
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
481
-
482
- - name: Setup Bun
483
- if: inputs.package_manager == 'bun'
484
- uses: oven-sh/setup-bun@v2
485
- with:
486
- bun-version: '1.3.8'
487
-
488
- - name: Install dependencies
489
- run: |
490
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
491
- npm ci
492
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
493
- yarn install --frozen-lockfile
494
- elif [ "${{ inputs.package_manager }}" = "pnpm" ]; then
495
- pnpm install --frozen-lockfile
496
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
497
- bun install --frozen-lockfile
498
- else
499
- npm ci
500
- fi
501
-
502
- - name: Determine Version
503
- id: version
504
- run: |
505
- source ./release-logger.sh
506
-
507
- # Version determination logic (same as v3)
508
- case "${{ inputs.release_strategy }}" in
509
- "standard-version")
510
- VERSION=$(npx standard-version --dry-run | grep "tagging release" | awk '{print $4}')
511
- ;;
512
- "semantic")
513
- if git log --format=%B -n 50 --no-merges | grep -qE "^(BREAKING CHANGE:|.*!:)"; then
514
- BUMP_TYPE="major"
515
- elif git log --format=%B -n 50 --no-merges | grep -qE "^feat(\(.+\))?:"; then
516
- BUMP_TYPE="minor"
517
- else
518
- BUMP_TYPE="patch"
519
- fi
520
-
521
- CURRENT_VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
522
- VERSION=$(npx semver -i $BUMP_TYPE ${CURRENT_VERSION#v})
523
- ;;
524
- "calendar")
525
- VERSION=$(date +%Y.%m.%d)
526
- if [ -n "${{ inputs.prerelease }}" ]; then
527
- VERSION="${VERSION}-${{ inputs.prerelease }}.$(date +%H%M)"
528
- fi
529
- ;;
530
- "custom")
531
- VERSION="${{ inputs.custom_version }}"
532
- if [ -z "$VERSION" ]; then
533
- log_release_event "version_error" "failed" "Custom version not provided"
534
- exit 1
535
- fi
536
- ;;
537
- esac
538
-
539
- # Add prerelease suffix if specified
540
- if [ -n "${{ inputs.prerelease }}" ] && [ "${{ inputs.release_strategy }}" != "calendar" ]; then
541
- VERSION="${VERSION}-${{ inputs.prerelease }}.$(date +%s)"
542
- echo "prerelease=true" >> $GITHUB_OUTPUT
543
- fi
544
-
545
- echo "version=$VERSION" >> $GITHUB_OUTPUT
546
- echo "tag=v$VERSION" >> $GITHUB_OUTPUT
547
- echo "bump_type=${BUMP_TYPE:-custom}" >> $GITHUB_OUTPUT
548
-
549
- export VERSION TAG="v$VERSION"
550
-
551
- log_release_event "version_determination" "completed" "Version determined successfully" \
552
- "version" "$VERSION" \
553
- "bump_type" "${BUMP_TYPE:-custom}"
554
-
555
- - name: Configure Git
556
- if: inputs.release_strategy == 'standard-version'
557
- run: |
558
- git config user.name "github-actions[bot]"
559
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
560
-
561
- - name: Generate Changelog
562
- id: changelog
563
- run: |
564
- source ./release-logger.sh
565
-
566
- log_release_event "changelog_generation" "started" "Generating changelog and release notes"
567
-
568
- # Generate changelog based on strategy
569
- if [ "${{ inputs.release_strategy }}" == "standard-version" ]; then
570
- npx standard-version --release-as ${{ steps.version.outputs.version }} --skip.tag --releaseCommitMessageFormat "chore(release): {{currentTag}} [skip ci]"
571
- else
572
- # Custom changelog generation
573
- LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
574
- if [ -n "$LAST_TAG" ]; then
575
- COMMIT_RANGE="$LAST_TAG..HEAD"
576
- else
577
- COMMIT_RANGE="HEAD"
578
- fi
579
-
580
- # Generate changelog with categories
581
- cat > CHANGELOG_ENTRY.md << EOF
582
- ## [${{ steps.version.outputs.version }}] - $(date +%Y-%m-%d)
583
-
584
- ### Release Information
585
- - **Environment**: ${{ inputs.environment }}
586
- - **Release Type**: ${{ inputs.release_strategy }}
587
- - **Correlation ID**: ${{ needs.release_init.outputs.correlation_id }}
588
- - **Emergency Release**: ${{ inputs.emergency_release }}
589
- - **Approved By**: ${{ needs.release_approval.outputs.approver || 'N/A' }}
590
-
591
- EOF
592
-
593
- # Add commit categories
594
- if git log $COMMIT_RANGE --format=%B | grep -qE "^(feat|feature)(\(.+\))?:"; then
595
- echo "### ✨ Features" >> CHANGELOG_ENTRY.md
596
- git log $COMMIT_RANGE --format="- %s (%h)" --grep="^feat" --grep="^feature" >> CHANGELOG_ENTRY.md
597
- echo "" >> CHANGELOG_ENTRY.md
598
- fi
599
-
600
- if git log $COMMIT_RANGE --format=%B | grep -qE "^fix(\(.+\))?:"; then
601
- echo "### 🐛 Bug Fixes" >> CHANGELOG_ENTRY.md
602
- git log $COMMIT_RANGE --format="- %s (%h)" --grep="^fix" >> CHANGELOG_ENTRY.md
603
- echo "" >> CHANGELOG_ENTRY.md
604
- fi
605
-
606
- # Update CHANGELOG.md
607
- if [ -f CHANGELOG.md ]; then
608
- cp CHANGELOG.md CHANGELOG.md.bak
609
- echo -e "# Changelog\n" > CHANGELOG.md
610
- cat CHANGELOG_ENTRY.md >> CHANGELOG.md
611
- echo "" >> CHANGELOG.md
612
- tail -n +2 CHANGELOG.md.bak >> CHANGELOG.md
613
- else
614
- echo -e "# Changelog\n" > CHANGELOG.md
615
- cat CHANGELOG_ENTRY.md >> CHANGELOG.md
616
- fi
617
- fi
618
-
619
- echo "generated=true" >> $GITHUB_OUTPUT
620
-
621
- log_release_event "changelog_generation" "completed" "Changelog generated successfully"
622
-
623
- - name: Push Changelog Changes
624
- if: inputs.release_strategy == 'standard-version'
625
- run: |
626
- git push origin HEAD:${{ github.ref_name }}
627
-
628
- - name: Generate SBOM
629
- if: ${{ inputs.generate_sbom == true }}
630
- continue-on-error: true
631
- run: |
632
- source ./release-logger.sh
633
-
634
- log_release_event "sbom_generation" "started" "Generating Software Bill of Materials"
635
-
636
- # Install SBOM generator
637
- npm install -g @cyclonedx/cyclonedx-npm
638
-
639
- # Generate SBOM in CycloneDX format
640
- # Note: Using || true to prevent failure on warnings
641
- cyclonedx-npm --output-format json --output-file sbom.json || {
642
- echo "::warning::SBOM generation encountered issues but continuing"
643
- echo '{"components":[],"metadata":{"timestamp":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}}' > sbom.json
644
- }
645
-
646
- # Add release metadata to SBOM
647
- jq --arg version "${{ steps.version.outputs.version }}" \
648
- --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
649
- --arg sha "${{ github.sha }}" \
650
- '.metadata.component.version = $version |
651
- .metadata.timestamp = $timestamp |
652
- .metadata.component["bom-ref"] = $sha' \
653
- sbom.json > sbom-release.json
654
-
655
- mv sbom-release.json sbom.json
656
-
657
- log_release_event "sbom_generation" "completed" "SBOM generated successfully" \
658
- "format" "CycloneDX" \
659
- "components" "$(jq '.components | length' sbom.json)"
660
-
661
- - name: Upload Version Artifacts
662
- uses: actions/upload-artifact@v4
663
- with:
664
- name: version-artifacts-${{ github.run_id }}
665
- path: |
666
- release-events.jsonl
667
- CHANGELOG.md
668
- CHANGELOG_ENTRY.md
669
- sbom.json
670
- retention-days: 90
671
-
672
- # Release signing
673
- release_signing:
674
- name: 🔏 Release Signing
675
- needs: [release_init, version]
676
- if: inputs.require_signatures == true
677
- runs-on: ubuntu-latest
678
- outputs:
679
- signed: ${{ steps.sign.outputs.signed }}
680
- signature_files: ${{ steps.sign.outputs.files }}
681
- steps:
682
- - uses: actions/checkout@v4
683
- with:
684
- fetch-depth: 0
685
- ref: ${{ github.sha }}
686
-
687
- - name: Download Version Artifacts
688
- uses: actions/download-artifact@v4
689
- with:
690
- name: version-artifacts-${{ github.run_id }}
691
-
692
- - name: Setup Signing Environment
693
- id: setup
694
- run: |
695
- # Install signing tools
696
- if [ "${{ runner.os }}" == "Linux" ]; then
697
- sudo apt-get update
698
- sudo apt-get install -y gnupg2
699
- fi
700
-
701
- # Check if signing key is available
702
- if [ -n "${{ secrets.RELEASE_SIGNING_KEY }}" ]; then
703
- echo "✅ Signing key available"
704
- echo "signing_available=true" >> $GITHUB_OUTPUT
705
- else
706
- echo "⚠️ No signing key available"
707
- if [ "${{ inputs.require_signatures }}" == "true" ]; then
708
- echo "::error::Signatures required but RELEASE_SIGNING_KEY not configured"
709
- exit 1
710
- fi
711
- echo "signing_available=false" >> $GITHUB_OUTPUT
712
- fi
713
-
714
- - name: Import Signing Key
715
- if: steps.setup.outputs.signing_available == 'true'
716
- run: |
717
- # Import GPG key
718
- echo "${{ secrets.RELEASE_SIGNING_KEY }}" | base64 -d > signing.key
719
-
720
- if [ -n "${{ secrets.SIGNING_KEY_PASSPHRASE }}" ]; then
721
- echo "${{ secrets.SIGNING_KEY_PASSPHRASE }}" | \
722
- gpg --batch --yes --passphrase-fd 0 --import signing.key
723
- else
724
- gpg --batch --yes --import signing.key
725
- fi
726
-
727
- rm -f signing.key
728
-
729
- # List imported keys
730
- gpg --list-secret-keys
731
-
732
- # Configure git
733
- git config --global user.signingkey "${{ secrets.SIGNING_KEY_ID || secrets.RELEASE_SIGNING_KEY_ID }}"
734
- git config --global commit.gpgsign true
735
- git config --global tag.gpgsign true
736
-
737
- - name: Sign Release Artifacts
738
- id: sign
739
- if: steps.setup.outputs.signing_available == 'true'
740
- run: |
741
- # Function to sign files
742
- sign_artifact() {
743
- local file="$1"
744
- local sig_file="${file}.sig"
745
- local sha_file="${file}.sha256"
746
-
747
- if [ -f "$file" ]; then
748
- # Create detached signature
749
- gpg --armor --detach-sign \
750
- --local-user "${{ secrets.SIGNING_KEY_ID || secrets.RELEASE_SIGNING_KEY_ID }}" \
751
- --output "$sig_file" \
752
- "$file"
753
-
754
- # Generate checksum
755
- sha256sum "$file" | cut -d' ' -f1 > "$sha_file"
756
-
757
- echo "✅ Signed: $file"
758
- return 0
759
- else
760
- echo "⚠️ File not found: $file"
761
- return 1
762
- fi
763
- }
764
-
765
- # Sign release artifacts
766
- SIGNED_FILES=()
767
-
768
- # Sign changelog
769
- if sign_artifact "CHANGELOG.md"; then
770
- SIGNED_FILES+=("CHANGELOG.md")
771
- fi
772
-
773
- # Sign SBOM if generated
774
- if [ -f "sbom.json" ] && sign_artifact "sbom.json"; then
775
- SIGNED_FILES+=("sbom.json")
776
- fi
777
-
778
- # Sign package.json
779
- if sign_artifact "package.json"; then
780
- SIGNED_FILES+=("package.json")
781
- fi
782
-
783
- # Create release manifest
784
- cat > release-manifest.json << EOF
785
- {
786
- "version": "${{ needs.version.outputs.version }}",
787
- "tag": "${{ needs.version.outputs.tag }}",
788
- "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
789
- "sha": "${{ github.sha }}",
790
- "signed_by": "${{ secrets.SIGNING_KEY_ID || 'release-bot' }}",
791
- "artifacts": [
792
- EOF
793
-
794
- # Add artifact entries
795
- first=true
796
- for file in "${SIGNED_FILES[@]}"; do
797
- if [ "$first" != "true" ]; then
798
- echo "," >> release-manifest.json
799
- fi
800
- first=false
801
-
802
- cat >> release-manifest.json << EOF
803
- {
804
- "name": "$file",
805
- "sha256": "$(cat ${file}.sha256)",
806
- "signature": "$(cat ${file}.sig | base64 -w0)"
807
- }
808
- EOF
809
- done
810
-
811
- cat >> release-manifest.json << EOF
812
- ]
813
- }
814
- EOF
815
-
816
- # Sign the manifest
817
- sign_artifact "release-manifest.json"
818
-
819
- echo "signed=true" >> $GITHUB_OUTPUT
820
- echo "files=${SIGNED_FILES[*]}" >> $GITHUB_OUTPUT
821
-
822
- - name: Create Signed Git Tag
823
- if: steps.setup.outputs.signing_available == 'true'
824
- run: |
825
- # Create signed tag
826
- if [ -n "${{ secrets.SIGNING_KEY_PASSPHRASE }}" ]; then
827
- echo "${{ secrets.SIGNING_KEY_PASSPHRASE }}" | \
828
- git tag -s "${{ needs.version.outputs.tag }}" \
829
- -m "Release ${{ needs.version.outputs.version }}" \
830
- -m "Signed-off-by: ${{ github.actor }}" \
831
- -m "Correlation ID: ${{ needs.release_init.outputs.correlation_id }}"
832
- else
833
- git tag -s "${{ needs.version.outputs.tag }}" \
834
- -m "Release ${{ needs.version.outputs.version }}" \
835
- -m "Signed-off-by: ${{ github.actor }}" \
836
- -m "Correlation ID: ${{ needs.release_init.outputs.correlation_id }}"
837
- fi
838
-
839
- echo "✅ Created signed tag: ${{ needs.version.outputs.tag }}"
840
-
841
- - name: Upload Signed Artifacts
842
- if: steps.sign.outputs.signed == 'true'
843
- uses: actions/upload-artifact@v4
844
- with:
845
- name: signed-artifacts-${{ github.run_id }}
846
- path: |
847
- *.sig
848
- *.sha256
849
- release-manifest.json
850
- retention-days: 365
851
-
852
- # Release attestation
853
- release_attestation:
854
- name: 📜 Release Attestation
855
- needs: [release_init, version, quality, release_signing]
856
- if: |
857
- always() &&
858
- !cancelled() &&
859
- needs.version.result == 'success' &&
860
- (needs.release_signing.result == 'success' || needs.release_signing.result == 'skipped')
861
- runs-on: ubuntu-latest
862
- outputs:
863
- attestation_created: ${{ steps.attestation.outputs.created }}
864
- attestation_url: ${{ steps.attestation.outputs.url }}
865
- steps:
866
- - name: Generate SLSA Provenance
867
- id: provenance
868
- run: |
869
- # Create SLSA provenance attestation
870
- cat > provenance.json << EOF
871
- {
872
- "_type": "https://in-toto.io/Statement/v0.1",
873
- "predicateType": "https://slsa.dev/provenance/v0.2",
874
- "subject": [{
875
- "name": "${{ github.repository }}",
876
- "digest": {
877
- "sha256": "${{ github.sha }}"
878
- }
879
- }],
880
- "predicate": {
881
- "builder": {
882
- "id": "https://github.com/actions/runner"
883
- },
884
- "buildType": "https://github.com/slsa-framework/slsa-github-generator/generic@v1",
885
- "invocation": {
886
- "configSource": {
887
- "uri": "git+https://github.com/${{ github.repository }}@${{ github.ref }}",
888
- "digest": {
889
- "sha1": "${{ github.sha }}"
890
- },
891
- "entryPoint": ".github/workflows/release.yml"
892
- },
893
- "parameters": {
894
- "environment": "${{ inputs.environment }}",
895
- "version": "${{ needs.version.outputs.version }}",
896
- "release_strategy": "${{ inputs.release_strategy }}",
897
- "emergency_release": ${{ inputs.emergency_release }},
898
- "approval_required": ${{ inputs.require_approval }},
899
- "signatures_required": ${{ inputs.require_signatures }}
900
- },
901
- "environment": {
902
- "github_run_id": "${{ github.run_id }}",
903
- "github_run_number": "${{ github.run_number }}",
904
- "github_actor": "${{ github.actor }}",
905
- "github_event_name": "${{ github.event_name }}"
906
- }
907
- },
908
- "buildConfig": {
909
- "version": 1,
910
- "steps": [
911
- {
912
- "command": ["quality-checks"],
913
- "env": null
914
- },
915
- {
916
- "command": ["version-determination"],
917
- "env": null
918
- },
919
- {
920
- "command": ["release-creation"],
921
- "env": null
922
- }
923
- ]
924
- },
925
- "metadata": {
926
- "completeness": {
927
- "parameters": true,
928
- "environment": true,
929
- "materials": false
930
- },
931
- "reproducible": false,
932
- "buildStartedOn": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
933
- "buildFinishedOn": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
934
- }
935
- }
936
- }
937
- EOF
938
-
939
- - name: Create Release Attestation
940
- id: attestation
941
- run: |
942
- # Create comprehensive release attestation
943
- cat > release-attestation.json << EOF
944
- {
945
- "version": "${{ needs.version.outputs.version }}",
946
- "tag": "${{ needs.version.outputs.tag }}",
947
- "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
948
- "correlation_id": "${{ needs.release_init.outputs.correlation_id }}",
949
- "environment": "${{ inputs.environment }}",
950
- "attestations": {
951
- "quality": {
952
- "passed": ${{ needs.quality.result == 'success' }},
953
- "skipped_checks": "${{ inputs.skip_jobs }}",
954
- "workflow_result": "${{ needs.quality.result }}"
955
- },
956
- "approvals": {
957
- "required": ${{ inputs.require_approval }},
958
- "received": ${{ needs.release_approval.outputs.approved == 'true' || inputs.require_approval == false }},
959
- "approver": "${{ needs.release_approval.outputs.approver || 'N/A' }}",
960
- "approval_time": "${{ needs.release_approval.outputs.approval_time || 'N/A' }}"
961
- },
962
- "signatures": {
963
- "required": ${{ inputs.require_signatures }},
964
- "artifacts_signed": ${{ needs.release_signing.outputs.signed == 'true' }},
965
- "signed_files": "${{ needs.release_signing.outputs.signature_files || 'none' }}"
966
- },
967
- "compliance": {
968
- "frameworks": ["SOC2", "ISO27001"],
969
- "emergency_release": ${{ inputs.emergency_release }},
970
- "blackout_override": ${{ inputs.override_blackout }},
971
- "dependency_check": ${{ inputs.check_dependencies }}
972
- },
973
- "integrity": {
974
- "source_sha": "${{ github.sha }}",
975
- "repository": "${{ github.repository }}",
976
- "ref": "${{ github.ref }}",
977
- "actor": "${{ github.actor }}"
978
- },
979
- "sbom": {
980
- "generated": ${{ inputs.generate_sbom }},
981
- "format": "CycloneDX",
982
- "version": "1.4"
983
- }
984
- },
985
- "metadata": {
986
- "workflow_id": "${{ github.run_id }}",
987
- "workflow_url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
988
- "initiated_by": "${{ github.actor }}",
989
- "event_type": "${{ github.event_name }}"
990
- }
991
- }
992
- EOF
993
-
994
- echo "created=true" >> $GITHUB_OUTPUT
995
- echo "url=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts" >> $GITHUB_OUTPUT
996
-
997
- - name: Store Attestations
998
- uses: actions/upload-artifact@v4
999
- with:
1000
- name: release-attestations-${{ needs.version.outputs.version }}
1001
- path: |
1002
- provenance.json
1003
- release-attestation.json
1004
- retention-days: 365
1005
-
1006
- # Create GitHub Release with enterprise features
1007
- github_release:
1008
- name: 🎉 Create GitHub Release
1009
- needs: [release_init, version, release_signing, release_attestation]
1010
- runs-on: ubuntu-latest
1011
- if: |
1012
- always() &&
1013
- !cancelled() &&
1014
- needs.version.result == 'success' &&
1015
- (needs.release_signing.result == 'success' || needs.release_signing.result == 'skipped') &&
1016
- (needs.release_attestation.result == 'success' || needs.release_attestation.result == 'skipped')
1017
- outputs:
1018
- release_url: ${{ steps.create_release.outputs.html_url }}
1019
- release_id: ${{ steps.create_release.outputs.id }}
1020
- upload_url: ${{ steps.create_release.outputs.upload_url }}
1021
- steps:
1022
- - uses: actions/checkout@v4
1023
- with:
1024
- fetch-depth: 0
1025
-
1026
- - name: Download All Artifacts
1027
- uses: actions/download-artifact@v4
1028
- with:
1029
- pattern: '*-${{ github.run_id }}'
1030
-
1031
- - name: Generate Release Notes
1032
- id: release_notes
1033
- run: |
1034
- # Create release notes
1035
- cat > RELEASE_NOTES.md << 'EOF'
1036
- # Release ${{ needs.version.outputs.version }}
1037
-
1038
- ## 📊 Release Information
1039
- - **Version**: ${{ needs.version.outputs.version }}
1040
- - **Environment**: ${{ inputs.environment }}
1041
- - **Release Date**: $(date +"%Y-%m-%d %H:%M:%S UTC")
1042
- - **Release ID**: ${{ needs.release_init.outputs.correlation_id }}
1043
- - **Emergency Release**: ${{ inputs.emergency_release && '⚠️ Yes' || 'No' }}
1044
-
1045
- ## 🔐 Security & Compliance
1046
- - **Signed Release**: ${{ needs.release_signing.outputs.signed == 'true' && '✅ Yes' || '❌ No' }}
1047
- - **Approval Required**: ${{ inputs.require_approval && '✅ Yes' || 'No' }}
1048
- - **SBOM Generated**: ${{ inputs.generate_sbom && '✅ Yes' || 'No' }}
1049
- - **Attestation**: ✅ [View Attestation](${{ needs.release_attestation.outputs.attestation_url }})
1050
-
1051
- ## 📝 Changelog
1052
- EOF
1053
-
1054
- # Include changelog content
1055
- if [ -f "version-artifacts-${{ github.run_id }}/CHANGELOG_ENTRY.md" ]; then
1056
- cat "version-artifacts-${{ github.run_id }}/CHANGELOG_ENTRY.md" >> RELEASE_NOTES.md
1057
- fi
1058
-
1059
- # Add verification instructions if signed
1060
- if [ "${{ needs.release_signing.outputs.signed }}" == "true" ]; then
1061
- cat >> RELEASE_NOTES.md << 'EOF'
1062
-
1063
- ## 🔒 Verification
1064
-
1065
- This release is cryptographically signed. To verify:
1066
-
1067
- ```bash
1068
- # Download signature files
1069
- curl -L -o release-manifest.json.sig <signature-url>
1070
- curl -L -o release-manifest.json <manifest-url>
1071
-
1072
- # Import public key
1073
- gpg --import release-key.pub
1074
-
1075
- # Verify signature
1076
- gpg --verify release-manifest.json.sig release-manifest.json
1077
- ```
1078
- EOF
1079
- fi
1080
-
1081
- cat >> RELEASE_NOTES.md << 'EOF'
1082
-
1083
- ## 🔗 Links
1084
- - [Workflow Run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
1085
- - [Compare Changes](https://github.com/${{ github.repository }}/compare/${{ github.event.before }}...${{ needs.version.outputs.tag }})
1086
- EOF
1087
-
1088
- - name: Create GitHub Release
1089
- id: create_release
1090
- env:
1091
- GITHUB_TOKEN: ${{ github.token }}
1092
- run: |
1093
- # Prepare release data
1094
- RELEASE_DATA=$(jq -n \
1095
- --arg tag "${{ needs.version.outputs.tag }}" \
1096
- --arg name "Release ${{ needs.version.outputs.version }}" \
1097
- --arg body "$(cat RELEASE_NOTES.md)" \
1098
- --arg target "${{ github.sha }}" \
1099
- --argjson prerelease "${{ needs.version.outputs.prerelease == 'true' }}" \
1100
- --argjson draft false \
1101
- '{
1102
- tag_name: $tag,
1103
- name: $name,
1104
- body: $body,
1105
- target_commitish: $target,
1106
- prerelease: $prerelease,
1107
- draft: $draft
1108
- }')
1109
-
1110
- # Create release
1111
- RESPONSE=$(curl -X POST \
1112
- -H "Authorization: token $GITHUB_TOKEN" \
1113
- -H "Accept: application/vnd.github.v3+json" \
1114
- -d "$RELEASE_DATA" \
1115
- "https://api.github.com/repos/${{ github.repository }}/releases")
1116
-
1117
- # Extract release information
1118
- echo "$RESPONSE" | jq -r '.html_url' > .release_url
1119
- echo "$RESPONSE" | jq -r '.id' > .release_id
1120
- echo "$RESPONSE" | jq -r '.upload_url' > .upload_url
1121
-
1122
- echo "html_url=$(cat .release_url)" >> $GITHUB_OUTPUT
1123
- echo "id=$(cat .release_id)" >> $GITHUB_OUTPUT
1124
- echo "upload_url=$(cat .upload_url)" >> $GITHUB_OUTPUT
1125
-
1126
- - name: Upload Release Assets
1127
- if: needs.release_signing.outputs.signed == 'true'
1128
- env:
1129
- GITHUB_TOKEN: ${{ github.token }}
1130
- run: |
1131
- UPLOAD_URL=$(cat .upload_url | sed 's/{?name,label}//')
1132
-
1133
- # Upload signature files
1134
- for sig_file in signed-artifacts-${{ github.run_id }}/*.sig; do
1135
- if [ -f "$sig_file" ]; then
1136
- filename=$(basename "$sig_file")
1137
- curl -X POST \
1138
- -H "Authorization: token $GITHUB_TOKEN" \
1139
- -H "Content-Type: application/pgp-signature" \
1140
- --data-binary @"$sig_file" \
1141
- "${UPLOAD_URL}?name=${filename}"
1142
- fi
1143
- done
1144
-
1145
- # Upload checksums
1146
- for sha_file in signed-artifacts-${{ github.run_id }}/*.sha256; do
1147
- if [ -f "$sha_file" ]; then
1148
- filename=$(basename "$sha_file")
1149
- curl -X POST \
1150
- -H "Authorization: token $GITHUB_TOKEN" \
1151
- -H "Content-Type: text/plain" \
1152
- --data-binary @"$sha_file" \
1153
- "${UPLOAD_URL}?name=${filename}"
1154
- fi
1155
- done
1156
-
1157
- # Upload release manifest
1158
- if [ -f "signed-artifacts-${{ github.run_id }}/release-manifest.json" ]; then
1159
- curl -X POST \
1160
- -H "Authorization: token $GITHUB_TOKEN" \
1161
- -H "Content-Type: application/json" \
1162
- --data-binary @"signed-artifacts-${{ github.run_id }}/release-manifest.json" \
1163
- "${UPLOAD_URL}?name=release-manifest.json"
1164
- fi
1165
-
1166
- # Upload SBOM if generated
1167
- if [ -f "version-artifacts-${{ github.run_id }}/sbom.json" ]; then
1168
- curl -X POST \
1169
- -H "Authorization: token $GITHUB_TOKEN" \
1170
- -H "Content-Type: application/json" \
1171
- --data-binary @"version-artifacts-${{ github.run_id }}/sbom.json" \
1172
- "${UPLOAD_URL}?name=sbom.json"
1173
- fi
1174
-
1175
- # Sentry Release Creation
1176
- sentry_release:
1177
- name: 🚨 Create Sentry Release
1178
- needs: [release_init, version]
1179
- runs-on: ubuntu-latest
1180
- if: |
1181
- always() &&
1182
- !cancelled() &&
1183
- needs.version.result == 'success'
1184
- outputs:
1185
- sentry_release_created: ${{ steps.set_outputs.outputs.created }}
1186
- sentry_release_url: ${{ steps.set_outputs.outputs.url }}
1187
- steps:
1188
- - uses: actions/checkout@v4
1189
- with:
1190
- fetch-depth: 0
1191
-
1192
- - name: Check Sentry Configuration
1193
- id: check_config
1194
- env:
1195
- SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
1196
- SENTRY_ORG: ${{ vars.SENTRY_ORG }}
1197
- SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
1198
- run: |
1199
- # Check if all required Sentry secrets are available
1200
- if [ -z "$SENTRY_AUTH_TOKEN" ] || [ -z "$SENTRY_ORG" ] || [ -z "$SENTRY_PROJECT" ]; then
1201
- echo "⚠️ Sentry configuration incomplete - skipping Sentry release" >> $GITHUB_STEP_SUMMARY
1202
- echo "configured=false" >> $GITHUB_OUTPUT
1203
- # Set default outputs for skipped job
1204
- echo "created=false" >> $GITHUB_OUTPUT
1205
- echo "url=" >> $GITHUB_OUTPUT
1206
- else
1207
- echo "✅ Sentry configuration verified" >> $GITHUB_STEP_SUMMARY
1208
- echo "configured=true" >> $GITHUB_OUTPUT
1209
- fi
1210
-
1211
- - name: Create Sentry Release
1212
- id: sentry
1213
- if: steps.check_config.outputs.configured == 'true'
1214
- uses: getsentry/action-release@v1
1215
- env:
1216
- SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
1217
- SENTRY_ORG: ${{ vars.SENTRY_ORG }}
1218
- SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
1219
- with:
1220
- environment: ${{ inputs.environment }}
1221
- version: ${{ needs.version.outputs.version }}
1222
- sourcemaps: ${{ inputs.sourcemaps }}
1223
-
1224
- - name: Update Release Summary
1225
- if: steps.check_config.outputs.configured == 'true' && steps.sentry.outcome == 'success'
1226
- run: |
1227
- echo "## 🚨 Sentry Release" >> $GITHUB_STEP_SUMMARY
1228
- echo "" >> $GITHUB_STEP_SUMMARY
1229
- echo "✅ Sentry release created successfully" >> $GITHUB_STEP_SUMMARY
1230
- echo "- **Version**: ${{ needs.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
1231
- echo "- **Environment**: ${{ inputs.environment }}" >> $GITHUB_STEP_SUMMARY
1232
- if [ -n "${{ inputs.sourcemaps }}" ]; then
1233
- echo "- **Source Maps**: Uploaded from \`${{ inputs.sourcemaps }}\`" >> $GITHUB_STEP_SUMMARY
1234
- fi
1235
- echo "- **Organization**: ${{ vars.SENTRY_ORG }}" >> $GITHUB_STEP_SUMMARY
1236
- echo "- **Project**: ${{ vars.SENTRY_PROJECT }}" >> $GITHUB_STEP_SUMMARY
1237
-
1238
- - name: Set Outputs
1239
- if: always()
1240
- id: set_outputs
1241
- run: |
1242
- # Set outputs based on whether Sentry was configured and successful
1243
- if [ "${{ steps.check_config.outputs.configured }}" == "true" ] && [ "${{ steps.sentry.outcome }}" == "success" ]; then
1244
- echo "created=true" >> $GITHUB_OUTPUT
1245
- echo "url=https://sentry.io/organizations/${{ vars.SENTRY_ORG }}/releases/${{ needs.version.outputs.version }}/" >> $GITHUB_OUTPUT
1246
- else
1247
- echo "created=false" >> $GITHUB_OUTPUT
1248
- echo "url=" >> $GITHUB_OUTPUT
1249
- fi
1250
-
1251
- # Check Jira Setup
1252
- checks_jira_setup:
1253
- name: 📋 Check Jira Setup
1254
- needs: [version]
1255
- runs-on: ubuntu-latest
1256
- outputs:
1257
- has_jira_setup: ${{ steps.check.outputs.has_jira_setup }}
1258
- steps:
1259
- - name: Checkout
1260
- uses: actions/checkout@v4
1261
- - uses: noliran/branch-based-secrets@v1
1262
- with:
1263
- secrets: JIRA_AUTOMATION_WEBHOOK
1264
- - name: Check Jira Configuration
1265
- id: check
1266
- run: |
1267
- if [[ -z "${JIRA_AUTOMATION_WEBHOOK}" ]] || [[ -z "${{ vars.JIRA_PROJECT_KEY }}" ]]; then
1268
- echo "⚠️ Jira configuration incomplete - skipping Jira release" >> $GITHUB_STEP_SUMMARY
1269
- if [[ -z "${JIRA_AUTOMATION_WEBHOOK}" ]]; then
1270
- echo " - Missing: JIRA_AUTOMATION_WEBHOOK secret" >> $GITHUB_STEP_SUMMARY
1271
- fi
1272
- if [[ -z "${{ vars.JIRA_PROJECT_KEY }}" ]]; then
1273
- echo " - Missing: JIRA_PROJECT_KEY variable" >> $GITHUB_STEP_SUMMARY
1274
- fi
1275
- echo "has_jira_setup=false" >> $GITHUB_OUTPUT
1276
- else
1277
- echo "✅ Jira configuration verified" >> $GITHUB_STEP_SUMMARY
1278
- echo "has_jira_setup=true" >> $GITHUB_OUTPUT
1279
- fi
1280
- shell: bash
1281
- env:
1282
- JIRA_AUTOMATION_WEBHOOK: ${{ secrets[env.JIRA_AUTOMATION_WEBHOOK_NAME] }}
1283
-
1284
- # Create Jira Release
1285
- jira_release:
1286
- name: 📋 Create Jira Release
1287
- runs-on: ubuntu-latest
1288
- if: |
1289
- always() &&
1290
- !cancelled() &&
1291
- needs.version.result == 'success' &&
1292
- needs.checks_jira_setup.outputs.has_jira_setup == 'true'
1293
- needs: [version, checks_jira_setup]
1294
- outputs:
1295
- jira_release_created: ${{ steps.create.outputs.created }}
1296
- jira_release_url: ${{ steps.create.outputs.url }}
1297
- steps:
1298
- - name: Checkout
1299
- uses: actions/checkout@v4
1300
- - uses: noliran/branch-based-secrets@v1
1301
- with:
1302
- secrets: JIRA_AUTOMATION_WEBHOOK
1303
- - name: Create Jira Release
1304
- id: create
1305
- continue-on-error: true
1306
- uses: GeoWerkstatt/create-jira-release@v1
1307
- with:
1308
- jira-project-key: ${{ vars.JIRA_PROJECT_KEY }}
1309
- jira-automation-webhook: ${{ secrets[env.JIRA_AUTOMATION_WEBHOOK_NAME] }}
1310
- build-version: '${{ github.event.repository.name }} v${{ needs.version.outputs.version }}'
1311
- - name: Update Release Summary
1312
- if: steps.create.outcome == 'success'
1313
- run: |
1314
- echo "## 📋 Jira Release" >> $GITHUB_STEP_SUMMARY
1315
- echo "" >> $GITHUB_STEP_SUMMARY
1316
- echo "✅ Jira release created successfully" >> $GITHUB_STEP_SUMMARY
1317
- echo "- **Version**: ${{ needs.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
1318
- echo "- **Project**: ${{ vars.JIRA_PROJECT_KEY }}" >> $GITHUB_STEP_SUMMARY
1319
- echo "- **Build Version**: ${{ github.event.repository.name }} v${{ needs.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
1320
- - name: Set Outputs
1321
- if: always()
1322
- run: |
1323
- if [ "${{ steps.create.outcome }}" == "success" ]; then
1324
- echo "created=true" >> $GITHUB_OUTPUT
1325
- echo "url=https://${{ vars.JIRA_BASE_URL || 'company.atlassian.net' }}/projects/${{ vars.JIRA_PROJECT_KEY }}/versions" >> $GITHUB_OUTPUT
1326
- else
1327
- echo "created=false" >> $GITHUB_OUTPUT
1328
- echo "url=" >> $GITHUB_OUTPUT
1329
- fi
1330
-
1331
- # Release compliance validation
1332
- release_compliance:
1333
- name: ✅ Release Compliance
1334
- needs:
1335
- [
1336
- release_init,
1337
- version,
1338
- quality,
1339
- release_signing,
1340
- release_attestation,
1341
- github_release,
1342
- sentry_release,
1343
- jira_release,
1344
- ]
1345
- if: always()
1346
- runs-on: ubuntu-latest
1347
- outputs:
1348
- status: ${{ steps.compliance.outputs.status }}
1349
- report_url: ${{ steps.compliance.outputs.report_url }}
1350
- steps:
1351
- - name: Download All Artifacts
1352
- uses: actions/download-artifact@v4
1353
- with:
1354
- pattern: '*-${{ github.run_id }}'
1355
-
1356
- - name: Compliance Validation
1357
- id: compliance
1358
- run: |
1359
- # Initialize compliance checks
1360
- COMPLIANCE_PASSED=true
1361
- COMPLIANCE_ISSUES=()
1362
-
1363
- # Check 1: Required approvals
1364
- if [ "${{ inputs.require_approval }}" == "true" ]; then
1365
- if [ "${{ needs.release_approval.outputs.approved }}" != "true" ]; then
1366
- COMPLIANCE_PASSED=false
1367
- COMPLIANCE_ISSUES+=("Missing required approval")
1368
- fi
1369
- fi
1370
-
1371
- # Check 2: Required signatures
1372
- if [ "${{ inputs.require_signatures }}" == "true" ]; then
1373
- if [ "${{ needs.release_signing.outputs.signed }}" != "true" ]; then
1374
- COMPLIANCE_PASSED=false
1375
- COMPLIANCE_ISSUES+=("Missing required signatures")
1376
- fi
1377
- fi
1378
-
1379
- # Check 3: Quality gates
1380
- if [ "${{ needs.quality.result }}" != "success" ] && [ "${{ needs.quality.result }}" != "skipped" ]; then
1381
- COMPLIANCE_PASSED=false
1382
- COMPLIANCE_ISSUES+=("Quality checks failed")
1383
- fi
1384
-
1385
- # Check 4: Attestation
1386
- if [ ! -f "release-attestations-${{ needs.version.outputs.version }}/release-attestation.json" ]; then
1387
- COMPLIANCE_PASSED=false
1388
- COMPLIANCE_ISSUES+=("Missing release attestation")
1389
- fi
1390
-
1391
- # Check 5: Emergency release documentation
1392
- if [ "${{ inputs.emergency_release }}" == "true" ]; then
1393
- echo "⚠️ Emergency release - additional documentation required"
1394
- fi
1395
-
1396
- # Generate compliance report
1397
- cat > compliance-report.json << EOF
1398
- {
1399
- "release_version": "${{ needs.version.outputs.version }}",
1400
- "compliance_status": "$([[ $COMPLIANCE_PASSED == true ]] && echo 'PASSED' || echo 'FAILED')",
1401
- "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
1402
- "correlation_id": "${{ needs.release_init.outputs.correlation_id }}",
1403
- "frameworks": {
1404
- "SOC2": {
1405
- "CC8.1_change_management": true,
1406
- "CC7.2_monitoring": true,
1407
- "CC7.3_security_monitoring": ${{ needs.release_signing.outputs.signed == 'true' }},
1408
- "CC6.1_logical_access": ${{ inputs.require_approval }}
1409
- },
1410
- "ISO27001": {
1411
- "A.12.1_operational_procedures": true,
1412
- "A.14.2_secure_development": true,
1413
- "A.12.4_logging_monitoring": true,
1414
- "A.14.2.8_security_testing": ${{ needs.quality.result == 'success' }}
1415
- },
1416
- "SLSA": {
1417
- "level": ${{ needs.release_signing.outputs.signed == 'true' && '2' || '1' }},
1418
- "source": true,
1419
- "build": true,
1420
- "provenance": true,
1421
- "common_requirements": true
1422
- }
1423
- },
1424
- "checks": {
1425
- "approvals": {
1426
- "required": ${{ inputs.require_approval }},
1427
- "completed": ${{ needs.release_approval.outputs.approved == 'true' || inputs.require_approval == false }}
1428
- },
1429
- "signatures": {
1430
- "required": ${{ inputs.require_signatures }},
1431
- "completed": ${{ needs.release_signing.outputs.signed == 'true' || inputs.require_signatures == false }}
1432
- },
1433
- "quality": {
1434
- "passed": ${{ needs.quality.result == 'success' || needs.quality.result == 'skipped' }},
1435
- "skipped_checks": "${{ inputs.skip_jobs }}"
1436
- },
1437
- "attestation": {
1438
- "created": ${{ needs.release_attestation.outputs.attestation_created == 'true' }}
1439
- },
1440
- "emergency_release": ${{ inputs.emergency_release }},
1441
- "blackout_override": ${{ inputs.override_blackout }}
1442
- },
1443
- "issues": $(printf '%s\n' "${COMPLIANCE_ISSUES[@]}" | jq -R . | jq -s .)
1444
- }
1445
- EOF
1446
-
1447
- # Set outputs
1448
- echo "status=$([[ $COMPLIANCE_PASSED == true ]] && echo 'PASSED' || echo 'FAILED')" >> $GITHUB_OUTPUT
1449
- echo "report_url=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts" >> $GITHUB_OUTPUT
1450
-
1451
- # Summary
1452
- echo "## ✅ Compliance Validation" >> $GITHUB_STEP_SUMMARY
1453
- echo "" >> $GITHUB_STEP_SUMMARY
1454
- echo "**Status**: $([[ $COMPLIANCE_PASSED == true ]] && echo '✅ PASSED' || echo '❌ FAILED')" >> $GITHUB_STEP_SUMMARY
1455
- echo "**Frameworks**: SOC2, ISO27001, SLSA Level ${{ needs.release_signing.outputs.signed == 'true' && '2' || '1' }}" >> $GITHUB_STEP_SUMMARY
1456
-
1457
- if [ ${#COMPLIANCE_ISSUES[@]} -gt 0 ]; then
1458
- echo "" >> $GITHUB_STEP_SUMMARY
1459
- echo "### Issues Found" >> $GITHUB_STEP_SUMMARY
1460
- printf -- '- %s\n' "${COMPLIANCE_ISSUES[@]}" >> $GITHUB_STEP_SUMMARY
1461
- fi
1462
-
1463
- - name: Generate Compliance Evidence Package
1464
- run: |
1465
- # Create evidence package
1466
- mkdir -p compliance-evidence
1467
-
1468
- # Copy all compliance-related artifacts
1469
- find . -name "release-attestation*.json" -exec cp {} compliance-evidence/ \;
1470
- find . -name "provenance.json" -exec cp {} compliance-evidence/ \;
1471
- find . -name "compliance-report.json" -exec cp {} compliance-evidence/ \;
1472
- find . -name "release-manifest.json*" -exec cp {} compliance-evidence/ \;
1473
- find . -name "*.sig" -exec cp {} compliance-evidence/ \;
1474
- find . -name "*.sha256" -exec cp {} compliance-evidence/ \;
1475
-
1476
- # Create evidence summary
1477
- cat > compliance-evidence/README.md << EOF
1478
- # Release Compliance Evidence Package
1479
-
1480
- **Release Version**: ${{ needs.version.outputs.version }}
1481
- **Generated**: $(date -u +%Y-%m-%dT%H:%M:%SZ)
1482
- **Environment**: ${{ inputs.environment }}
1483
- **Correlation ID**: ${{ needs.release_init.outputs.correlation_id }}
1484
-
1485
- ## Contents
1486
-
1487
- - Release attestations and provenance
1488
- - Digital signatures (if applicable)
1489
- - Compliance validation report
1490
- - Release manifest with checksums
1491
- - Approval records (if applicable)
1492
-
1493
- ## Compliance Status
1494
-
1495
- **Overall Status**: ${{ steps.compliance.outputs.status }}
1496
-
1497
- This release complies with:
1498
- - SOC 2 Type II
1499
- - ISO 27001:2022
1500
- - SLSA Level ${{ needs.release_signing.outputs.signed == 'true' && '2' || '1' }}
1501
-
1502
- ## Verification Instructions
1503
-
1504
- 1. Verify signatures using the provided .sig files
1505
- 2. Check SHA256 checksums against artifacts
1506
- 3. Review attestation for build provenance
1507
- 4. Validate compliance report status
1508
-
1509
- ## Retention
1510
-
1511
- This evidence package should be retained for 7 years per compliance requirements.
1512
- EOF
1513
-
1514
- # Create tarball
1515
- tar -czf compliance-evidence-${{ needs.version.outputs.version }}.tar.gz compliance-evidence/
1516
-
1517
- - name: Store Compliance Evidence
1518
- uses: actions/upload-artifact@v4
1519
- with:
1520
- name: compliance-evidence-${{ needs.version.outputs.version }}
1521
- path: compliance-evidence-${{ needs.version.outputs.version }}.tar.gz
1522
- retention-days: 2555 # 7 years for compliance
1523
-
1524
- # Final release summary
1525
- release_summary:
1526
- name: 📋 Release Summary
1527
- needs:
1528
- [
1529
- release_init,
1530
- quality,
1531
- version,
1532
- release_signing,
1533
- release_attestation,
1534
- github_release,
1535
- sentry_release,
1536
- jira_release,
1537
- release_compliance,
1538
- ]
1539
- if: always()
1540
- runs-on: ubuntu-latest
1541
- steps:
1542
- - name: Generate Final Summary
1543
- run: |
1544
- # Create comprehensive summary
1545
- cat >> $GITHUB_STEP_SUMMARY << 'EOF'
1546
-
1547
- # 🎉 Enterprise Release Complete
1548
-
1549
- ## Release Information
1550
- - **Version**: ${{ needs.version.outputs.version }}
1551
- - **Tag**: ${{ needs.version.outputs.tag }}
1552
- - **Environment**: ${{ inputs.environment }}
1553
- - **Correlation ID**: ${{ needs.release_init.outputs.correlation_id }}
1554
-
1555
- ## Enterprise Features
1556
- - **Approval Required**: ${{ inputs.require_approval && '✅ Yes' || '❌ No' }}
1557
- - **Signatures**: ${{ needs.release_signing.outputs.signed == 'true' && '✅ Signed' || '❌ Not Signed' }}
1558
- - **SBOM**: ${{ inputs.generate_sbom && '✅ Generated' || '❌ Not Generated' }}
1559
- - **Compliance**: ${{ needs.release_compliance.outputs.status }}
1560
-
1561
- ## Execution Summary
1562
- - **Total Duration**: $(($(date +%s) - ${{ needs.release_init.outputs.start_time }}))s
1563
- - **Quality Checks**: ${{ needs.quality.result }}
1564
- - **Version Creation**: ${{ needs.version.result }}
1565
- - **GitHub Release**: ${{ needs.github_release.result }}
1566
-
1567
- ## Outputs
1568
- - **Release URL**: ${{ needs.github_release.outputs.release_url || 'N/A' }}
1569
- - **Attestation**: ${{ needs.release_attestation.outputs.attestation_url || 'N/A' }}
1570
- - **Compliance Report**: ${{ needs.release_compliance.outputs.report_url || 'N/A' }}
1571
- - **Sentry Release**: ${{ needs.sentry_release.outputs.sentry_release_url || 'Not configured' }}
1572
- - **Jira Release**: ${{ needs.jira_release.outputs.jira_release_url || 'Not configured' }}
1573
-
1574
- ## Security & Compliance
1575
- - ✅ Audit trail generated
1576
- - ✅ Release attestation created
1577
- - ✅ Compliance evidence packaged
1578
- - ${{ needs.release_signing.outputs.signed == 'true' && '✅ Release signed' || '⚠️ Release not signed' }}
1579
-
1580
- ## Next Steps
1581
- 1. Review the release at ${{ needs.github_release.outputs.release_url || 'GitHub Releases page' }}
1582
- 2. Verify signatures if applicable
1583
- 3. Review compliance report
1584
- 4. Deploy using your deployment workflow
1585
- 5. Run load tests if needed
1586
- EOF
1587
-
1588
- # Set workflow status
1589
- if [ "${{ needs.github_release.result }}" == "success" ] && [ "${{ needs.release_compliance.outputs.status }}" == "PASSED" ]; then
1590
- echo "✅ Enterprise release completed successfully!" >> $GITHUB_STEP_SUMMARY
1591
- else
1592
- echo "⚠️ Release completed with issues. Please review the compliance report." >> $GITHUB_STEP_SUMMARY
1593
- fi