designpaca/apps/site/tools/capture-showcase-duo.mjs
Yun Chan e09d4efb1c
Some checks failed
ci / build (push) Has been cancelled
feat: strengthen design skill and refresh all showcases
2026-09-12 18:16:33 +09:00

111 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 한남자리·철판클럽 전용 캡처.
//
// 이 스크립트가 덮어쓰는 파일은 아래 여섯 개뿐이다.
// src/assets/shots/work-hannam-jari.png
// src/assets/shots/work-hannam-jari-2.png
// src/assets/shots/work-griddle-club.png
// src/assets/shots/work-griddle-club-2.png
// public/work/hannam-jari/og.png
// public/work/griddle-club/og.png
//
// 공개 자산과 같은 HTTP 경로로 상대 링크를 검증하고, 폰트·이미지가 준비되지 않으면
// 불완전한 화면을 저장하지 않고 실패한다.
import fs from "node:fs";
import path from "node:path";
import url from "node:url";
import puppeteer from "puppeteer-core";
import {
collectPageFailures,
prepareStillPage,
resolveChrome,
startPublicServer,
waitForStableVisual,
} from "./capture-stable.mjs";
const here = path.dirname(url.fileURLToPath(import.meta.url));
const root = path.resolve(here, "..");
const publicRoot = path.join(root, "public");
const shotsRoot = path.join(root, "src", "assets", "shots");
const jobs = [
{
label: "한남자리 공개",
route: "/work/hannam-jari/",
output: path.join(shotsRoot, "work-hannam-jari.png"),
},
{
label: "한남자리 관리자",
route: "/work/hannam-jari/admin/",
output: path.join(shotsRoot, "work-hannam-jari-2.png"),
},
{
label: "철판클럽 공개",
route: "/work/griddle-club/",
output: path.join(shotsRoot, "work-griddle-club.png"),
},
{
label: "철판클럽 가맹",
route: "/work/griddle-club/franchise/",
output: path.join(shotsRoot, "work-griddle-club-2.png"),
},
];
const ogJobs = [
{
label: "한남자리 OG",
route: "/work/hannam-jari/",
output: path.join(publicRoot, "work", "hannam-jari", "og.png"),
},
{
label: "철판클럽 OG",
route: "/work/griddle-club/",
output: path.join(publicRoot, "work", "griddle-club", "og.png"),
},
];
await fs.promises.mkdir(shotsRoot, { recursive: true });
const server = await startPublicServer(publicRoot);
let browser;
async function capture(job, viewport) {
const page = await browser.newPage();
const failures = collectPageFailures(page, server.origin);
try {
await prepareStillPage(page);
await page.setViewport(viewport);
const response = await page.goto(server.origin + job.route, { waitUntil: "domcontentloaded", timeout: 30000 });
if (!response?.ok()) throw new Error(`${job.route} returned ${response?.status() ?? "no response"}`);
await waitForStableVisual(page);
if (failures.length) throw new Error(failures.join(" · "));
await fs.promises.mkdir(path.dirname(job.output), { recursive: true });
await page.screenshot({ path: job.output, type: "png", fullPage: false });
const size = Math.round((await fs.promises.stat(job.output)).size / 1024);
console.log(`CAPTURE ${job.label} → ${path.relative(root, job.output)} (${viewport.width}×${viewport.height}, ${size}KB)`);
} finally {
await page.close();
}
}
try {
console.log("OVERWRITE SCOPE");
for (const job of [...jobs, ...ogJobs]) console.log(`- ${path.relative(root, job.output)}`);
browser = await puppeteer.launch({
executablePath: resolveChrome(),
headless: "new",
args: [
"--no-sandbox",
"--use-angle=swiftshader",
"--enable-unsafe-swiftshader",
"--force-device-scale-factor=1",
],
});
for (const job of jobs) await capture(job, { width: 1440, height: 900, deviceScaleFactor: 1 });
for (const job of ogJobs) await capture(job, { width: 1200, height: 630, deviceScaleFactor: 1 });
} finally {
if (browser) await browser.close();
await server.close();
}
console.log("쇼케이스 듀오 카드 4장과 OG 2장 캡처 완료");