diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index 6371a2a..646a7e4 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -66,3 +66,7 @@ jobs: BUILD_SHA=${{ github.sha }} BUILD_DATE=${{ github.run_started_at }} BUILD_REF=${{ github.server_url }}/${{ github.repository }} + PUBLIC_SITE_URL=${{ secrets.PUBLIC_SITE_URL }} + PUBLIC_UMAMI_SCRIPT_URL=${{ secrets.PUBLIC_UMAMI_SCRIPT_URL }} + PUBLIC_UMAMI_WEBSITE_ID=${{ secrets.PUBLIC_UMAMI_WEBSITE_ID }} + PUBLIC_ENABLE_SW=${{ secrets.PUBLIC_ENABLE_SW }} diff --git a/Dockerfile b/Dockerfile index 9587fea..bf278e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,14 @@ WORKDIR /app/site ARG PUBLIC_ENABLE_SW=true ENV PUBLIC_ENABLE_SW=$PUBLIC_ENABLE_SW +# Public, build-time config (baked into static HTML via Astro/Vite). +ARG PUBLIC_SITE_URL +ARG PUBLIC_UMAMI_SCRIPT_URL +ARG PUBLIC_UMAMI_WEBSITE_ID +ENV PUBLIC_SITE_URL=$PUBLIC_SITE_URL +ENV PUBLIC_UMAMI_SCRIPT_URL=$PUBLIC_UMAMI_SCRIPT_URL +ENV PUBLIC_UMAMI_WEBSITE_ID=$PUBLIC_UMAMI_WEBSITE_ID + COPY site/package.json site/package-lock.json ./ RUN npm ci --no-audit --no-fund diff --git a/docker-compose.yml b/docker-compose.yml index 352aafd..27d3800 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,11 @@ services: dockerfile: Dockerfile args: # Build-time toggle for service worker registration in the generated static HTML. - PUBLIC_ENABLE_SW: "true" + PUBLIC_ENABLE_SW: ${PUBLIC_ENABLE_SW:-true} + # Public, build-time config baked into the HTML. + PUBLIC_SITE_URL: ${PUBLIC_SITE_URL:-} + PUBLIC_UMAMI_SCRIPT_URL: ${PUBLIC_UMAMI_SCRIPT_URL:-} + PUBLIC_UMAMI_WEBSITE_ID: ${PUBLIC_UMAMI_WEBSITE_ID:-} ports: - "8080:80" networks: @@ -28,4 +32,4 @@ services: networks: fast-website-network: - driver: bridge \ No newline at end of file + driver: bridge diff --git a/scripts/refresh.sh b/scripts/refresh.sh index 989c3a9..2c083dd 100644 --- a/scripts/refresh.sh +++ b/scripts/refresh.sh @@ -2,6 +2,11 @@ ## Fetch content, build the image, clear the cache and restart the container docker run --rm -it --network container:$(docker compose ps -q redis) -v "${PWD}:/usr/src/app" -w /usr/src/app/site -e CACHE_REDIS_URL="redis://127.0.0.1:6379/0" node:24-alpine npm run fetch-content -docker build --build-arg PUBLIC_ENABLE_SW=true -t fast-website:local . +docker build \ + --build-arg PUBLIC_ENABLE_SW=$(grep PUBLIC_ENABLE_SW ./site/.env | cut -d '=' -f2) \ + --build-arg PUBLIC_SITE_URL=$(grep PUBLIC_SITE_URL ./site/.env | cut -d '=' -f2) \ + --build-arg PUBLIC_UMAMI_SCRIPT_URL=$(grep PUBLIC_UMAMI_SCRIPT_URL ./site/.env | cut -d '=' -f2) \ + --build-arg PUBLIC_UMAMI_WEBSITE_ID=$(grep PUBLIC_UMAMI_WEBSITE_ID ./site/.env | cut -d '=' -f2) \ + -t fast-website:local . docker run --rm -it --network container:$(docker compose ps -q redis) -v "${PWD}:/usr/src/app" -w /usr/src/app/site -e CACHE_REDIS_URL="redis://127.0.0.1:6379/0" node:24-alpine npm run cache:clear docker compose up -d \ No newline at end of file diff --git a/site/package.json b/site/package.json index 42fe645..5b99fab 100644 --- a/site/package.json +++ b/site/package.json @@ -9,6 +9,7 @@ "fetch-content": "tsx scripts/fetch-content.ts", "cache:clear": "tsx scripts/cache-clear.ts", "verify:blog": "npm run build && tsx scripts/verify-blog-build.ts", + "verify:umami": "npm run build && tsx scripts/verify-umami-in-dist.ts", "typecheck": "astro check", "format": "prettier -w .", "format:check": "prettier -c .", diff --git a/site/scripts/verify-umami-in-dist.ts b/site/scripts/verify-umami-in-dist.ts new file mode 100644 index 0000000..2dea2d1 --- /dev/null +++ b/site/scripts/verify-umami-in-dist.ts @@ -0,0 +1,39 @@ +import "dotenv/config"; + +import { readFile } from "node:fs/promises"; + +function fail(msg: string): never { + // eslint-disable-next-line no-console + console.error(`[verify:umami] ${msg}`); + process.exit(1); +} + +function info(msg: string) { + // eslint-disable-next-line no-console + console.log(`[verify:umami] ${msg}`); +} + +async function main() { + const html = await readFile("dist/index.html", "utf8"); + + const scriptUrl = process.env.PUBLIC_UMAMI_SCRIPT_URL || ""; + const websiteId = process.env.PUBLIC_UMAMI_WEBSITE_ID || ""; + + const expectsEnabled = Boolean(scriptUrl && websiteId); + const hasWebsiteId = html.includes('data-website-id="'); + const hasScriptUrl = scriptUrl ? html.includes(`src="${scriptUrl}"`) : false; + + if (expectsEnabled) { + if (!hasWebsiteId) fail(`expected Umami enabled, but data-website-id not found in dist/index.html`); + if (!hasScriptUrl) + fail(`expected Umami enabled, but src="${scriptUrl}" not found in dist/index.html`); + info("ok (umami script present)"); + return; + } + + // When not configured, the site should not render a script tag. + if (hasWebsiteId) fail("expected Umami disabled, but data-website-id was found in dist/index.html"); + info("ok (umami disabled, no script rendered)"); +} + +main().catch((e) => fail(String(e)));