Files
document-haness/.agents/skills/writing-tech-log-records/scripts/check_body.mjs
T

96 lines
3.3 KiB
JavaScript

#!/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");
// 기록 파일을 통째로 넣으면 칸의 <br> 과 주석까지 파서에 걸린다. Studio 가 받는 것은
// body:start ~ body:end 사이뿐이므로 그 구간만 잘라 검사한다. 마커가 없으면 파일 전체를
// 본문으로 본다 — 본문만 담은 초안을 그대로 넣는 경우다.
const BODY_START = "<!-- body:start -->";
const BODY_END = "<!-- 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;
}