-
Notifications
You must be signed in to change notification settings - Fork 2
345 lines (307 loc) · 13.9 KB
/
deploy-s3.yml
File metadata and controls
345 lines (307 loc) · 13.9 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
name: Deploy to S3
on:
push:
branches: ["main"]
workflow_dispatch:
inputs:
fix_mimetypes:
description: 'Fix MIME types on ALL existing S3 objects (no build/deploy, only metadata fix)'
required: false
default: 'false'
type: choice
options:
- 'false'
- 'true'
permissions:
contents: read
id-token: write
actions: write
env:
AWS_REGION : "us-east-1"
AWS_REGION_ZONE : "us-east-1"
S3_BUCKET_NAME: "riksdagsmonitor-frontend-us-east-1-172017021075"
CLOUDFRONT_STACK_NAME: "riksdagsmonitor-frontend"
jobs:
deploy:
if: ${{ github.event.inputs.fix_mimetypes != 'true' }}
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: block
allowed-endpoints: >
accounts.google.com:443
amazon-cloudfront-secure-static-site-s3bucketroot-14oliw5cmta06.s3.us-east-1.amazonaws.com:443
api.github.com:443
api.securityscorecards.dev:443
app.fossa.io:443
auth.docker.io:443
bestpractices.coreinfrastructure.org:443
cfu.zaproxy.org:443
cla-assistant.io:443
cla-assistant.io:80
clients2.google.com:80
cloudformation.us-east-1.amazonaws.com:443
cloudfront.amazonaws.com:443
content-signature-2.cdn.mozilla.net:443
deb.debian.org:80
firefox-settings-attachments.cdn.mozilla.net:443
firefox.settings.services.mozilla.com:443
fonts.googleapis.com:443
fonts.gstatic.com:443
ghcr.io:443
github.com:443
hack23.com:443
hack23.com:80
hack23.comnull:443
img.shields.io:443
isitmaintained.com:443
isitmaintained.com:80
location.services.mozilla.com:443
news.zaproxy.org:443
objects.githubusercontent.com:443
pkg-containers.githubusercontent.com:443
production.cloudflare.docker.com:443
r10.o.lencr.org:443
r11.o.lencr.org:80
raw.githubusercontent.com:443
registry-1.docker.io:443
registry.npmjs.org:443
safebrowsingohttpgateway.googleapis.com:443
shavar.services.mozilla.com:443
slsa.dev:443
sonarcloud.io:443
storage.googleapis.com:443
sts.us-east-1.amazonaws.com:443
tel.zaproxy.org:443
tracking-protection.cdn.mozilla.net:443
us-central1-lighthouse-infrastructure.cloudfunctions.net:443
www.google.com:443
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0 # Need full history to restore mtimes
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '25'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Guard against broken HTML regressions
run: |
echo "🔍 Checking committed news articles for known bad patterns…"
FAIL=0
# No raw TypeScript script references in articles
COUNT=$(grep -rl 'src="[^"]*back-to-top\.ts"' news/ 2>/dev/null | wc -l)
if [ "$COUNT" -gt 0 ]; then
echo "❌ $COUNT article(s) still reference back-to-top.ts"
grep -rl 'src="[^"]*back-to-top\.ts"' news/ | head -5
FAIL=1
fi
# No non-existent news-article.js references
COUNT=$(grep -rl 'news-article\.js' news/ 2>/dev/null | wc -l)
if [ "$COUNT" -gt 0 ]; then
echo "❌ $COUNT article(s) still reference news-article.js"
FAIL=1
fi
# No absolute /js/lib/ paths
COUNT=$(grep -rl 'src="/js/lib/' news/ 2>/dev/null | wc -l)
if [ "$COUNT" -gt 0 ]; then
echo "❌ $COUNT article(s) use absolute /js/lib/ path"
FAIL=1
fi
if [ "$FAIL" -eq 0 ]; then
echo "✅ No broken HTML patterns found in news articles."
else
exit 1
fi
- name: Build with Vite
# Vite/Rollup loads ~3500 news/*.html entries (auto-discovered in
# vite.config.js → discoverNewsArticles()), and at the
# `rendering chunks` phase the default ~4 GB Node heap is no longer
# enough — the build OOMs with
# `FATAL ERROR: Ineffective mark-compacts near heap limit
# Allocation failed - JavaScript heap out of memory` → exit 134.
# GitHub-hosted ubuntu-latest runners ship with 16 GB RAM, so
# raising the V8 old-space limit to 8 GB gives the build headroom
# for further growth in news-article volume without changing the
# build graph.
env:
NODE_OPTIONS: --max-old-space-size=8192
run: npm run build
- name: Verify build artifacts
run: |
echo "Verifying Vite build output..."
if [ ! -d "dist" ]; then
echo "❌ Build verification failed: 'dist' directory is missing."
exit 1
fi
if [ ! -f "dist/index.html" ]; then
echo "❌ Build verification failed: 'dist/index.html' is missing."
exit 1
fi
if [ ! -f "dist/rss.xml" ]; then
echo "❌ Build verification failed: 'dist/rss.xml' is missing."
exit 1
fi
if [ ! -f "dist/sitemap.xml" ]; then
echo "❌ Build verification failed: 'dist/sitemap.xml' is missing."
exit 1
fi
if [ ! -f "dist/sitemap.html" ]; then
echo "❌ Build verification failed: 'dist/sitemap.html' is missing."
exit 1
fi
# Spot-check a representative subset of localized HTML sitemaps
# (Swedish — primary non-English audience, Arabic — RTL coverage).
for loc in sitemap_sv.html sitemap_ar.html; do
if [ ! -f "dist/$loc" ]; then
echo "❌ Build verification failed: 'dist/$loc' is missing."
exit 1
fi
done
# Political Intelligence pages (added by PR #1962) must ship to S3.
# They are explicit Vite inputs — a missing one means the Vite
# rollupOptions.input list has drifted out of sync with the
# generator in scripts/generate-political-intelligence.ts.
if [ ! -f "dist/political-intelligence.html" ]; then
echo "❌ Build verification failed: 'dist/political-intelligence.html' is missing."
echo " Ensure all 14 political-intelligence* entries are listed in vite.config.js rollupOptions.input."
exit 1
fi
for loc in political-intelligence_sv.html political-intelligence_ar.html; do
if [ ! -f "dist/$loc" ]; then
echo "❌ Build verification failed: 'dist/$loc' is missing."
exit 1
fi
done
echo "✅ Build artifacts verified: 'dist' directory, 'dist/index.html', 'dist/rss.xml', 'dist/sitemap.xml', HTML sitemaps, and political-intelligence pages exist."
if [ ! -d "dist/news" ]; then
echo "❌ Build verification failed: 'dist/news/' directory is missing."
exit 1
fi
echo "✅ dist/news/ directory present."
- name: Copy documentation to build output
run: |
if [ -d "docs" ]; then
echo "📚 Copying documentation to dist/docs/..."
cp -r docs dist/docs
echo "✅ Documentation copied to dist/docs/"
else
echo "ℹ️ No docs directory found, skipping"
fi
- name: Copy JS libraries to build output
run: |
echo "📦 Copying js/ directory to dist/js/..."
# The js/ directory contains utility scripts (theme-init.js, etc.)
# and vendor libraries (js/lib/chart.umd.*.js, d3, papaparse, etc.)
# that are referenced by news articles and dashboard pages via
# relative paths (../js/lib/chart.umd.4.4.1.js).
# Vite only copies public/js/ to dist/js/, so we merge the full
# js/ tree on top — cp -rn (no-clobber) keeps Vite's copies when
# both directories contain the same filename.
if [ -d "js" ]; then
cp -rn js/* dist/js/
echo "✅ JS directory merged into dist/js/"
echo " Files in dist/js/:"
find dist/js -type f | sort
else
echo "⚠️ No js/ directory found — skipping"
fi
- name: configure aws credentials
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
with:
role-to-assume: arn:aws:iam::172017021075:role/GithubWorkFlowRole
role-session-name: githubworkflowrolesessiont2
aws-region: ${{ env.AWS_REGION }}
- name: Deploy to S3 with cache headers
run: bash scripts/deploy-s3.sh dist "s3://${{ env.S3_BUCKET_NAME }}"
# Invalidate CloudFront cache to ensure latest content is served
# Note: Using blanket "/*" invalidation is optimal for this use case:
# - Site has ~500 files (well within 1000 free invalidations/month)
# - Deployment frequency is low (PR-based, not continuous)
# - Simplicity trumps optimization at this scale
# - Cache headers already optimized (1hr HTML, 1yr assets, 24hr metadata)
- name: Invalidate CloudFront
run: |
echo "🔍 Discovering CloudFront distribution ID from stack: ${{ env.CLOUDFRONT_STACK_NAME }}"
CloudFrontDistId=$(aws cloudformation describe-stacks \
--stack-name ${{ env.CLOUDFRONT_STACK_NAME }} \
--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..."
# List all distributions and filter by S3 bucket origin
CloudFrontDistId=$(aws cloudfront list-distributions \
--output json 2>/dev/null | \
jq -r ".DistributionList.Items[] | select(.Origins.Items[].DomainName | contains(\"${{ env.S3_BUCKET_NAME }}\")) | .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 for all paths..."
aws cloudfront create-invalidation \
--distribution-id $CloudFrontDistId \
--paths "/*"
echo "✅ CloudFront cache invalidation completed"
# ── Fix MIME types on existing S3 objects (manual trigger only) ──
fix-mimetypes:
if: ${{ github.event.inputs.fix_mimetypes == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: block
allowed-endpoints: >
api.github.com:443
github.com:443
objects.githubusercontent.com:443
riksdagsmonitor-frontend-us-east-1-172017021075.s3.us-east-1.amazonaws.com:443
s3.us-east-1.amazonaws.com:443
sts.us-east-1.amazonaws.com:443
cloudfront.amazonaws.com:443
cloudformation.us-east-1.amazonaws.com:443
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
sparse-checkout: scripts/fix-s3-mimetypes.sh
sparse-checkout-cone-mode: false
- name: configure aws credentials
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
with:
role-to-assume: arn:aws:iam::172017021075:role/GithubWorkFlowRole
role-session-name: githubworkflowrolesessiont2
aws-region: ${{ env.AWS_REGION }}
- name: Fix MIME types on all S3 objects
run: bash scripts/fix-s3-mimetypes.sh "s3://${{ env.S3_BUCKET_NAME }}"
- name: Invalidate CloudFront
run: |
echo "🔍 Discovering CloudFront distribution ID from stack: ${{ env.CLOUDFRONT_STACK_NAME }}"
CloudFrontDistId=$(aws cloudformation describe-stacks \
--stack-name ${{ env.CLOUDFRONT_STACK_NAME }} \
--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"
CloudFrontDistId=$(aws cloudfront list-distributions \
--output json 2>/dev/null | \
jq -r ".DistributionList.Items[] | select(.Origins.Items[].DomainName | contains(\"${{ env.S3_BUCKET_NAME }}\")) | .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 for all paths..."
aws cloudfront create-invalidation \
--distribution-id $CloudFrontDistId \
--paths "/*"
echo "✅ CloudFront cache invalidation completed after MIME type fix"