Repository navigation
563 lines (479 loc) · 20.7 KB
/
Copy pathrelease-with-sbom.yml
File metadata and controls
563 lines (479 loc) · 20.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
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
name: Release with SBOM and Notes
# This workflow creates GitHub releases and publishes to NPM after a version bump.
# It is triggered only by the auto-version-bump workflow or manual dispatch.
# Default permissions for all jobs
permissions:
contents: write # Needed for creating releases and tags
actions: read # Needed to read workflow runs
id-token: write # Needed for npm provenance and build attestations
attestations: write # Needed for build provenance attestations
on:
# Allow manual triggering for specific versions
workflow_dispatch:
inputs:
force_version:
description: 'Force release of specific version (leave empty to use package.json)'
required: false
# Direct trigger from auto-version-bump workflow
repository_dispatch:
types: [trigger-release]
# Trigger on pushes to main that modify package.json
# This catches the commits made by the auto-version-bump workflow
# and serves as a fallback mechanism
push:
branches:
- main
paths:
- 'package.json'
# Also triggered when auto-version-bump workflow completes successfully
# This is a backup trigger method
workflow_run:
workflows:
- "Auto Version Bump"
types:
- completed
branches:
- main
jobs:
check-version:
runs-on: ubuntu-latest
permissions:
contents: write # Needed for checking releases and tags
actions: read # Needed to read workflow runs
outputs:
version: ${{ steps.get-version.outputs.version }}
proceed: ${{ steps.check.outputs.proceed }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for release notes
ref: ${{ github.event_name == 'workflow_run' && 'main' || github.ref }}
- name: Debug workflow metadata
run: |
echo "========== WORKFLOW DEBUG INFO =========="
echo "Event name: ${{ github.event_name }}"
echo "Event trigger: ${{ github.event_name }}"
echo "Ref: ${{ github.ref }}"
echo "SHA: ${{ github.sha }}"
echo "Repository: ${{ github.repository }}"
echo "Actor: ${{ github.actor }}"
echo "Workflow permissions:"
echo " Contents: ${{ github.token_permissions.contents }}"
echo " Actions: ${{ github.token_permissions.actions }}"
echo " ID-token: ${{ github.token_permissions.id-token }}"
if [ "${{ github.event_name }}" = "workflow_run" ]; then
echo "Workflow run details:"
echo " Source workflow: ${{ github.event.workflow_run.name }}"
echo " Workflow ID: ${{ github.event.workflow_run.id }}"
echo " Workflow conclusion: ${{ github.event.workflow_run.conclusion }}"
echo " Source HEAD SHA: ${{ github.event.workflow_run.head_sha }}"
echo " Source branch: ${{ github.event.workflow_run.head_branch }}"
echo " Source repository: ${{ github.event.workflow_run.repository.full_name }}"
echo " Created at: ${{ github.event.workflow_run.created_at }}"
echo " Updated at: ${{ github.event.workflow_run.updated_at }}"
fi
echo "Listing repository tags:"
git tag --list -n
echo "Recent commits on main:"
git log -n 5 --pretty=format:"%h %s"
# Package.json is now the sole source of version information
echo "Package.json version:"
cat package.json | grep version || echo "Could not find version in package.json"
echo "========================================"
- name: Get version from package.json
id: get-version
run: |
# First check if we're using a forced version from workflow_dispatch
FORCE_VERSION="${{ github.event.inputs.force_version }}"
if [ -n "$FORCE_VERSION" ]; then
echo "Using forced version from workflow dispatch: $FORCE_VERSION"
echo "version=$FORCE_VERSION" >> $GITHUB_OUTPUT
exit 0
fi
# Check if this is a repository_dispatch event with version in payload
if [ "${{ github.event_name }}" = "repository_dispatch" ]; then
DISPATCH_VERSION="${{ github.event.client_payload.version }}"
if [ -n "$DISPATCH_VERSION" ]; then
echo "Using version from repository dispatch: $DISPATCH_VERSION"
echo "version=$DISPATCH_VERSION" >> $GITHUB_OUTPUT
exit 0
fi
fi
# Get version from package.json
if [ -f "package.json" ]; then
VERSION=$(jq -r '.version' package.json)
echo "Current version in package.json: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
else
echo "ERROR: package.json not found!"
echo "Files in current directory:"
ls -la
exit 1
fi
- name: Check for existing GitHub release
id: check-release
env:
VERSION_INPUT: ${{ steps.get-version.outputs.version }}
run: |
VERSION="${VERSION_INPUT}"
echo "Checking if GitHub release v$VERSION already exists..."
# Set GH_TOKEN for gh CLI
export GH_TOKEN="${{ secrets.GITHUB_TOKEN }}"
if gh release view "v$VERSION" &> /dev/null; then
echo "::warning::Release v$VERSION already exists!"
echo "release_exists=true" >> $GITHUB_OUTPUT
# Get release URL
RELEASE_URL=$(gh release view "v$VERSION" --json url -q .url)
echo "Existing release: $RELEASE_URL"
else
echo "No existing release for v$VERSION found, can proceed with new release"
echo "release_exists=false" >> $GITHUB_OUTPUT
fi
- name: Check if we should proceed with release
id: check
run: |
echo "Event name: ${{ github.event_name }}"
# Always honor forced release for manual workflow dispatch
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
FORCE_VERSION="${{ github.event.inputs.force_version }}"
if [ -n "$FORCE_VERSION" ]; then
echo "Manual workflow trigger with forced version - proceeding with release"
echo "proceed=true" >> $GITHUB_OUTPUT
exit 0
fi
echo "Manual workflow trigger - proceeding with release"
echo "proceed=true" >> $GITHUB_OUTPUT
exit 0
fi
# For repository_dispatch events from auto-version-bump workflow
if [ "${{ github.event_name }}" = "repository_dispatch" ]; then
echo "Processing repository_dispatch event with type: ${{ github.event.action }}"
if [ "${{ github.event.action }}" = "trigger-release" ]; then
echo "Direct trigger from auto-version-bump workflow - proceeding with release"
echo "proceed=true" >> $GITHUB_OUTPUT
exit 0
fi
fi
# Check if release already exists
if [ "${{ steps.check-release.outputs.release_exists }}" = "true" ]; then
echo "::warning::Release already exists for this version - skipping release"
echo "proceed=false" >> $GITHUB_OUTPUT
exit 0
fi
# For workflow_run events from Auto Version Bump
if [ "${{ github.event_name }}" = "workflow_run" ]; then
echo "Processing workflow_run event from ${github.event.workflow_run.name}"
if [ "${{ github.event.workflow_run.conclusion }}" = "success" ]; then
echo "Auto Version Bump completed successfully - proceeding with release"
# Get the head SHA from the workflow run
HEAD_SHA="${{ github.event.workflow_run.head_sha }}"
echo "Source workflow HEAD SHA: $HEAD_SHA"
# Ensure we have the latest code from main
git fetch origin main
git checkout main
git pull origin main
# Get the current version from package.json
CURRENT_VERSION=$(jq -r '.version' package.json)
echo "Current version from package.json: $CURRENT_VERSION"
# Check if this version already has a release
export GH_TOKEN="${{ secrets.GITHUB_TOKEN }}"
if gh release view "v$CURRENT_VERSION" &> /dev/null; then
echo "Release v$CURRENT_VERSION already exists - skipping release creation"
echo "proceed=false" >> $GITHUB_OUTPUT
else
echo "Version $CURRENT_VERSION has no existing GitHub release - proceeding with release"
echo "proceed=true" >> $GITHUB_OUTPUT
fi
else
echo "Auto Version Bump did not complete successfully - skipping release"
echo "proceed=false" >> $GITHUB_OUTPUT
fi
exit 0
fi
# For push events - check if this is a version bump commit
if [ "${{ github.event_name }}" = "push" ]; then
echo "This is a push event - checking if it's a version bump commit"
# Check the commit message to see if it's from the auto-version-bump workflow
COMMIT_MSG=$(git log -1 --pretty=%B)
echo "Last commit message: $COMMIT_MSG"
if [[ "$COMMIT_MSG" == *"Bump version to"* ]]; then
echo "This appears to be a version bump commit"
# Check existing GitHub releases
export GH_TOKEN="${{ secrets.GITHUB_TOKEN }}"
# Get current version from package.json
CURRENT_VERSION=$(jq -r '.version' package.json)
echo "Current version in package.json: $CURRENT_VERSION"
# Check if this version already has a release
if gh release view "v$CURRENT_VERSION" &> /dev/null; then
echo "Release v$CURRENT_VERSION already exists - skipping release creation"
echo "proceed=false" >> $GITHUB_OUTPUT
exit 0
fi
# If we've reached here, we have a version that needs releasing
echo "Version $CURRENT_VERSION has no existing GitHub release - proceeding with release"
echo "proceed=true" >> $GITHUB_OUTPUT
exit 0
else
echo "This does not appear to be a version bump commit - skipping release"
echo "proceed=false" >> $GITHUB_OUTPUT
exit 0
fi
fi
# Default fallback
echo "Unknown event type - proceeding with release as fallback"
echo "proceed=true" >> $GITHUB_OUTPUT
build:
needs: check-version
if: needs.check-version.outputs.proceed == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for release notes
ref: ${{ github.event_name == 'workflow_run' && 'main' || github.ref }}
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Display version info
run: |
echo "Working with version: ${{ needs.check-version.outputs.version }}"
- name: Build project
run: |
echo "TypeScript version:"
npx tsc --version
echo "Checking for workers-types package:"
npm list @cloudflare/workers-types || true
echo "Building project with verbose TypeScript output..."
npx tsc --listEmittedFiles
npx tsc -p tsconfig.cli.json --listEmittedFiles
- name: Run tests
run: npm test || echo "Tests skipped"
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: dist/
generate-sbom:
needs: [check-version, build]
if: needs.check-version.outputs.proceed == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Install dependencies
run: npm ci
- name: Generate CycloneDX SBOM
uses: CycloneDX/gh-node-module-generatebom@v1
with:
path: ./
output: ./bom.json
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: ./bom.json
create-release-notes:
needs: [check-version, build]
if: needs.check-version.outputs.proceed == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
release_notes: ${{ steps.generate-notes.outputs.notes }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set version for release notes
id: version
run: |
echo "version=${{ needs.check-version.outputs.version }}" >> $GITHUB_OUTPUT
echo "Using version: ${{ needs.check-version.outputs.version }}"
- name: Get previous tag
id: previoustag
run: |
PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "none")
echo "tag=$PREVIOUS_TAG" >> $GITHUB_OUTPUT
- name: Generate release notes
id: generate-notes
run: |
if [ "${{ steps.previoustag.outputs.tag }}" = "none" ]; then
COMMITS=$(git log --pretty=format:"- %s (%h)" -n 20)
else
COMMITS=$(git log ${{ steps.previoustag.outputs.tag }}..HEAD --pretty=format:"- %s (%h)")
fi
PR_NUMBERS=$(echo "$COMMITS" | grep -o "#[0-9]\+" | sort -u | sed 's/#//')
echo "## What's Changed in v${{ steps.version.outputs.version }}" > RELEASE_NOTES.md
echo "" >> RELEASE_NOTES.md
echo "### Commits" >> RELEASE_NOTES.md
echo "$COMMITS" >> RELEASE_NOTES.md
echo "" >> RELEASE_NOTES.md
if [ ! -z "$PR_NUMBERS" ]; then
echo "### Pull Requests" >> RELEASE_NOTES.md
for PR in $PR_NUMBERS; do
PR_INFO=$(curl -s -H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${{ github.repository }}/pulls/$PR)
PR_TITLE=$(echo "$PR_INFO" | jq -r '.title // "Unknown PR Title"')
PR_URL=$(echo "$PR_INFO" | jq -r '.html_url // "#"')
PR_AUTHOR=$(echo "$PR_INFO" | jq -r '.user.login // "Unknown"')
echo "- #$PR: $PR_TITLE by @$PR_AUTHOR [View PR]($PR_URL)" >> RELEASE_NOTES.md
done
echo "" >> RELEASE_NOTES.md
fi
echo "### SBOM Information" >> RELEASE_NOTES.md
echo "This release includes a Software Bill of Materials (SBOM) in CycloneDX format." >> RELEASE_NOTES.md
echo "" >> RELEASE_NOTES.md
echo "### 🔐 Verified Release" >> RELEASE_NOTES.md
echo "This release includes cryptographic build provenance attestations and is immutable (artifacts and tags cannot be modified or deleted). Release immutability is enabled in this repository." >> RELEASE_NOTES.md
echo "" >> RELEASE_NOTES.md
echo "**Verify this release:**" >> RELEASE_NOTES.md
echo '```bash' >> RELEASE_NOTES.md
echo "# Verify SBOM" >> RELEASE_NOTES.md
echo "gh attestation verify bom.json --owner anoncam --repo dedpaste" >> RELEASE_NOTES.md
echo "" >> RELEASE_NOTES.md
echo "# Verify build artifacts" >> RELEASE_NOTES.md
echo "gh attestation verify <artifact-path> --owner anoncam --repo dedpaste" >> RELEASE_NOTES.md
echo '```' >> RELEASE_NOTES.md
echo "" >> RELEASE_NOTES.md
echo "This ensures the artifacts have not been tampered with and were built by the official GitHub Actions workflow." >> RELEASE_NOTES.md
echo "" >> RELEASE_NOTES.md
# Escape special characters for GitHub Actions output
NOTES=$(cat RELEASE_NOTES.md)
NOTES="${NOTES//'%'/'%25'}"
# Handle newlines properly - note that we need to escape \n and \r
NOTES="${NOTES//$'\n'/'%0A'}"
NOTES="${NOTES//$'\r'/'%0D'}"
echo "notes=$NOTES" >> $GITHUB_OUTPUT
- name: Upload release notes
uses: actions/upload-artifact@v4
with:
name: release-notes
path: RELEASE_NOTES.md
release:
needs: [check-version, build, generate-sbom, create-release-notes]
if: always() && needs.check-version.outputs.proceed == 'true' && !cancelled() && !failure()
runs-on: ubuntu-latest
permissions:
contents: write # Needed for creating releases
id-token: write # Needed for npm provenance and build attestations
attestations: write # Needed for build provenance attestations
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set version for release
id: version
env:
VERSION_INPUT: ${{ needs.check-version.outputs.version }}
run: |
echo "version=${VERSION_INPUT}" >> $GITHUB_OUTPUT
echo "Using version: ${VERSION_INPUT}"
- name: Debug release information
env:
VERSION_INPUT: ${{ needs.check-version.outputs.version }}
run: |
echo "========== RELEASE DEBUG INFO =========="
echo "Release version: v${VERSION_INPUT}"
echo "Creating tag: v${VERSION_INPUT}"
echo "Checking if artifacts are available..."
echo "========================================"
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build-artifacts
path: dist/
- name: Download SBOM
uses: actions/download-artifact@v4
with:
name: sbom
path: ./
- name: Download release notes
uses: actions/download-artifact@v4
with:
name: release-notes
path: ./
- name: Verify artifacts
run: |
echo "Checking for downloaded artifacts..."
if [ -f "./RELEASE_NOTES.md" ]; then
echo "✅ Release notes found"
echo "Content preview:"
head -n 5 ./RELEASE_NOTES.md
else
echo "❌ Release notes not found!"
ls -la ./
fi
if [ -f "./bom.json" ]; then
echo "✅ SBOM file found"
else
echo "❌ SBOM file not found!"
ls -la ./
fi
echo "Build artifacts:"
ls -la dist/ || echo "Dist directory not found!"
- name: Setup Node.js for publishing
uses: actions/setup-node@v4
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- name: Attest build provenance for SBOM
uses: actions/attest-build-provenance@v2
with:
subject-path: ./bom.json
- name: Attest build provenance for build artifacts
uses: actions/attest-build-provenance@v2
with:
subject-path: 'dist/*'
- name: Create GitHub Release
id: create_release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.version.outputs.version }}
name: Release v${{ steps.version.outputs.version }}
body_path: ./RELEASE_NOTES.md
files: |
./bom.json
draft: false
prerelease: false
generate_release_notes: false # We create our own custom notes
fail_on_unmatched_files: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish to NPM
if: success()
run: |
echo "Version in package.json before publishing:"
cat package.json | grep version
# Make sure all dependencies are installed correctly (including dev dependencies)
echo "Reinstalling dependencies to ensure build works"
npm ci
# Verify TypeScript is working
echo "Checking TypeScript config"
npx tsc --version
npx tsc --listFiles
echo "Publishing to NPM with provenance (using OIDC Trusted Publisher)"
npm publish --provenance --access public
echo "NPM publish complete"
# No NODE_AUTH_TOKEN needed - using npm Trusted Publishers (OIDC)
- name: Release complete
env:
RELEASE_VERSION: ${{ steps.version.outputs.version }}
run: |
echo "✅ Release process complete"
echo "Version: v$RELEASE_VERSION"
echo "Release URL: ${{ steps.create_release.outputs.url }}"
echo "SBOM and detailed release notes published to GitHub release"