// Deploy embed shells to the public embed-check Pages project on a fresh // random URL, print the links to paste into Discord/Slack/etc., then delete // the deployment after 5 minutes. The embed-purge Worker (workers/embed-purge) // enforces the same 5-minute lifetime server-side even if this script dies. // // Why a fresh URL each run: unfurlers cache per-URL. A new branch alias per // check means Discord always fetches fresh - no stale-cache fights while // iterating on OG tags. See docs/STAGE-TEST-RELEASE.md ยง4. // // Usage: // npm run build:static # once per code change // node scripts/embed-check.mjs [--pages /en,/10years] [--keep] // // --pages Only shell + publish these clean URL paths. // --keep Skip the polite 5-minute delete (the purge Worker still fires). // // Env (all optional): // EMBED_PROJECT Pages project name (default bigscreen-embed-check) // CLOUDFLARE_API_TOKEN enables the polite delete + exact-URL lookup // CLOUDFLARE_ACCOUNT_ID (same) import { execSync } from "node:child_process"; import crypto from "node:crypto"; const PROJECT = process.env.EMBED_PROJECT || "bigscreen-embed-check"; const TTL_MS = 5 * 60 * 1000; const args = process.argv.slice(2); const keep = args.includes("--keep"); const pagesIdx = args.indexOf("--pages"); const pages = pagesIdx >= 0 ? args[pagesIdx + 1] : null; const slug = "check-" + crypto.randomBytes(3).toString("hex"); const aliasUrl = `https://${slug}.${PROJECT}.pages.dev`; // 1. build shells with asset URLs baked for the alias we're about to create execSync( `node scripts/build-embed-shells.mjs --host ${aliasUrl}` + (pages ? ` --pages ${pages}` : ""), { stdio: "inherit" }, ); // 2. deploy to a random branch -> unique, predictable alias URL execSync( `npx --yes wrangler pages deploy embed-shells --project-name=${PROJECT} ` + `--branch=${slug} --commit-dirty=true`, { stdio: "inherit" }, ); // 3. print the URLs to paste const fs = await import("node:fs"); const shelled = []; (function walk(dir, url) { for (const e of fs.readdirSync(dir, { withFileTypes: true })) { if (e.isDirectory()) walk(`${dir}/${e.name}`, `${url}/${e.name}`); else if (e.name === "index.html") shelled.push(url || "/"); } })("embed-shells", ""); console.log(`\n== paste into Discord/Slack to test unfurls (live ~5 min) ==`); for (const u of shelled) console.log(` ${aliasUrl}${u === "/" ? "" : u}`); // 4. polite teardown after 5 minutes (the purge Worker is the backstop) const token = process.env.CLOUDFLARE_API_TOKEN; const account = process.env.CLOUDFLARE_ACCOUNT_ID; if (keep) { console.log(`\n--keep: leaving it up; the embed-purge Worker removes it in <=5 min.`); process.exit(0); } if (!token || !account) { console.log( `\nNo CLOUDFLARE_API_TOKEN/ACCOUNT_ID in env - skipping the polite delete.` + `\nThe embed-purge Worker removes the deployment within 5 minutes anyway.`, ); process.exit(0); } console.log(`\nDeleting in 5 minutes... (Ctrl-C is safe: the purge Worker cleans up)`); await new Promise((r) => setTimeout(r, TTL_MS)); const api = `https://api.cloudflare.com/client/v4/accounts/${account}/pages/projects/${PROJECT}/deployments`; const headers = { Authorization: `Bearer ${token}` }; const list = await fetch(`${api}?per_page=25`, { headers }).then((r) => r.json()); const mine = (list.result || []).filter( (d) => d.deployment_trigger?.metadata?.branch === slug, ); for (const d of mine) { const res = await fetch(`${api}/${d.id}?force=true`, { method: "DELETE", headers, }).then((r) => r.json()); console.log(`deleted ${d.id}: ${res.success}`); } if (!mine.length) console.log("deployment already purged by the Worker - done.");