-
Notifications
You must be signed in to change notification settings - Fork 2
693 lines (601 loc) · 29.4 KB
/
release.yml
File metadata and controls
693 lines (601 loc) · 29.4 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
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
name: Release with Attestations
on:
workflow_dispatch:
inputs:
version:
description: "Version to release (vX.Y.Z format)"
required: true
default: "v1.0.0"
prerelease:
description: "Is this a pre-release?"
type: boolean
default: false
push:
tags:
- "v*"
# Restrict top-level permissions to minimum required defaults
permissions: read-all
jobs:
prepare:
name: Prepare Release
runs-on: ubuntu-latest
# Only prepare job needs write permissions for commit and tagging
permissions:
contents: write # Required for git auto-commit
outputs:
version: ${{ steps.get-version.outputs.version }}
npm_version: ${{ steps.get-version.outputs.npm_version }}
is_prerelease: ${{ github.event.inputs.prerelease || 'false' }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Get version
id: get-version
run: |
if [[ "${{ github.event_name }}" == "push" ]]; then
VERSION=${GITHUB_REF#refs/tags/}
else
VERSION=${{ github.event.inputs.version }}
fi
echo "version=${VERSION}" >> $GITHUB_OUTPUT
# npm version without 'v' prefix
NPM_VERSION="${VERSION#v}"
echo "npm_version=${NPM_VERSION}" >> $GITHUB_OUTPUT
echo "Version: ${VERSION}"
echo "npm version: ${NPM_VERSION}"
- name: Setup Web Test Environment (Chrome + Xvfb + Dependencies)
timeout-minutes: 15
run: |
echo "🔧 Setting up complete Web test environment..."
# Install system dependencies for Web rendering with retries to reduce
# the impact of transient apt mirror/network failures and hanging installs.
for attempt in 1 2 3; do
if sudo apt-get update -o Acquire::Retries=5 -o Acquire::http::Timeout=30; then
break
fi
echo "⚠️ apt-get update failed (attempt $attempt) — retrying in $((attempt * 10))s"
sleep $((attempt * 10))
if [ "$attempt" -eq 3 ]; then
echo "❌ apt-get update failed after 3 attempts"
exit 1
fi
done
for attempt in 1 2 3; do
if sudo apt-get install -y --no-install-recommends \
xvfb dbus-x11 x11-utils \
libgtk2.0-0 libgtk-3-0 libgbm-dev libgbm1 \
libnotify-dev libnss3 libxss1 \
libxtst6 xauth \
graphviz ffmpeg \
fonts-noto fonts-noto-cjk fonts-noto-cjk-extra \
ca-certificates fonts-liberation \
libatk-bridge2.0-0 libatk1.0-0 libcups2 \
libdbus-1-3 libdrm2 libnspr4 \
libx11-xcb1 libxcomposite1 libxdamage1 \
libxfixes3 libxrandr2 libxrender1 \
libxshmfence1 xdg-utils wget libxkbcommon0 xkb-data; then
break
fi
echo "⚠️ apt-get install failed (attempt $attempt) — retrying in $((attempt * 10))s"
sleep $((attempt * 10))
if [ "$attempt" -eq 3 ]; then
echo "❌ apt-get install failed after 3 attempts"
exit 1
fi
done
# Setup D-Bus for Chrome
sudo mkdir -p /var/run/dbus
sudo dbus-daemon --system --fork || true
echo "✅ Web test environment setup complete"
env:
DISPLAY: ":99"
XKB_DEFAULT_RULES: evdev
XKB_DEFAULT_MODEL: pc105
XKB_DEFAULT_LAYOUT: us
- name: Start Xvfb
run: |
echo "🖥️ Starting Xvfb virtual display..."
Xvfb :99 -screen 0 1920x1080x24 &
sleep 2
echo "DISPLAY=:99" >> $GITHUB_ENV
echo "✅ Xvfb started on display :99"
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "26"
cache: "npm"
cache-dependency-path: |
package-lock.json
.github/workflows/release.yml
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Set Version for release
if: github.event_name == 'workflow_dispatch'
run: |
PLAIN_VERSION="${{ github.event.inputs.version }}"
# Remove 'v' prefix if present
PLAIN_VERSION="${PLAIN_VERSION#v}"
npm version $PLAIN_VERSION --no-git-tag-version
- uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
if: github.event_name == 'workflow_dispatch'
with:
commit_message: "chore(release): bump version to ${{ github.event.inputs.version }}"
tagging_message: "${{ github.event.inputs.version }}"
- name: Generate API documentation
run: |
echo "📚 Generating TypeDoc API documentation..."
npm run typedoc
echo "✅ API documentation generated in api/"
- name: Verify Cypress
run: npx cypress verify
- name: Run unit tests with coverage
run: |
mkdir -p builds/test-results builds/coverage
npm run coverage
- name: Start app and run E2E tests
shell: bash
env:
CYPRESS_VIDEO: false
NODE_OPTIONS: "--max-old-space-size=8192"
XKB_DEFAULT_RULES: evdev
XKB_DEFAULT_MODEL: pc105
XKB_DEFAULT_LAYOUT: us
TERM: dumb
run: |
echo "🚀 Running E2E tests for release validation..."
mkdir -p builds/cypress
set -o pipefail
NODE_OPTIONS="--max-old-space-size=8192" \
xvfb-run --auto-servernum --server-args="-screen 0 1280x720x24 -ac +extension GLX +extension RANDR +render -nolisten tcp" \
npm run e2e 2>&1 | tee builds/cypress/e2e-output.log
TEST_EXIT_CODE=$?
# Copy Cypress screenshots if they exist
cp -r cypress/screenshots builds/cypress/ 2>/dev/null || true
exit ${TEST_EXIT_CODE}
- name: Clean old docs
run: |
echo "🧹 Cleaning old documentation..."
rm -rf docs/api/* || true
rm -rf docs/cypress/* || true
rm -rf docs/test-results/* || true
rm -rf docs/coverage/* || true
rm -rf docs/dependencies/* || true
echo "✅ Old documentation cleaned"
- name: Copy API documentation to docs
run: |
echo "📁 Syncing API documentation to docs/api/..."
mkdir -p docs/api
if [ -d "api" ]; then
cp -r api/* docs/api/ || true
echo "✅ API documentation available in docs/api/"
else
echo "⚠️ TypeDoc output directory 'api' not found; skipping copy to docs/api/"
fi
- name: Generate dependencies documentation
run: |
echo "📦 Generating dependency tree documentation..."
npm run docs:dependencies
echo "✅ Dependency documentation generated in docs/dependencies/"
- name: Copy test reports to docs
run: |
echo "📊 Copying test reports to docs/..."
npm run build:test-reports
# Vitest HTML reporter output (builds/test-results/html/) is copied
# automatically by build:test-reports to docs/test-results/html/
# Istanbul HTML coverage is copied to docs/coverage/ (includes index.html)
# Generate E2E test results HTML index from log output
node scripts/generate-e2e-results-html.cjs || echo "⚠️ Failed to generate E2E test results HTML"
# Generate dependency report HTML from npm tree
node scripts/generate-dependency-report-html.cjs || echo "⚠️ Failed to generate dependency report HTML"
echo "✅ Test reports copied to docs/"
- name: Create documentation index
run: |
echo "📝 Creating documentation index..."
cat > docs/index.html << 'DOCSEOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Riksdagsmonitor Documentation Hub</title>
<style>
:root{--bg:#0a0e27;--bg2:#1a1e3d;--cyan:#00d9ff;--green:#00ff88;--red:#ff006e;--yellow:#ffbe0b;--text:#e0e0e0;--muted:#808080;--card:rgba(26,30,61,.6);--border:rgba(0,217,255,.3)}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif;line-height:1.6;color:var(--text);background:linear-gradient(135deg,var(--bg) 0%,var(--bg2) 100%);min-height:100vh;padding:2rem}
.container{max-width:1200px;margin:0 auto}
h1{color:var(--cyan);font-size:2.5rem;margin-bottom:.5rem;text-shadow:0 0 10px rgba(0,217,255,.5)}
.subtitle{color:var(--red);font-size:1.2rem;margin-bottom:1rem}
.release-info{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1rem 1.5rem;margin-bottom:2rem;font-size:.9rem;color:var(--muted)}
.release-info strong{color:var(--cyan)}
h2{color:var(--yellow);font-size:1.3rem;margin:2rem 0 1rem;padding-bottom:.5rem;border-bottom:1px solid rgba(0,217,255,.15)}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1.5rem;margin-bottom:2rem}
.card{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1.5rem;transition:all .3s ease}
.card:hover{border-color:var(--cyan);box-shadow:0 0 20px rgba(0,217,255,.4);transform:translateY(-4px)}
.card h3{color:var(--yellow);font-size:1.2rem;margin-bottom:.75rem}
.card p{margin-bottom:1rem;color:#b0b0b0;font-size:.9rem}
.card .links{display:flex;flex-wrap:wrap;gap:.5rem}
.card .links a{display:inline-block;padding:.5rem 1rem;background:linear-gradient(135deg,#00d9ff 0%,#0099cc 100%);color:#0a0e27;text-decoration:none;border-radius:4px;font-weight:700;font-size:.85rem;transition:all .3s ease}
.card .links a:hover{background:linear-gradient(135deg,#00ffff 0%,#00d9ff 100%);box-shadow:0 0 10px rgba(0,217,255,.6)}
.card .links a.secondary{background:rgba(0,217,255,.1);color:var(--cyan);border:1px solid var(--border)}
.card .links a.secondary:hover{background:rgba(0,217,255,.2)}
.footer{text-align:center;color:var(--muted);margin-top:3rem;padding-top:2rem;border-top:1px solid rgba(0,217,255,.2);font-size:.85rem}
</style>
</head>
<body>
<div class="container">
<h1>📚 Riksdagsmonitor Documentation Hub</h1>
<p class="subtitle">Swedish Parliament Intelligence Platform — Technical Documentation</p>
<div class="release-info">
📅 Generated during release workflow · <strong>Riksdagsmonitor</strong> by Hack23 AB
</div>
<h2>📖 API & Architecture</h2>
<div class="grid">
<div class="card">
<h3>📖 API Documentation</h3>
<p>Comprehensive TypeDoc-generated API reference for all TypeScript modules, dashboard components, and utility functions.</p>
<div class="links">
<a href="api/index.html">View API Docs →</a>
</div>
</div>
</div>
<h2>🧪 Testing & Quality</h2>
<div class="grid">
<div class="card">
<h3>🧪 Unit Test Results</h3>
<p>Interactive Vitest HTML report with test tree, filtering, duration breakdown, and re-run capability (built-in @vitest/ui reporter).</p>
<div class="links">
<a href="test-results/html/index.html">Vitest HTML Report →</a>
<a href="test-results/vitest-results.json" class="secondary">JSON</a>
</div>
</div>
<div class="card">
<h3>🎭 E2E Test Results</h3>
<p>Cypress end-to-end test reports with spec-level breakdown, timing per spec, screenshot gallery, and searchable log.</p>
<div class="links">
<a href="cypress/index.html">View E2E Reports →</a>
</div>
</div>
<div class="card">
<h3>📊 Test Coverage</h3>
<p>Istanbul HTML coverage report with per-file line/branch/function/statement metrics, source annotation, and directory breakdown (built-in @vitest/coverage-v8).</p>
<div class="links">
<a href="coverage/index.html">Istanbul Report →</a>
<a href="coverage/lcov-report/index.html" class="secondary">LCOV</a>
<a href="coverage/coverage-final.json" class="secondary">JSON</a>
</div>
</div>
</div>
<h2>📦 Dependencies & Supply Chain</h2>
<div class="grid">
<div class="card">
<h3>📦 Dependency Report</h3>
<p>Interactive dependency report with package counts, search, prod/dev badges, and expandable full tree view.</p>
<div class="links">
<a href="dependencies/index.html">View Dependencies →</a>
<a href="dependencies/dependency-tree.json" class="secondary">JSON</a>
<a href="dependencies/dependency-tree.txt" class="secondary">TXT</a>
</div>
</div>
</div>
<h2>🔗 Quick Links</h2>
<div class="grid">
<div class="card">
<h3>🏠 Main Site</h3>
<p>Return to the main Riksdagsmonitor platform to explore Swedish Parliament intelligence dashboards.</p>
<div class="links">
<a href="../index.html">Go to Main Site →</a>
</div>
</div>
</div>
<div class="footer">
<p>Generated during release workflow · Riksdagsmonitor by Hack23 AB · Licensed under Apache License 2.0</p>
</div>
</div>
</body>
</html>
DOCSEOF
echo "✅ Documentation index created at docs/index.html"
- name: Create .nojekyll file
run: |
echo "🔧 Creating .nojekyll file to bypass Jekyll processing..."
touch docs/.nojekyll
echo "✅ .nojekyll file created"
- name: Update sitemap
run: |
echo "🗺️ Updating sitemap.xml..."
npm run docs:sitemap
echo "✅ Sitemap updated"
- name: Deploy Documentation to GitHub Pages
uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4.8.0
with:
folder: docs
target-folder: docs
branch: main
clean: false
commit-message: "docs: update documentation for ${{ steps.get-version.outputs.version || 'development' }}"
build:
name: Build Release Package
needs: [prepare]
runs-on: ubuntu-latest
# Build job needs specific permissions for attestations
permissions:
contents: read
id-token: write # Required for OIDC
attestations: write # Required for SBOM and build attestations
steps:
- name: Harden Runner
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
# Use GITHUB_REF directly for tag events
ref: ${{ github.event_name == 'push' && github.ref || github.event_name == 'workflow_dispatch' && github.event.inputs.version || '' }}
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "26"
cache: "npm"
cache-dependency-path: |
package-lock.json
.github/workflows/release.yml
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Create release artifacts
run: |
echo "📦 Creating release artifacts..."
# Create the zip file from the dist directory
cd dist
zip -r ../riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip .
cd ..
# Create checksums
sha256sum riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip > riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip.sha256
echo "✅ Release artifacts created"
- name: Upload build artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: build-artifacts
path: |
dist/
riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip
riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip.sha256
if-no-files-found: error
- name: Generate SBOM
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
id: sbom
with:
format: spdx-json
output-file: riksdagsmonitor-${{ needs.prepare.outputs.version }}.spdx.json
artifact-name: riksdagsmonitor-${{ needs.prepare.outputs.version }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
id: attest
with:
subject-path: riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip
- name: Copy artifact attestation for zip
run: cp ${{ steps.attest.outputs.bundle-path }} riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip.intoto.jsonl
- name: Generate SBOM attestation
id: attestsbom
uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0
with:
subject-path: riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip
sbom-path: riksdagsmonitor-${{ needs.prepare.outputs.version }}.spdx.json
- name: Copy SBOM attestation for zip
run: cp ${{ steps.attestsbom.outputs.bundle-path }} riksdagsmonitor-${{ needs.prepare.outputs.version }}.spdx.json.intoto.jsonl
- name: Upload security artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: security-artifacts
path: |
riksdagsmonitor-${{ needs.prepare.outputs.version }}.spdx.json
riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip.intoto.jsonl
riksdagsmonitor-${{ needs.prepare.outputs.version }}.spdx.json.intoto.jsonl
if-no-files-found: error
release:
name: Create Release and Deploy
needs: [prepare, build]
runs-on: ubuntu-latest
# Release job needs specific permissions to create GitHub releases and deploy
permissions:
contents: write # Required to create releases
id-token: write # Required for OIDC/AWS
steps:
- name: Harden Runner
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
with:
egress-policy: audit
# Checkout main branch to get latest documentation
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
ref: main # Always use main branch for deployment
- name: Download build artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: build-artifacts
path: artifacts/build
- name: Download security artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: security-artifacts
path: artifacts/security
# NOTE: release-drafter emits benign warnings about "pull_request_target.*" not being
# known webhook names. This is a known upstream issue (release-drafter/release-drafter#1369)
# caused by Probot's GitHub Actions adapter not recognizing these event types.
# The warnings are harmless and do not affect functionality.
- name: Draft Release Notes
id: release-drafter
uses: release-drafter/release-drafter@c2e2804cc59f45f57076a99af580d0fedb697927 # v7.3.0
with:
version: ${{ needs.prepare.outputs.version }}
tag: ${{ needs.prepare.outputs.version }}
name: Riksdagsmonitor ${{ needs.prepare.outputs.version }}
publish: false
prerelease: ${{ needs.prepare.outputs.is_prerelease }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Create GitHub Release with all artifacts
- name: Create GitHub Release
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
with:
tag: ${{ needs.prepare.outputs.version }}
name: Riksdagsmonitor ${{ needs.prepare.outputs.version }}
body: |
${{ steps.release-drafter.outputs.body }}
## 📦 Release Artifacts
- `riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip` - Production build
- `riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip.sha256` - Checksum for verification
- `riksdagsmonitor-${{ needs.prepare.outputs.version }}.spdx.json` - SBOM (Software Bill of Materials)
- `*.intoto.jsonl` - SLSA Build Provenance Attestations
## 📦 npm Package
Shared types, theme system, and utilities are available as an npm package:
```bash
npm install riksdagsmonitor
```
## 📚 Documentation
- [API Documentation](https://riksdagsmonitor.com/docs/api/)
- [Test Coverage Report](https://riksdagsmonitor.com/docs/coverage/)
- [E2E Test Reports](https://riksdagsmonitor.com/docs/cypress/)
- [Documentation Hub](https://riksdagsmonitor.com/docs/)
## 🔐 Security
All artifacts include SLSA Build Provenance attestations and SBOM for supply chain security.
Verify attestations using the GitHub CLI:
```bash
gh attestation verify riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip -R Hack23/riksdagsmonitor
```
generateReleaseNotes: false
immutableCreate: true
draft: false
prerelease: ${{ needs.prepare.outputs.is_prerelease }}
artifacts: |
artifacts/build/riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip
artifacts/build/riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip.sha256
artifacts/security/riksdagsmonitor-${{ needs.prepare.outputs.version }}.spdx.json
artifacts/security/riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip.intoto.jsonl
artifacts/security/riksdagsmonitor-${{ needs.prepare.outputs.version }}.spdx.json.intoto.jsonl
token: ${{ secrets.GITHUB_TOKEN }}
# ── Deploy to S3/CloudFront (primary deployment) ──────────────────
# NOTE: S3 deployment uses the main branch checkout (already done above)
# and downloaded build artifacts. This runs BEFORE the npm publish
# checkout to ensure deployment isn't blocked by npm-related failures.
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1
with:
role-to-assume: arn:aws:iam::172017021075:role/GithubWorkFlowRole
role-session-name: githubworkflowrolesession-release
aws-region: us-east-1
- name: Extract build to repository root
run: |
echo "📦 Extracting release build to repository root..."
# Remove old build artifacts
find . -maxdepth 1 -type f \( -name "*.html" -o -name "*.css" -o -name "*.js" -o -name "*.json" \) ! -name "package*.json" -delete
rm -rf styles/ js/ dashboard/ api/ || true
# Extract new build
unzip -o artifacts/build/riksdagsmonitor-${{ needs.prepare.outputs.version }}.zip -d .
# Create version marker
echo "Version ${{ needs.prepare.outputs.version }} deployed at $(date)" > version.txt
echo "✅ Build extracted and ready for deployment"
- name: Deploy to S3 with cache headers
run: bash scripts/deploy-s3.sh . "s3://riksdagsmonitor-frontend-us-east-1-172017021075"
- name: Invalidate CloudFront
run: |
echo "🔍 Discovering CloudFront distribution ID..."
CloudFrontDistId=$(aws cloudformation describe-stacks \
--stack-name riksdagsmonitor-frontend \
--query "Stacks[0].Outputs[?OutputKey=='CloudFrontDistributionId'].OutputValue" \
--output text 2>/dev/null || echo "")
if [ -z "$CloudFrontDistId" ]; then
echo "⚠️ Warning: CloudFront distribution ID not found in stack outputs"
echo "Attempting to find distribution by S3 origin domain..."
CloudFrontDistId=$(aws cloudfront list-distributions \
--output json 2>/dev/null | \
jq -r ".DistributionList.Items[] | select(.Origins.Items[].DomainName | contains(\"riksdagsmonitor-frontend\")) | .Id" | \
head -n 1 || echo "")
fi
if [ -z "$CloudFrontDistId" ] || [ "$CloudFrontDistId" = "None" ]; then
echo "❌ Error: Could not discover CloudFront distribution ID"
exit 1
fi
echo "✅ Found CloudFront distribution: $CloudFrontDistId"
echo "🔄 Creating cache invalidation..."
aws cloudfront create-invalidation \
--distribution-id $CloudFrontDistId \
--paths "/*"
echo "✅ CloudFront cache invalidation completed"
# ── npm Package Publish ──────────────────────────────────────────────
- name: Checkout release tag for npm publish
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ needs.prepare.outputs.version }}
- name: Setup Node.js for npm publish
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "26"
cache: "npm"
cache-dependency-path: |
package-lock.json
.github/workflows/release.yml
registry-url: "https://registry.npmjs.org"
- name: Install dependencies
run: npm ci
- name: Lint library source
run: npm run lint
- name: Build library package
run: npm run build:lib
- name: Run tests
run: npm test
- name: Verify package contents
run: |
echo "📦 Package contents preview:"
PACK_OUTPUT="$(npm pack --dry-run 2>&1)"
printf '%s\n' "$PACK_OUTPUT"
echo ""
echo "📊 Package size summary:"
printf '%s\n' "$PACK_OUTPUT" | grep -E "^npm notice (package size|unpacked size|total files)" || true
- name: Publish to npm with provenance
run: npm publish --ignore-scripts --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: true
- name: Verify npm package
run: |
echo "✅ Package published successfully!"
echo "📦 Package: riksdagsmonitor@${{ needs.prepare.outputs.npm_version }}"
echo "🔗 npm URL: https://www.npmjs.com/package/riksdagsmonitor"
echo ""
echo "To install:"
echo " npm install riksdagsmonitor@${{ needs.prepare.outputs.npm_version }}"
echo ""
echo "To verify provenance:"
echo " npm audit signatures"
- name: Deployment Summary
run: |
echo "🎉 Release ${{ needs.prepare.outputs.version }} deployment complete!"
echo ""
echo "📍 Deployments:"
echo " - Primary: https://riksdagsmonitor.com (S3/CloudFront)"
echo " - Backup: GitHub Pages (main branch)"
echo ""
echo "📚 Documentation:"
echo " - https://riksdagsmonitor.com/docs/"
echo " - API: https://riksdagsmonitor.com/docs/api/"
echo " - Coverage: https://riksdagsmonitor.com/docs/coverage/"
echo ""
echo "🔐 Security Artifacts:"
echo " - SBOM: riksdagsmonitor-${{ needs.prepare.outputs.version }}.spdx.json"
echo " - Attestations: *.intoto.jsonl files"
echo ""
echo "✅ All deployment steps completed successfully!"