L'application est à la racine du site : plateau en SVG, saisie au toucher (pion puis case d'arrivée, proposées à partir du moteur), boutons du jet, du videau et de nouvelle partie, coup de l'ordinateur affiché avec son temps, fin de partie, reprise depuis localStorage, aide lue dans les copies de docs/. Le moteur tourne dans le Web Worker existant ; main.ts est le seul assembleur (nouvelles règles dependency-cruiser). Le service worker met tout le site en cache, avec une empreinte du contenu comme version. Les pages de test du moteur passent sous /moteur/. Politique de sécurité du contenu dans le Caddyfile ; make test-browser joue une partie dans Chromium et Firefox sans écran (Playwright, hors de make test). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
104 lines
4.5 KiB
Bash
Executable File
104 lines
4.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Vérifie l'image de production (deploy/) avec le site assemblé (make package) :
|
|
# construit l'image en local, la lance, et contrôle ce que reçoit un
|
|
# navigateur (application, pages de test du moteur, aide, manifeste, service
|
|
# worker, types MIME, compression, politique de sécurité, version).
|
|
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")/.."
|
|
|
|
unset MAKEFLAGS MFLAGS MAKELEVEL
|
|
make -s package >/dev/null
|
|
|
|
image="ustagammon/site-test:local"
|
|
docker build -q -t "$image" build/deploy >/dev/null
|
|
container="$(docker run -d --rm -p 127.0.0.1::80 "$image")"
|
|
trap 'docker stop "$container" >/dev/null' EXIT
|
|
port="$(docker port "$container" 80/tcp | head -n1 | sed 's/.*://')"
|
|
base="http://127.0.0.1:$port"
|
|
|
|
# Caddy démarre en une fraction de seconde : on attend qu'il réponde.
|
|
for _ in $(seq 1 50); do
|
|
curl -sf -o /dev/null "$base/" && break
|
|
sleep 0.1
|
|
done
|
|
|
|
errors=0
|
|
report() {
|
|
echo "$1"
|
|
errors=$((errors + 1))
|
|
}
|
|
|
|
# check <chemin> <début du type MIME attendu>
|
|
check() {
|
|
local headers
|
|
headers="$(curl -s -D - -o /dev/null "$base/$1")"
|
|
grep -qE "^HTTP/1.1 200" <<<"$headers" || report "/$1 : pas de réponse 200"
|
|
grep -qiE "^Content-Type: $2" <<<"$headers" || report "/$1 : type MIME attendu $2"
|
|
}
|
|
|
|
check "" "text/html"
|
|
check "app.css" "text/css"
|
|
check "app/main.js" "text/javascript"
|
|
check "app/ui/app.js" "text/javascript"
|
|
check "app/domain/game/game.js" "text/javascript"
|
|
check "manifest.webmanifest" "application/manifest\+json"
|
|
check "icons/icon-192.png" "image/png"
|
|
check "icons/icon-512.png" "image/png"
|
|
check "sw.js" "text/javascript"
|
|
check "engine-worker.mjs" "text/javascript"
|
|
check "wasm/engine.mjs" "text/javascript"
|
|
check "wasm/engine.wasm" "application/wasm"
|
|
check "wasm/engine.data" "application/octet-stream"
|
|
check "version.json" "application/json"
|
|
# Pages de test du moteur (étape 0b).
|
|
check "moteur/" "text/html"
|
|
check "moteur/bench.html" "text/html"
|
|
check "moteur/engine-client.mjs" "text/javascript"
|
|
# Aide : copies du glossaire et des scénarios (ADR 0008).
|
|
check "docs/scenarios/jouer-une-partie.feature" "text/plain"
|
|
check "docs/glossaire/README.md" "text/markdown"
|
|
check "docs/glossaire/videau.md" "text/markdown"
|
|
cmp -s <(curl -s "$base/docs/glossaire/videau.md") docs/glossaire/videau.md \
|
|
|| report "/docs/glossaire/videau.md diffère de la source"
|
|
|
|
# Le manifeste désigne des icônes servies, et l'application à la racine.
|
|
manifest="$(curl -s "$base/manifest.webmanifest")"
|
|
grep -q '"start_url": "./"' <<<"$manifest" || report "manifeste : start_url absent"
|
|
while read -r icon; do
|
|
curl -sf -o /dev/null "$base/$icon" || report "manifeste : icône $icon introuvable"
|
|
done < <(grep -o '"src": "[^"]*"' <<<"$manifest" | sed 's/"src": "\(.*\)"/\1/')
|
|
|
|
# Le service worker met en cache chaque fichier du site, avec une version.
|
|
sw="$(curl -s "$base/sw.js")"
|
|
grep -qE '^const VERSION = "[0-9a-f]{16}";' <<<"$sw" || report "sw.js : version absente"
|
|
for file in ./ index.html app/main.js wasm/engine.data wasm/engine.wasm moteur/ \
|
|
docs/glossaire/videau.md version.json; do
|
|
grep -qF " \"$file\"," <<<"$sw" || report "sw.js : $file absent de la liste à mettre en cache"
|
|
done
|
|
listed="$(grep -c '^ "' <<<"$sw")"
|
|
present="$(find build/site -type f ! -name sw.js | wc -l)"
|
|
# Chaque fichier, plus « ./ » et « moteur/ » pour les deux index.html.
|
|
[ "$listed" -eq "$((present + 2))" ] \
|
|
|| report "sw.js liste $listed adresses pour $present fichiers (plus 2 dossiers)"
|
|
grep -o '^ "[^"]*"' <<<"$sw" | tr -d ' "' | while read -r file; do
|
|
curl -sf -o /dev/null "$base/$file" || echo "sw.js : $file introuvable sur le site"
|
|
done | grep . && report "sw.js liste des fichiers introuvables"
|
|
|
|
# Politique de sécurité du contenu : WebAssembly permis, rien en ligne.
|
|
csp="$(curl -s -D - -o /dev/null "$base/" | grep -i '^Content-Security-Policy:')"
|
|
grep -q "script-src 'self' 'wasm-unsafe-eval';" <<<"$csp" \
|
|
|| report "CSP absente ou sans 'wasm-unsafe-eval'"
|
|
grep -q "unsafe-inline" <<<"$csp" && report "CSP : unsafe-inline ne doit pas être permis"
|
|
curl -s -D - -o /dev/null "$base/engine-worker.mjs" | grep -qi '^Content-Security-Policy:' \
|
|
|| report "CSP absente du Web Worker du moteur"
|
|
|
|
curl -s -H "Accept-Encoding: gzip" -D - -o /dev/null "$base/wasm/engine.data" \
|
|
| grep -qi "^Content-Encoding: gzip" || report "/wasm/engine.data : pas de compression gzip"
|
|
curl -s -D - -o /dev/null "$base/" | grep -qi "^Cache-Control: no-cache" \
|
|
|| report "/ : en-tête Cache-Control absent"
|
|
[ "$(curl -s "$base/version.json" | sed -n 's/.*"commit": "\([0-9a-f]*\)".*/\1/p')" = "$(git rev-parse HEAD)" ] \
|
|
|| report "version.json ne donne pas le commit courant"
|
|
|
|
[ "$errors" -eq 0 ]
|