111 lines
3.6 KiB
JavaScript
111 lines
3.6 KiB
JavaScript
// 한남자리·철판클럽 전용 캡처.
|
||
//
|
||
// 이 스크립트가 덮어쓰는 파일은 아래 여섯 개뿐이다.
|
||
// 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장 캡처 완료");
|