@byh3071/vhk 1.7.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +170 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2661,7 +2661,9 @@ function generateGateScript(id) {
|
|
|
2661
2661
|
" process.exit(1)",
|
|
2662
2662
|
"}",
|
|
2663
2663
|
"",
|
|
2664
|
-
"
|
|
2664
|
+
"// BOM-safe \uC77D\uAE30: PowerShell Set-Content -Encoding utf8 \uC758 UTF-8 BOM \uC81C\uAC70(\uC5C6\uC73C\uBA74 throw).",
|
|
2665
|
+
"const readJson = (p) => { const t = readFileSync(p, 'utf-8'); return JSON.parse(t.charCodeAt(0) === 0xfeff ? t.slice(1) : t) }",
|
|
2666
|
+
"const pkg = existsSync('package.json') ? readJson('package.json') : {}",
|
|
2665
2667
|
"const scripts = pkg.scripts ?? {}",
|
|
2666
2668
|
"const pm = existsSync('pnpm-lock.yaml') ? 'pnpm' : existsSync('yarn.lock') ? 'yarn' : 'npm'",
|
|
2667
2669
|
"const skipDeep = process.env.VHK_GATES_SKIP_DEEP === '1'",
|
|
@@ -5030,7 +5032,7 @@ function readCloudConfig(rootDir) {
|
|
|
5030
5032
|
const p = path12.join(rootDir, VHK_DIR2, CLOUD_CONFIG_FILE);
|
|
5031
5033
|
if (!fs11.existsSync(p)) return null;
|
|
5032
5034
|
try {
|
|
5033
|
-
const parsed =
|
|
5035
|
+
const parsed = readJsonFile(p);
|
|
5034
5036
|
if (parsed && typeof parsed.gistId === "string" && parsed.gistId) {
|
|
5035
5037
|
return { gistId: parsed.gistId };
|
|
5036
5038
|
}
|
|
@@ -5364,9 +5366,116 @@ import { execFileSync as execFileSync4 } from "child_process";
|
|
|
5364
5366
|
import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
5365
5367
|
import { join as join10 } from "path";
|
|
5366
5368
|
import chalk30 from "chalk";
|
|
5369
|
+
|
|
5370
|
+
// src/commands/verify-report.ts
|
|
5371
|
+
function escapeHtml(text) {
|
|
5372
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
5373
|
+
}
|
|
5374
|
+
var STATUS_COLOR = {
|
|
5375
|
+
PASS: { bg: "#16a34a", fg: "#ffffff", label: "PASS" },
|
|
5376
|
+
WARN: { bg: "#d97706", fg: "#ffffff", label: "WARN" },
|
|
5377
|
+
FAIL: { bg: "#dc2626", fg: "#ffffff", label: "FAIL" }
|
|
5378
|
+
};
|
|
5379
|
+
var GATE_STATUS = {
|
|
5380
|
+
pass: { color: "#16a34a", label: "\uD1B5\uACFC" },
|
|
5381
|
+
fail: { color: "#dc2626", label: "\uC2E4\uD328" },
|
|
5382
|
+
skip: { color: "#d97706", label: "\uAC74\uB108\uB700" }
|
|
5383
|
+
};
|
|
5384
|
+
function renderGateRow(g) {
|
|
5385
|
+
const s = GATE_STATUS[g.status];
|
|
5386
|
+
const exit = g.exitCode === null ? "\u2014" : String(g.exitCode);
|
|
5387
|
+
const detail = g.detail ? escapeHtml(g.detail) : "";
|
|
5388
|
+
return ` <tr>
|
|
5389
|
+
<td class="gate-label">${escapeHtml(g.label)}</td>
|
|
5390
|
+
<td><span class="gate-status" style="color:${s.color}">${s.label}</span></td>
|
|
5391
|
+
<td class="gate-exit">${escapeHtml(exit)}</td>
|
|
5392
|
+
<td class="gate-detail">${detail}</td>
|
|
5393
|
+
</tr>`;
|
|
5394
|
+
}
|
|
5395
|
+
function renderReportHtml(report) {
|
|
5396
|
+
const c = STATUS_COLOR[report.status];
|
|
5397
|
+
const s = report.summary;
|
|
5398
|
+
const rows = report.gates.map(renderGateRow).join("\n");
|
|
5399
|
+
const actions = report.nextActions.map((a) => ` <li>${escapeHtml(a)}</li>`).join("\n");
|
|
5400
|
+
return `<!DOCTYPE html>
|
|
5401
|
+
<html lang="ko">
|
|
5402
|
+
<head>
|
|
5403
|
+
<meta charset="utf-8">
|
|
5404
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
5405
|
+
<title>vhk verify \uB9AC\uD3EC\uD2B8 \u2014 ${c.label}</title>
|
|
5406
|
+
<style>
|
|
5407
|
+
:root { color-scheme: light dark; }
|
|
5408
|
+
* { box-sizing: border-box; }
|
|
5409
|
+
body {
|
|
5410
|
+
margin: 0; padding: 2rem 1rem;
|
|
5411
|
+
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans KR", sans-serif;
|
|
5412
|
+
background: #f8fafc; color: #0f172a; line-height: 1.55;
|
|
5413
|
+
}
|
|
5414
|
+
.wrap { max-width: 760px; margin: 0 auto; }
|
|
5415
|
+
header { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.5rem; }
|
|
5416
|
+
h1 { font-size: 1.25rem; margin: 0; font-weight: 700; }
|
|
5417
|
+
.badge {
|
|
5418
|
+
display: inline-block; padding: 0.35rem 1rem; border-radius: 9999px;
|
|
5419
|
+
font-weight: 800; font-size: 1rem; letter-spacing: 0.05em;
|
|
5420
|
+
background: ${c.bg}; color: ${c.fg};
|
|
5421
|
+
}
|
|
5422
|
+
.summary { color: #475569; font-size: 0.95rem; margin: 0.25rem 0 1.5rem; }
|
|
5423
|
+
.summary strong { color: #0f172a; }
|
|
5424
|
+
table { width: 100%; border-collapse: collapse; margin-bottom: 1.75rem; font-size: 0.95rem; }
|
|
5425
|
+
th, td { text-align: left; padding: 0.6rem 0.75rem; border-bottom: 1px solid #e2e8f0; }
|
|
5426
|
+
th { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; color: #64748b; }
|
|
5427
|
+
.gate-label { font-weight: 600; }
|
|
5428
|
+
.gate-status { font-weight: 700; }
|
|
5429
|
+
.gate-exit { font-variant-numeric: tabular-nums; color: #475569; }
|
|
5430
|
+
.gate-detail { color: #64748b; font-size: 0.88rem; }
|
|
5431
|
+
h2 { font-size: 1rem; margin: 0 0 0.5rem; }
|
|
5432
|
+
ul { margin: 0 0 1.75rem; padding-left: 1.25rem; }
|
|
5433
|
+
li { margin: 0.2rem 0; }
|
|
5434
|
+
footer { color: #94a3b8; font-size: 0.8rem; border-top: 1px solid #e2e8f0; padding-top: 0.75rem; }
|
|
5435
|
+
@media (prefers-color-scheme: dark) {
|
|
5436
|
+
body { background: #0f172a; color: #e2e8f0; }
|
|
5437
|
+
.summary { color: #94a3b8; } .summary strong { color: #e2e8f0; }
|
|
5438
|
+
th { color: #94a3b8; } th, td { border-color: #1e293b; }
|
|
5439
|
+
.gate-exit { color: #94a3b8; } .gate-detail { color: #94a3b8; }
|
|
5440
|
+
footer { color: #64748b; border-color: #1e293b; }
|
|
5441
|
+
}
|
|
5442
|
+
</style>
|
|
5443
|
+
</head>
|
|
5444
|
+
<body>
|
|
5445
|
+
<div class="wrap">
|
|
5446
|
+
<header>
|
|
5447
|
+
<h1>\u{1F50E} vhk verify \uB9AC\uD3EC\uD2B8</h1>
|
|
5448
|
+
<span class="badge">${c.label}</span>
|
|
5449
|
+
</header>
|
|
5450
|
+
<p class="summary">
|
|
5451
|
+
\uAC8C\uC774\uD2B8 <strong>${s.total}</strong>\uAC1C \u2014 \uD1B5\uACFC ${s.pass} / \uC2E4\uD328 ${s.fail} / \uAC74\uB108\uB700 ${s.skip}
|
|
5452
|
+
</p>
|
|
5453
|
+
<table>
|
|
5454
|
+
<thead>
|
|
5455
|
+
<tr><th>\uAC8C\uC774\uD2B8</th><th>\uC0C1\uD0DC</th><th>\uC885\uB8CC\uCF54\uB4DC</th><th>\uBE44\uACE0</th></tr>
|
|
5456
|
+
</thead>
|
|
5457
|
+
<tbody>
|
|
5458
|
+
${rows}
|
|
5459
|
+
</tbody>
|
|
5460
|
+
</table>
|
|
5461
|
+
<h2>\uB2E4\uC74C \uD589\uB3D9</h2>
|
|
5462
|
+
<ul>
|
|
5463
|
+
${actions}
|
|
5464
|
+
</ul>
|
|
5465
|
+
<footer>
|
|
5466
|
+
\uC0DD\uC131: ${escapeHtml(report.generatedAt)} \xB7 \uB0A0\uC9DC: ${escapeHtml(report.date)} \xB7 schema v${report.schemaVersion}
|
|
5467
|
+
</footer>
|
|
5468
|
+
</div>
|
|
5469
|
+
</body>
|
|
5470
|
+
</html>
|
|
5471
|
+
`;
|
|
5472
|
+
}
|
|
5473
|
+
|
|
5474
|
+
// src/commands/verify.ts
|
|
5367
5475
|
var REPORT_SCHEMA_VERSION = 1;
|
|
5368
5476
|
var REPORT_DIR_REL = join10(".vhk", "reports");
|
|
5369
5477
|
var REPORT_PATH_REL = join10(REPORT_DIR_REL, "latest.json");
|
|
5478
|
+
var REPORT_HTML_PATH_REL = join10(REPORT_DIR_REL, "latest.html");
|
|
5370
5479
|
var SHIM = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
|
|
5371
5480
|
function detectPm(cwd) {
|
|
5372
5481
|
if (existsSync16(join10(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
@@ -5523,9 +5632,67 @@ var STATUS_BADGE = {
|
|
|
5523
5632
|
WARN: chalk30.yellow.bold("WARN"),
|
|
5524
5633
|
FAIL: chalk30.red.bold("FAIL")
|
|
5525
5634
|
};
|
|
5635
|
+
async function renderVerifyReport(cwd, opts) {
|
|
5636
|
+
const jsonPath = join10(cwd, REPORT_PATH_REL);
|
|
5637
|
+
let report;
|
|
5638
|
+
if (existsSync16(jsonPath)) {
|
|
5639
|
+
try {
|
|
5640
|
+
report = readJsonFile(jsonPath);
|
|
5641
|
+
} catch {
|
|
5642
|
+
console.log(chalk30.yellow(" \u26A0\uFE0F \uAE30\uC874 latest.json \uC190\uC0C1 \u2014 verify \uC7AC\uC2E4\uD589\uC73C\uB85C \uC99D\uAC70\uB97C \uB2E4\uC2DC \uB9CC\uB4ED\uB2C8\uB2E4."));
|
|
5643
|
+
report = verifyEvidence(cwd).report;
|
|
5644
|
+
}
|
|
5645
|
+
} else {
|
|
5646
|
+
console.log(chalk30.dim(" latest.json \uC5C6\uC74C \u2014 verify 1\uD68C \uC120\uC2E4\uD589\uC73C\uB85C \uC99D\uAC70\uB97C \uB9CC\uB4ED\uB2C8\uB2E4."));
|
|
5647
|
+
report = verifyEvidence(cwd).report;
|
|
5648
|
+
}
|
|
5649
|
+
const html = renderReportHtml(report);
|
|
5650
|
+
const htmlPath = join10(cwd, REPORT_HTML_PATH_REL);
|
|
5651
|
+
try {
|
|
5652
|
+
mkdirSync11(join10(cwd, REPORT_DIR_REL), { recursive: true });
|
|
5653
|
+
writeFileSync11(htmlPath, html, "utf-8");
|
|
5654
|
+
} catch (e) {
|
|
5655
|
+
console.error(
|
|
5656
|
+
chalk30.red(` \u274C \uB9AC\uD3EC\uD2B8 HTML \uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (${REPORT_HTML_PATH_REL}): ${e instanceof Error ? e.message : String(e)}`)
|
|
5657
|
+
);
|
|
5658
|
+
console.error(chalk30.dim(" \uD574\uB2F9 \uACBD\uB85C\uC758 \uC4F0\uAE30 \uAD8C\uD55C\uC744 \uD655\uC778\uD558\uC138\uC694."));
|
|
5659
|
+
process.exitCode = 1;
|
|
5660
|
+
return;
|
|
5661
|
+
}
|
|
5662
|
+
console.log(chalk30.bold("\n\u{1F50E} \uAC80\uC99D \uB9AC\uD3EC\uD2B8 (verify --report)"));
|
|
5663
|
+
console.log(` \uACB0\uACFC: ${STATUS_BADGE[report.status]}`);
|
|
5664
|
+
console.log(chalk30.dim(` \u{1F4C4} HTML: ${REPORT_HTML_PATH_REL}`));
|
|
5665
|
+
process.exitCode = report.status === "FAIL" ? 1 : 0;
|
|
5666
|
+
if (opts.open) {
|
|
5667
|
+
if (isInteractive()) openReportInBrowser(htmlPath);
|
|
5668
|
+
else console.log(chalk30.dim(" (\uBE44\uB300\uD654\uD615/CI/MCP \u2014 --open \uC790\uB3D9 \uC2A4\uD0B5)"));
|
|
5669
|
+
return;
|
|
5670
|
+
}
|
|
5671
|
+
printNextStep({
|
|
5672
|
+
message: "\uB9AC\uD3EC\uD2B8 \uC0DD\uC131 \uC644\uB8CC. \uBE0C\uB77C\uC6B0\uC800\uB85C \uC5F4\uB824\uBA74:",
|
|
5673
|
+
command: "vhk verify --report --open",
|
|
5674
|
+
cursorHint: "\uB9AC\uD3EC\uD2B8 \uC5F4\uC5B4\uC918"
|
|
5675
|
+
});
|
|
5676
|
+
}
|
|
5677
|
+
function openReportInBrowser(filePath) {
|
|
5678
|
+
let result;
|
|
5679
|
+
if (process.platform === "darwin") {
|
|
5680
|
+
result = safeExecFile("open", [filePath]);
|
|
5681
|
+
} else if (process.platform === "win32") {
|
|
5682
|
+
result = safeExecFile("rundll32.exe", ["url.dll,FileProtocolHandler", filePath]);
|
|
5683
|
+
} else {
|
|
5684
|
+
result = safeExecFile("xdg-open", [filePath]);
|
|
5685
|
+
}
|
|
5686
|
+
if (result.ok) console.log(chalk30.green(" \u2705 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
5687
|
+
else console.log(chalk30.yellow(" \u26A0\uFE0F \uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC704 \uD30C\uC77C\uC744 \uC9C1\uC811 \uC5EC\uC138\uC694."));
|
|
5688
|
+
}
|
|
5526
5689
|
async function verify(opts = {}) {
|
|
5527
5690
|
if (!ensureNotHardStopped("verify")) return;
|
|
5528
5691
|
const cwd = process.cwd();
|
|
5692
|
+
if (opts.report) {
|
|
5693
|
+
await renderVerifyReport(cwd, opts);
|
|
5694
|
+
return;
|
|
5695
|
+
}
|
|
5529
5696
|
const { report, path: path14 } = verifyEvidence(cwd);
|
|
5530
5697
|
if (opts.json) {
|
|
5531
5698
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -6040,7 +6207,7 @@ program.command("context").alias("\uB9E5\uB77D").option("--compact", "\uD1A0\uD0
|
|
|
6040
6207
|
program.command("mode [target]").alias("\uBAA8\uB4DC").description("Safety Mode \uC870\uD68C/\uBCC0\uACBD (lite|standard|strict) \u2014 \uC704\uD5D8 \uC791\uC5C5 \uAC00\uB4DC \uAC15\uB3C4").action(async (target) => {
|
|
6041
6208
|
await mode(target);
|
|
6042
6209
|
});
|
|
6043
|
-
program.command("verify").alias("\uC0AC\uC804\uC810\uAC80").option("--json", "\uB9AC\uD3EC\uD2B8 JSON \uC744 stdout \uC73C\uB85C \uCD9C\uB825 (CI\uC6A9 \u2014 \uACBD\uB85C \uB300\uC2E0)").description("\uAC80\uC99D \uAC8C\uC774\uD2B8(tsc/test/build/secure) \uC2E4\uC81C \uC2E4\uD589 + \uC99D\uAC70 \uAE30\uB85D (.vhk/reports/latest.json)").action(async (opts) => {
|
|
6210
|
+
program.command("verify").alias("\uC0AC\uC804\uC810\uAC80").option("--json", "\uB9AC\uD3EC\uD2B8 JSON \uC744 stdout \uC73C\uB85C \uCD9C\uB825 (CI\uC6A9 \u2014 \uACBD\uB85C \uB300\uC2E0)").option("--report", "latest.json \uC744 \uC0AC\uB78C\uC6A9 \uC815\uC801 HTML(.vhk/reports/latest.html) \uB85C \uB80C\uB354 (\uC678\uBD80 \uC758\uC874 0)").option("--open", "\uB9AC\uD3EC\uD2B8 \uC0DD\uC131 \uD6C4 \uAE30\uBCF8 \uBE0C\uB77C\uC6B0\uC800\uB85C \uC5F4\uAE30 (\uBE44\uB300\uD654\uD615/CI/MCP \uC790\uB3D9 \uC2A4\uD0B5)").description("\uAC80\uC99D \uAC8C\uC774\uD2B8(tsc/test/build/secure) \uC2E4\uC81C \uC2E4\uD589 + \uC99D\uAC70 \uAE30\uB85D (.vhk/reports/latest.json)").action(async (opts) => {
|
|
6044
6211
|
await verify(opts);
|
|
6045
6212
|
});
|
|
6046
6213
|
program.command("context-show").alias("\uB9E5\uB77D\uBCF4\uAE30").description("\uD604\uC7AC \uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C \uB0B4\uC6A9 \uCD9C\uB825").action(async () => {
|
package/package.json
CHANGED