96 lines
3.3 KiB
JavaScript
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;
|
|
}
|