#!/usr/bin/env node /** * Case 본문이 Studio 파서를 통과하는지 게시 전에 확인한다. * * Studio 에 붙여넣고 저장한 뒤에야 거절을 알게 되면, 어느 줄이 문제인지 찾느라 화면을 오가게 * 된다. 같은 파서를 그대로 부르므로 여기서 통과하면 저장도 통과한다. * * node check_body.mjs <파일> [--frontend <경로>] * * `--frontend` 는 tech-log-frontend 체크아웃 경로다. 생략하면 TECH_LOG_FRONTEND 환경변수를 * 쓰고, 그것도 없으면 기본 경로를 쓴다. */ import { readFile } from "node:fs/promises"; import path from "node:path"; import process from "node:process"; import { pathToFileURL } from "node:url"; const DEFAULT_FRONTEND = "/home/donghyeon/workspace/desktop-server-git/tech-log-frontend"; function optionValue(name) { const index = process.argv.indexOf(name); return index >= 0 ? process.argv[index + 1] : undefined; } const target = process.argv[2]; if (!target || target.startsWith("--")) { console.error("usage: node check_body.mjs <파일> [--frontend <경로>]"); process.exit(2); } const frontend = path.resolve( optionValue("--frontend") ?? process.env.TECH_LOG_FRONTEND ?? DEFAULT_FRONTEND, ); const parserPath = path.join( frontend, "src/features/tech-log/domain/content-format/parse-case-content.ts", ); let parseCaseContent; let ContentFormatError; try { ({ parseCaseContent, ContentFormatError } = await import( pathToFileURL(parserPath).href )); } catch (error) { console.error(`파서를 불러오지 못했습니다: ${parserPath}`); console.error( "tech-log-frontend 경로를 --frontend 또는 TECH_LOG_FRONTEND 로 알려 주세요.", ); console.error( "TypeScript 를 그대로 읽으므로 node 는 --experimental-transform-types 가 필요합니다.", ); console.error(String(error instanceof Error ? error.message : error)); process.exit(2); } const raw = await readFile(target, "utf8"); // 기록 파일을 통째로 넣으면 칸의
과 주석까지 파서에 걸린다. Studio 가 받는 것은 // body:start ~ body:end 사이뿐이므로 그 구간만 잘라 검사한다. 마커가 없으면 파일 전체를 // 본문으로 본다 — 본문만 담은 초안을 그대로 넣는 경우다. const BODY_START = ""; const BODY_END = ""; let source = raw; let offset = 0; const startIndex = raw.indexOf(BODY_START); const endIndex = raw.indexOf(BODY_END); if (startIndex !== -1 && endIndex > startIndex) { const bodyStart = startIndex + BODY_START.length; source = raw.slice(bodyStart, endIndex); offset = raw.slice(0, bodyStart).split("\n").length - 1; console.log(`본문 구간만 검사합니다 — ${BODY_START} ~ ${BODY_END}`); } try { const blocks = parseCaseContent(source); const counts = new Map(); for (const block of blocks) { counts.set(block.type, (counts.get(block.type) ?? 0) + 1); } const summary = [...counts] .sort(([a], [b]) => a.localeCompare(b)) .map(([type, count]) => `${type} ${count}`) .join(" · "); console.log(`PASS ${blocks.length}개 블록 — ${summary}`); } catch (error) { if (error instanceof ContentFormatError) { for (const issue of error.issues) { console.error(`FAIL ${target}:${issue.line + offset}:${issue.column} ${issue.detail}`); } process.exit(1); } throw error; }