mirror of
https://github.com/tiennm99/loto.git
synced 2026-09-05 18:16:53 +00:00
ci: add inline-script guard for built index.html
SvelteKit emits one inline bootstrap <script> in build/index.html and the CSP in static/_headers is relaxed to `script-src 'unsafe-inline'` to admit it. If a SvelteKit upgrade adds another inline block, the relaxation no longer matches reality and the new block could ship unhashed. `npm run verify:build` reads build/index.html, counts inline scripts (no `src=`), and fails when count > EXPECTED_INLINE (1). New GH Actions workflow runs test + build + verify on push/PR to main. Mutation-tested locally: setting EXPECTED_INLINE=0 fails as expected, restored to 1 passes.
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
name: Verify build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: verify-build-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
- run: npm run build
|
||||
- run: npm run verify:build
|
||||
@@ -46,7 +46,9 @@
|
||||
|------|---------|
|
||||
| `svelte.config.js` | adapter-static (HTML export), dual basePath via BUILD_PROFILE env, SvelteKit PWA plugin config. |
|
||||
| `vite.config.js` | Tailwind + SvelteKit + PWA plugins. codeserver HMR config (port, allowedHosts, hmr). |
|
||||
| `package.json` | SvelteKit 2, Svelte 5 (runes), Tailwind 4, Vite, @vite-pwa/sveltekit. Scripts: dev, dev:codeserver, build, build:gh, lint, test, test:watch. |
|
||||
| `package.json` | SvelteKit 2, Svelte 5 (runes), Tailwind 4, Vite, @vite-pwa/sveltekit. Scripts: dev, dev:codeserver, build, build:gh, lint, test, test:watch, verify:build. |
|
||||
| `scripts/verify-build-inline-scripts.mjs` | Post-build CSP guard. Counts inline `<script>` tags in `build/index.html` and fails if > EXPECTED_INLINE (1). Catches future SvelteKit upgrades that add inline blocks the CSP `'unsafe-inline'` relaxation isn't calibrated for. |
|
||||
| `.github/workflows/verify-build.yml` | CI: on push/PR to main runs `npm test && npm run build && npm run verify:build` to enforce the inline-script guard above. |
|
||||
| `eslint.config.mjs` | ESLint 9 flat config (@eslint/js + eslint-plugin-svelte). Declares Svelte 5 rune globals. |
|
||||
| `jsconfig.json` | Path alias `$lib`, no checkJs. |
|
||||
| `.gitignore` | Excludes node_modules, build, .env.local, etc. |
|
||||
|
||||
+2
-1
@@ -11,7 +11,8 @@
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint .",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
"test:watch": "vitest",
|
||||
"verify:build": "node scripts/verify-build-inline-scripts.mjs"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"lightningcss-linux-x64-gnu": "1.32.0"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
name: CI inline-script guard
|
||||
phase: 2
|
||||
status: todo
|
||||
status: completed
|
||||
priority: high
|
||||
effort: 30m
|
||||
completed: 2026-04-28
|
||||
---
|
||||
|
||||
# Phase 2 — CI inline-script guard
|
||||
|
||||
@@ -18,7 +18,7 @@ YAGNI — parking-lot features and upstream-blocked items skipped.
|
||||
| # | Phase | File |
|
||||
|---|-------|------|
|
||||
| 1 | Auto-tick integration test ✅ | `phase-01-auto-tick-test.md` |
|
||||
| 2 | CI inline-script guard | `phase-02-ci-inline-script-guard.md` |
|
||||
| 2 | CI inline-script guard ✅ | `phase-02-ci-inline-script-guard.md` |
|
||||
| 3 | Mode picker glyph redesign | `phase-03-mode-picker-glyphs.md` |
|
||||
| 4 | Settings modal sticky on small screens | `phase-04-settings-modal-sticky.md` |
|
||||
| 5 | Per-row "Chờ" indicator | `phase-05-cho-row-indicator.md` |
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Guard: count inline <script> tags in build/index.html. SvelteKit
|
||||
* currently emits exactly one inline bootstrap block — the relaxation
|
||||
* we ship in static/_headers (`script-src 'self' 'unsafe-inline'`)
|
||||
* is calibrated to that. If a future SvelteKit upgrade adds another
|
||||
* inline block, this guard fails CI so we either (a) hash the new
|
||||
* block into CSP or (b) bump EXPECTED_INLINE intentionally.
|
||||
*
|
||||
* Inline = no `src=` attribute. Module/external scripts are excluded.
|
||||
*/
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const EXPECTED_INLINE = 1;
|
||||
const HTML_PATH = "build/index.html";
|
||||
|
||||
let html;
|
||||
try {
|
||||
html = readFileSync(HTML_PATH, "utf8");
|
||||
} catch (e) {
|
||||
console.error(`verify-build: cannot read ${HTML_PATH} — run \`npm run build\` first.`);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const inline = (html.match(/<script(?![^>]*\bsrc=)[^>]*>/g) || []).length;
|
||||
|
||||
if (inline > EXPECTED_INLINE) {
|
||||
console.error(
|
||||
`verify-build: found ${inline} inline <script> tags in ${HTML_PATH} (expected ${EXPECTED_INLINE}).\n` +
|
||||
`If this is intentional, update EXPECTED_INLINE in scripts/verify-build-inline-scripts.mjs\n` +
|
||||
`AND add the SHA-256 hash(es) of the new inline block(s) to static/_headers script-src.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (inline < EXPECTED_INLINE) {
|
||||
console.warn(
|
||||
`verify-build: found ${inline} inline <script> tags but expected ${EXPECTED_INLINE}.\n` +
|
||||
`If SvelteKit changed its bootstrap strategy, lower EXPECTED_INLINE and tighten CSP.`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`verify-build: ${inline} inline <script> tag(s) — OK.`);
|
||||
Reference in New Issue
Block a user