diff --git a/.github/workflows/browserstack.yml b/.github/workflows/browserstack.yml index 5b0047c..91651ce 100644 --- a/.github/workflows/browserstack.yml +++ b/.github/workflows/browserstack.yml @@ -55,7 +55,7 @@ jobs: run: pnpm run test:matrix - name: Run unit tests - run: pnpm run build && pnpm run test:unit + run: pnpm run build && pnpm run test:unit && pnpm run test:deploy - name: Prettier check (report only, no writes or commits) if: always() diff --git a/deploy.mts b/deploy.mts index b595327..956fb90 100644 --- a/deploy.mts +++ b/deploy.mts @@ -71,8 +71,8 @@ const CDN_PUBLIC_ROOT = "https://scripts.simpleanalyticscdn.com"; const CDN_STORAGE_ROOT = "https://storage.bunnycdn.com/sa-cdn"; const CDN_PURGE_URL = "https://api.bunny.net/purge?url=https://simpleanalyticscdn.b-cdn.net/*"; -const HEADER_PATTERN = - /^\/\* Simple Analytics - Privacy friendly analytics \(docs\.simpleanalytics\.com\/[^;]+; \d{4}-\d{2}-\d{2}; [0-9a-f]{4}(?:; SRI-version)?(?:; v\d+)?\) \*\/$/; +export const HEADER_PATTERN = + /^\/\* Simple Analytics - Privacy(?: friendly|-first) analytics \(docs\.simpleanalytics\.com\/[^;]+; \d{4}-\d{2}-\d{2}; [0-9a-f]{4}(?:; SRI-version)?(?:; v\d+)?\) \*\/$/; const ansi = { bold: (value: string) => color("1", value), diff --git a/test/deploy.test.mts b/test/deploy.test.mts index 3ae7408..5bf7727 100644 --- a/test/deploy.test.mts +++ b/test/deploy.test.mts @@ -1,10 +1,12 @@ import assert from "node:assert/strict"; +import { readFile, readdir } from "node:fs/promises"; import { describe, it } from "node:test"; import { classifyContent, createManifest, firstLine, + HEADER_PATTERN, latestVersionFromNames, repositoryPolicy, summarize, @@ -13,6 +15,45 @@ import { type PreviewFile, } from "../deploy.mts"; +describe("deployment headers", () => { + it("accepts every generated JavaScript header in the current manifest", async () => { + const version = latestVersionFromNames( + await readdir(new URL("../dist", import.meta.url)) + ); + assert.ok(version !== undefined, "dist has no versioned SRI directory"); + const files = createManifest( + { + destinations: ["cdn", "custom"], + scripts: ["default", "auto-events"], + variants: ["latest", "sri", "light", "proxy"], + }, + version + ); + for (const file of files.filter((file) => file.kind === "javascript")) { + const content = await readFile( + new URL("../" + file.localPath, import.meta.url) + ); + assert.match(firstLine(content), HEADER_PATTERN, file.localPath); + } + }); + + it("keeps accepting legacy headers", () => { + assert.match( + "/* Simple Analytics - Privacy friendly analytics (docs.simpleanalytics.com/script; 2024-01-01; abcd; SRI-version; v11) */", + HEADER_PATTERN + ); + }); + + it("rejects malformed or unrelated headers", () => { + for (const header of [ + "/* unrelated script */", + "/* Simple Analytics - Privacy-first analytics (docs.simpleanalytics.com/script; 2026-10-15; not-a-hash; v12) */", + ]) { + assert.doesNotMatch(header, HEADER_PATTERN); + } + }); +}); + describe("deployment manifest", () => { it("maps latest and its legacy companions to both destinations", () => { const files = createManifest({