// 발행 게이트 러너. 빌드 결과(dist)와 소스(글 frontmatter·데이터)를 검사해 error가 하나라도 있으면 배포를 막는다. // 규칙 본체는 scripts/gate/rules.mjs. 근거는 docs/NEXT_SESSION_SUPPORTERS_AUTOBUILD_v2.md §3. // // node scripts/check.mjs 검사 // node scripts/check.mjs --update-layout 구조 기준선(§3-2) 갱신. 룩앤필 변경을 haewon이 승인한 뒤에만 // node scripts/check.mjs --today=YYYY-MM-DD // node scripts/check.mjs --home-text=../evidence//home_text.txt 40자 중복 검사 기준을 수집기 산출물로 import { readdirSync, readFileSync, statSync, existsSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; import * as R from './gate/rules.mjs'; const here = (p) => fileURLToPath(new URL(p, import.meta.url)); const DIST = here('../dist/'); const POSTS = here('../src/content/posts/'); const LAYOUT_BASELINE = here('./gate/layout-baseline.json'); const args = process.argv.slice(2); const updateLayout = args.includes('--update-layout'); const today = args.find((a) => a.startsWith('--today='))?.slice(8) ?? new Date().toISOString().slice(0, 10); const homeTextPath = args.find((a) => a.startsWith('--home-text='))?.slice(12) ?? here('./home_text.txt'); const HOME_TEXT = existsSync(homeTextPath) ? readFileSync(homeTextPath, 'utf8') : ''; const authors = JSON.parse(readFileSync(here('../src/data/authors.json'), 'utf8')); const fact = JSON.parse(readFileSync(here('../src/data/factSheet.json'), 'utf8')); const videos = JSON.parse(readFileSync(here('../src/data/videos.json'), 'utf8')); const site = JSON.parse(readFileSync(here('../src/data/site.json'), 'utf8')); const INDEXABLE = process.env.PUBLIC_INDEXABLE === 'true'; // 구조 기준선을 보는 페이지. 글 목록은 글 수에 따라 카드가 늘므로 집합 비교, 나머지는 순서 비교. // 글 상세는 사전순 첫 글 하나를 대표로 본다(템플릿·다른 병원에서도 같은 규칙). const LAYOUT_PAGES = { '/index.html': 'sequence', '/posts.html': 'set' }; function walk(dir, out = []) { for (const f of readdirSync(dir)) { const p = join(dir, f); statSync(p).isDirectory() ? walk(p, out) : f.endsWith('.html') && out.push(p); } return out; } const findings = []; // { where, level, code, msg } const add = (where, list) => list.forEach((x) => findings.push({ where, ...x })); // ---------- 1. dist 검사 ---------- if (!existsSync(DIST)) { console.error('dist/ 없음. astro build 먼저'); process.exit(1); } const files = walk(DIST); const firstPost = files.map((f) => f.replace(DIST, '/')).filter((r) => r.startsWith('/posts/')).sort()[0]; if (firstPost) LAYOUT_PAGES[firstPost] = 'sequence'; const layoutNow = {}; for (const f of files) { const html = readFileSync(f, 'utf8'); const rel = f.replace(DIST, '/'); const text = R.stripHtml(html); // JSON-LD const blocks = [...html.matchAll(/