/** * 이전 Pages 배포 세대의 자산 보존 — immutable preview URL(들)의 HTML/JS/CSS 의존 * 그래프를 따라 내려받아, 새 빌드 dist 에 없는 파일만 추가한다(기존 파일은 덮어쓰지 않는다). * 사용: node preserve-assets.mjs */ import { mkdir, writeFile, readFile } from "node:fs/promises"; import { createHash } from "node:crypto"; import path from "node:path"; const ORIGINS = [ "https://vignette.chanpaca.net", "https://5a525329.vignette-b1q.pages.dev", "https://e1c73735.vignette-b1q.pages.dev", "https://bbebb2d3.vignette-b1q.pages.dev", "https://44edecc8.vignette-b1q.pages.dev", "https://0ffdc694.vignette-b1q.pages.dev", "https://7bd59055.vignette-b1q.pages.dev", ]; const DIST = "dist"; async function exists(p) { try { await readFile(p); return true; } catch { return false; } } async function collect(origin) { const seen = new Map(); // path -> sha256 hex const html = await (await fetch(origin + "/")).text(); // index.html 이 참조하는 엔트리 자산 const refs = new Set(); for (const m of html.matchAll(/(?:src|href)="(\/[^"]+\.(?:js|css))"/g)) refs.add(m[1]); for (const m of html.matchAll(/(?:src|href)="(\/[^"]+\.(?:png|svg|ico|webmanifest|json|woff2?))"/g)) refs.add(m[1]); // JS 청크 의존 그래프 폐쇄 const queue = [...refs]; while (queue.length) { const ref = queue.pop(); if (seen.has(ref)) continue; const res = await fetch(origin + ref); if (!res.ok) { seen.set(ref, "FETCH_FAILED_" + res.status); continue; } const buf = Buffer.from(await res.arrayBuffer()); seen.set(ref, createHash("sha256").update(buf).digest("hex")); if (ref.endsWith(".js")) { const text = buf.toString("utf8"); for (const m of text.matchAll(/["'`](\.\/[A-Za-z0-9_.-]+\.(?:js|css))["'`]/g)) { const p = path.posix.normalize(path.posix.join(path.posix.dirname(ref), m[1])); if (!seen.has(p)) queue.push(p); } } } return seen; } const perOrigin = []; let added = 0; const addedList = []; for (const origin of ORIGINS) { const map = await collect(origin); let ok = 0; for (const [p, sha] of map) { if (sha.startsWith("FETCH_FAILED")) continue; const rel = p.replace(/^\//, ""); const target = path.join(DIST, rel); if (await exists(target)) { ok += 1; continue; } await mkdir(path.dirname(target), { recursive: true }); const res = await fetch(origin + p); await writeFile(target, Buffer.from(await res.arrayBuffer())); added += 1; addedList.push(rel); } perOrigin.push({ origin, assets: map.size, reused_in_new_build: ok }); console.log(`origin ${origin}: ${map.size} assets, ${ok} already in new build`); } console.log(`assets_added: ${added}`); await writeFile( "preserve-report.json", JSON.stringify({ origins: perOrigin, assets_added: added, added: addedList }, null, 1), "utf8", );