diff --git a/.gitignore b/.gitignore index cef8cda..d2643c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ node_modules/ *.log assets/work/ -dist/ +/dist/ pilot/course-1.html pilot/arc.html diff --git a/assets/dist/appendix-dependency-ledger.webp b/assets/dist/appendix-dependency-ledger.webp new file mode 100644 index 0000000..16c6c46 Binary files /dev/null and b/assets/dist/appendix-dependency-ledger.webp differ diff --git a/assets/dist/course-VI-gitops.webp b/assets/dist/course-VI-gitops.webp new file mode 100644 index 0000000..29d4d8b Binary files /dev/null and b/assets/dist/course-VI-gitops.webp differ diff --git a/assets/dist/course-VII-supply-chain.webp b/assets/dist/course-VII-supply-chain.webp new file mode 100644 index 0000000..f264cc4 Binary files /dev/null and b/assets/dist/course-VII-supply-chain.webp differ diff --git a/assets/dist/course-VIII-helm-press.webp b/assets/dist/course-VIII-helm-press.webp new file mode 100644 index 0000000..9e1ac46 Binary files /dev/null and b/assets/dist/course-VIII-helm-press.webp differ diff --git a/assets/dist/docker-layers.webp b/assets/dist/docker-layers.webp new file mode 100644 index 0000000..e9a5d77 Binary files /dev/null and b/assets/dist/docker-layers.webp differ diff --git a/build.sh b/build.sh index 946229c..0e65e8b 100755 --- a/build.sh +++ b/build.sh @@ -1,70 +1,31 @@ #!/usr/bin/env bash # Assemble dist/ — the CSP-clean static build served at learn.bztmon.com. -# -# The artifact build inlines GSAP and the app JS because the artifact CSP blocks every -# external request. nginx has no such constraint, so here they stay SEPARATE files: -# `script-src 'self'` holds with no 'unsafe-inline' exception, and the two GSAP bundles -# (116KB, unchanged between deploys) become independently cacheable. +# The site's face is the delivery arc (index.html); scripts stay SEPARATE files so +# `script-src 'self'` holds with no 'unsafe-inline' exception. Assembly logic lives +# in pilot/assemble-arc.mjs (node — the build image carries no python). set -euo pipefail IFS=$'\n\t' - trap 'printf "build failed at line %s\n" "$LINENO" >&2' ERR here="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" cd -- "$here" -dist="$here/dist" -src_markup="ec2-part1.html" -src_app="ec2-part2.html" - -for f in "$src_markup" "$src_app" gsap.min.js st.min.js; do +for f in pilot/arc.tpl.html pilot/manifest-gitops.json pilot/manifest-supply.json \ + pilot/manifest-helm.json gsap.min.js st.min.js \ + assets/dist/course-VI-gitops.webp assets/dist/course-VII-supply-chain.webp \ + assets/dist/course-VIII-helm-press.webp assets/dist/appendix-dependency-ledger.webp; do [[ -f "$f" ]] || { printf 'missing source: %s\n' "$f" >&2; exit 1; } done +dist="$here/dist" rm -rf -- "$dist" -mkdir -p -- "$dist" - -# --- app.js: strip the single block; served standalone it must be raw JS. -sed -e '1{/^[[:space:]]* - - - - -HTML -} > "$dist/index.html" +node pilot/assemble-arc.mjs "$dist" # No inline JS may survive into the served HTML, or the CSP silently kills the page. -# Every "); +if (i < 0 || j < i) throw new Error("app script block not found"); +let app = tpl.slice(i + 8, j) + .replace("__MAN_GITOPS__", man("gitops")) + .replace("__MAN_SUPPLY__", man("supply")) + .replace("__MAN_HELM__", man("helm")); +let body = tpl.slice(0, i) + tpl.slice(j + 9); + +const heroes = { + __HERO_GITOPS__: "course-VI-gitops.webp", + __HERO_SUPPLY__: "course-VII-supply-chain.webp", + __HERO_HELM__: "course-VIII-helm-press.webp", + __HERO_APPENDIX__: "appendix-dependency-ledger.webp", +}; +for (const [ph, file] of Object.entries(heroes)) body = body.replace(ph, "/assets/" + file); +if (body.includes("__HERO_") || app.includes("__MAN_")) throw new Error("unfilled placeholder"); + +const favicon = "data:image/svg+xml," + encodeURIComponent( + ``); + +const html = ` + + + + +The Exploded Cluster + + + + + +${body} + + + + + +`; + +mkdirSync(join(DIST, "assets"), { recursive: true }); +writeFileSync(join(DIST, "index.html"), html); +writeFileSync(join(DIST, "arc.js"), app); +copyFileSync(join(ROOT, "gsap.min.js"), join(DIST, "gsap.min.js")); +copyFileSync(join(ROOT, "st.min.js"), join(DIST, "st.min.js")); +for (const f of Object.values(heroes)) copyFileSync(join(ROOT, "assets/dist", f), join(DIST, "assets", f)); + +// CSP gate: no inline