@doccov/cli 0.26.0 → 0.27.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/cli.js +203 -106
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.js +2 -0
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -32,6 +32,7 @@ var apiSurfaceConfigSchema = z.object({
|
|
|
32
32
|
});
|
|
33
33
|
var checkConfigSchema = z.object({
|
|
34
34
|
examples: exampleModesSchema.optional(),
|
|
35
|
+
minHealth: z.number().min(0).max(100).optional(),
|
|
35
36
|
minCoverage: z.number().min(0).max(100).optional(),
|
|
36
37
|
maxDrift: z.number().min(0).max(100).optional(),
|
|
37
38
|
minApiSurface: z.number().min(0).max(100).optional(),
|
|
@@ -70,6 +71,7 @@ var normalizeConfig = (input) => {
|
|
|
70
71
|
if (input.check) {
|
|
71
72
|
check = {
|
|
72
73
|
examples: input.check.examples,
|
|
74
|
+
minHealth: input.check.minHealth,
|
|
73
75
|
minCoverage: input.check.minCoverage,
|
|
74
76
|
maxDrift: input.check.maxDrift,
|
|
75
77
|
minApiSurface: input.check.minApiSurface,
|
|
@@ -1362,6 +1364,10 @@ async function handleFixes(openpkg, doccov, options, deps) {
|
|
|
1362
1364
|
const fixCount = fileEdits.reduce((s, e) => s + e.fixes.length, 0);
|
|
1363
1365
|
log(chalk5.dim(` ${relativePath} (${fixCount} fixes)`));
|
|
1364
1366
|
}
|
|
1367
|
+
if (options.healthScore !== undefined) {
|
|
1368
|
+
log("");
|
|
1369
|
+
log(chalk5.cyan("Run doccov check again to see updated health score"));
|
|
1370
|
+
}
|
|
1365
1371
|
return {
|
|
1366
1372
|
fixedDriftKeys,
|
|
1367
1373
|
editsApplied: totalFixes,
|
|
@@ -1800,9 +1806,31 @@ function renderGithubSummary(stats, options = {}) {
|
|
|
1800
1806
|
const coverageScore = options.coverageScore ?? stats.coverageScore;
|
|
1801
1807
|
const driftCount = options.driftCount ?? stats.driftCount;
|
|
1802
1808
|
const qualityIssues = options.qualityIssues ?? 0;
|
|
1803
|
-
let output =
|
|
1809
|
+
let output = "";
|
|
1810
|
+
if (stats.health) {
|
|
1811
|
+
const h = stats.health;
|
|
1812
|
+
const status = h.score >= 80 ? "✅" : h.score >= 50 ? "⚠️" : "❌";
|
|
1813
|
+
output += `## ${status} Documentation Health: ${h.score}%
|
|
1814
|
+
|
|
1815
|
+
`;
|
|
1816
|
+
output += `| Metric | Score | Details |
|
|
1817
|
+
|--------|-------|---------|
|
|
1818
|
+
`;
|
|
1819
|
+
output += `| Completeness | ${h.completeness.score}% | ${h.completeness.total - h.completeness.documented} missing docs |
|
|
1820
|
+
`;
|
|
1821
|
+
output += `| Accuracy | ${h.accuracy.score}% | ${h.accuracy.issues} drift issues |
|
|
1822
|
+
`;
|
|
1823
|
+
if (h.examples) {
|
|
1824
|
+
output += `| Examples | ${h.examples.score}% | ${h.examples.passed}/${h.examples.total} passed |
|
|
1825
|
+
`;
|
|
1826
|
+
}
|
|
1827
|
+
output += `
|
|
1828
|
+
`;
|
|
1829
|
+
} else {
|
|
1830
|
+
output += `## Documentation Coverage: ${coverageScore}%
|
|
1804
1831
|
|
|
1805
1832
|
`;
|
|
1833
|
+
}
|
|
1806
1834
|
output += `| Metric | Value |
|
|
1807
1835
|
|--------|-------|
|
|
1808
1836
|
`;
|
|
@@ -1814,10 +1842,12 @@ function renderGithubSummary(stats, options = {}) {
|
|
|
1814
1842
|
`;
|
|
1815
1843
|
output += `| Quality Issues | ${qualityIssues} |
|
|
1816
1844
|
`;
|
|
1817
|
-
|
|
1818
|
-
|
|
1845
|
+
if (!stats.health) {
|
|
1846
|
+
const status = coverageScore >= 80 ? "✅" : coverageScore >= 50 ? "⚠️" : "❌";
|
|
1847
|
+
output += `
|
|
1819
1848
|
${status} Coverage ${coverageScore >= 80 ? "passing" : coverageScore >= 50 ? "needs improvement" : "failing"}
|
|
1820
1849
|
`;
|
|
1850
|
+
}
|
|
1821
1851
|
return output;
|
|
1822
1852
|
}
|
|
1823
1853
|
// src/reports/markdown.ts
|
|
@@ -1831,6 +1861,19 @@ function renderMarkdown(stats, options = {}) {
|
|
|
1831
1861
|
const lines = [];
|
|
1832
1862
|
lines.push(`# DocCov Report: ${stats.packageName}@${stats.version}`);
|
|
1833
1863
|
lines.push("");
|
|
1864
|
+
if (stats.health) {
|
|
1865
|
+
const h = stats.health;
|
|
1866
|
+
lines.push(`## Documentation Health: ${h.score}%`);
|
|
1867
|
+
lines.push("");
|
|
1868
|
+
lines.push("| Metric | Score | Details |");
|
|
1869
|
+
lines.push("|--------|-------|---------|");
|
|
1870
|
+
lines.push(`| Completeness | ${h.completeness.score}% | ${h.completeness.total - h.completeness.documented} missing docs |`);
|
|
1871
|
+
lines.push(`| Accuracy | ${h.accuracy.score}% | ${h.accuracy.issues} drift issues |`);
|
|
1872
|
+
if (h.examples) {
|
|
1873
|
+
lines.push(`| Examples | ${h.examples.score}% | ${h.examples.passed}/${h.examples.total} passed |`);
|
|
1874
|
+
}
|
|
1875
|
+
lines.push("");
|
|
1876
|
+
}
|
|
1834
1877
|
lines.push(`**Coverage: ${stats.coverageScore}%** \`${bar2(stats.coverageScore)}\``);
|
|
1835
1878
|
lines.push("");
|
|
1836
1879
|
lines.push("| Metric | Value |");
|
|
@@ -2346,7 +2389,8 @@ function computeStats(openpkg, doccov) {
|
|
|
2346
2389
|
driftIssues,
|
|
2347
2390
|
driftByCategory,
|
|
2348
2391
|
driftSummary,
|
|
2349
|
-
apiSurface: doccov.apiSurface
|
|
2392
|
+
apiSurface: doccov.apiSurface,
|
|
2393
|
+
health: doccov.summary.health
|
|
2350
2394
|
};
|
|
2351
2395
|
}
|
|
2352
2396
|
// src/reports/writer.ts
|
|
@@ -2388,13 +2432,74 @@ function writeReports(options) {
|
|
|
2388
2432
|
return results;
|
|
2389
2433
|
}
|
|
2390
2434
|
// src/commands/check/output.ts
|
|
2435
|
+
function getHealthStatus(score) {
|
|
2436
|
+
if (score >= 80)
|
|
2437
|
+
return "pass";
|
|
2438
|
+
if (score >= 60)
|
|
2439
|
+
return "warn";
|
|
2440
|
+
return "fail";
|
|
2441
|
+
}
|
|
2442
|
+
function getHealthColor(status) {
|
|
2443
|
+
switch (status) {
|
|
2444
|
+
case "pass":
|
|
2445
|
+
return colors.success;
|
|
2446
|
+
case "warn":
|
|
2447
|
+
return colors.warning;
|
|
2448
|
+
case "fail":
|
|
2449
|
+
return colors.error;
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
function displayHealthTree(health, log) {
|
|
2453
|
+
const tree = supportsUnicode() ? { branch: "├─", corner: "└─" } : { branch: "|-", corner: "\\-" };
|
|
2454
|
+
const missingTotal = Object.values(health.completeness.missing).reduce((a, b) => a + b, 0);
|
|
2455
|
+
const completenessLabel = missingTotal > 0 ? `${health.completeness.score}% (${missingTotal} missing docs)` : `${health.completeness.score}%`;
|
|
2456
|
+
const completenessColor = getHealthColor(getHealthStatus(health.completeness.score));
|
|
2457
|
+
log(`${tree.branch} ${colors.muted("completeness")} ${completenessColor(completenessLabel)}`);
|
|
2458
|
+
const accuracyLabel = health.accuracy.issues > 0 ? `${health.accuracy.score}% (${health.accuracy.issues} drift issues${health.accuracy.fixable > 0 ? `, ${health.accuracy.fixable} fixable` : ""})` : `${health.accuracy.score}%`;
|
|
2459
|
+
const accuracyColor = getHealthColor(getHealthStatus(health.accuracy.score));
|
|
2460
|
+
const lastBranch = !health.examples ? tree.corner : tree.branch;
|
|
2461
|
+
log(`${lastBranch} ${colors.muted("accuracy")} ${accuracyColor(accuracyLabel)}`);
|
|
2462
|
+
if (health.examples) {
|
|
2463
|
+
const examplesLabel = health.examples.failed > 0 ? `${health.examples.score}% (${health.examples.failed} failed)` : `${health.examples.score}%`;
|
|
2464
|
+
const examplesColor = getHealthColor(getHealthStatus(health.examples.score));
|
|
2465
|
+
log(`${tree.corner} ${colors.muted("examples")} ${examplesColor(examplesLabel)}`);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
function displayHealthVerbose(health, log) {
|
|
2469
|
+
const tree = supportsUnicode() ? { branch: "├─", corner: "└─" } : { branch: "|-", corner: "\\-" };
|
|
2470
|
+
log(colors.bold("COMPLETENESS") + ` ${health.completeness.score}%`);
|
|
2471
|
+
const missingRules = Object.entries(health.completeness.missing);
|
|
2472
|
+
for (let i = 0;i < missingRules.length; i++) {
|
|
2473
|
+
const [rule, count] = missingRules[i];
|
|
2474
|
+
const isLast = i === missingRules.length - 1;
|
|
2475
|
+
const prefix2 = isLast ? tree.corner : tree.branch;
|
|
2476
|
+
const label = count > 0 ? `(${count} missing)` : "";
|
|
2477
|
+
const pct = health.completeness.total > 0 ? Math.round((health.completeness.total - count) / health.completeness.total * 100) : 100;
|
|
2478
|
+
const color = getHealthColor(getHealthStatus(pct));
|
|
2479
|
+
log(`${prefix2} ${colors.muted(rule.padEnd(12))} ${color(`${pct}%`)} ${colors.muted(label)}`);
|
|
2480
|
+
}
|
|
2481
|
+
log("");
|
|
2482
|
+
log(colors.bold("ACCURACY") + ` ${health.accuracy.score}% ${colors.muted(`(${health.accuracy.issues} issues)`)}`);
|
|
2483
|
+
const categories = Object.entries(health.accuracy.byCategory);
|
|
2484
|
+
for (let i = 0;i < categories.length; i++) {
|
|
2485
|
+
const [category, count] = categories[i];
|
|
2486
|
+
const isLast = i === categories.length - 1;
|
|
2487
|
+
const prefix2 = isLast ? tree.corner : tree.branch;
|
|
2488
|
+
log(`${prefix2} ${colors.muted(category.padEnd(12))} ${count}`);
|
|
2489
|
+
}
|
|
2490
|
+
if (health.examples) {
|
|
2491
|
+
log("");
|
|
2492
|
+
log(colors.bold("EXAMPLES") + ` ${health.examples.score}%`);
|
|
2493
|
+
log(`${tree.branch} ${colors.muted("passed".padEnd(12))} ${health.examples.passed}`);
|
|
2494
|
+
log(`${tree.corner} ${colors.muted("failed".padEnd(12))} ${health.examples.failed}`);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2391
2497
|
function displayTextOutput(options, deps) {
|
|
2392
2498
|
const {
|
|
2393
2499
|
openpkg,
|
|
2394
2500
|
doccov,
|
|
2395
2501
|
coverageScore,
|
|
2396
|
-
|
|
2397
|
-
maxDrift,
|
|
2502
|
+
minHealth,
|
|
2398
2503
|
minApiSurface,
|
|
2399
2504
|
warnBelowApiSurface,
|
|
2400
2505
|
driftExports,
|
|
@@ -2407,14 +2512,15 @@ function displayTextOutput(options, deps) {
|
|
|
2407
2512
|
} = options;
|
|
2408
2513
|
const { log } = deps;
|
|
2409
2514
|
const sym = getSymbols(supportsUnicode());
|
|
2515
|
+
const health = doccov.summary.health;
|
|
2516
|
+
const healthScore = health?.score ?? coverageScore;
|
|
2410
2517
|
const totalExportsForDrift = openpkg.exports?.length ?? 0;
|
|
2411
2518
|
const exportsWithDrift = new Set(driftExports.map((d) => d.name)).size;
|
|
2412
2519
|
const driftScore = totalExportsForDrift === 0 ? 0 : Math.round(exportsWithDrift / totalExportsForDrift * 100);
|
|
2413
2520
|
const apiSurface = doccov.apiSurface;
|
|
2414
2521
|
const apiSurfaceScore = apiSurface?.completeness ?? 100;
|
|
2415
2522
|
const forgottenCount = apiSurface?.forgotten?.length ?? 0;
|
|
2416
|
-
const
|
|
2417
|
-
const driftFailed = maxDrift !== undefined && driftScore > maxDrift;
|
|
2523
|
+
const healthFailed = healthScore < minHealth;
|
|
2418
2524
|
const apiSurfaceFailed = minApiSurface !== undefined && apiSurfaceScore < minApiSurface;
|
|
2419
2525
|
const apiSurfaceWarn = warnBelowApiSurface !== undefined && apiSurfaceScore < warnBelowApiSurface && !apiSurfaceFailed;
|
|
2420
2526
|
const hasTypecheckErrors = typecheckErrors.length > 0;
|
|
@@ -2435,43 +2541,35 @@ function displayTextOutput(options, deps) {
|
|
|
2435
2541
|
}
|
|
2436
2542
|
const pkgName = openpkg.meta?.name ?? "unknown";
|
|
2437
2543
|
const pkgVersion = openpkg.meta?.version ?? "";
|
|
2438
|
-
const totalExports = openpkg.exports?.length ?? 0;
|
|
2439
2544
|
log("");
|
|
2440
|
-
log(colors.bold(`${pkgName}${pkgVersion ?
|
|
2545
|
+
log(colors.bold(`${pkgName}${pkgVersion ? ` v${pkgVersion}` : ""}`));
|
|
2441
2546
|
log("");
|
|
2442
|
-
const
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2547
|
+
const hasStaleRefs = staleRefs.length > 0;
|
|
2548
|
+
if (health) {
|
|
2549
|
+
const healthColor = getHealthColor(getHealthStatus(health.score));
|
|
2550
|
+
log(`${colors.muted("Health")} ${healthColor(`${health.score}%`)}`);
|
|
2551
|
+
if (verbose) {
|
|
2552
|
+
log("");
|
|
2553
|
+
displayHealthVerbose(health, log);
|
|
2554
|
+
} else {
|
|
2555
|
+
displayHealthTree(health, log);
|
|
2556
|
+
}
|
|
2447
2557
|
} else {
|
|
2558
|
+
const summaryBuilder = summary({ keyWidth: 10 });
|
|
2559
|
+
summaryBuilder.addKeyValue("Exports", openpkg.exports?.length ?? 0);
|
|
2560
|
+
summaryBuilder.addKeyValue("Coverage", `${coverageScore}%`);
|
|
2448
2561
|
summaryBuilder.addKeyValue("Drift", `${driftScore}%`);
|
|
2562
|
+
summaryBuilder.print();
|
|
2449
2563
|
}
|
|
2564
|
+
log("");
|
|
2450
2565
|
if (forgottenCount > 0 || minApiSurface !== undefined || warnBelowApiSurface !== undefined) {
|
|
2451
2566
|
const surfaceLabel = forgottenCount > 0 ? `${apiSurfaceScore}% (${forgottenCount} forgotten)` : `${apiSurfaceScore}%`;
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
} else if (apiSurfaceWarn) {
|
|
2455
|
-
summaryBuilder.addKeyValue("API Surface", surfaceLabel, "warn");
|
|
2456
|
-
} else if (minApiSurface !== undefined) {
|
|
2457
|
-
summaryBuilder.addKeyValue("API Surface", surfaceLabel, "pass");
|
|
2458
|
-
} else {
|
|
2459
|
-
summaryBuilder.addKeyValue("API Surface", surfaceLabel, forgottenCount > 0 ? "warn" : undefined);
|
|
2460
|
-
}
|
|
2461
|
-
}
|
|
2462
|
-
if (exampleResult) {
|
|
2463
|
-
const typecheckCount = exampleResult.typecheck?.errors.length ?? 0;
|
|
2464
|
-
if (typecheckCount > 0) {
|
|
2465
|
-
summaryBuilder.addKeyValue("Examples", `${typecheckCount} type error(s)`, "warn");
|
|
2466
|
-
} else {
|
|
2467
|
-
summaryBuilder.addKeyValue("Examples", "validated", "pass");
|
|
2468
|
-
}
|
|
2567
|
+
const surfaceColor = apiSurfaceFailed ? colors.error : apiSurfaceWarn || forgottenCount > 0 ? colors.warning : colors.success;
|
|
2568
|
+
log(`${colors.muted("API Surface")} ${surfaceColor(surfaceLabel)}`);
|
|
2469
2569
|
}
|
|
2470
|
-
const hasStaleRefs = staleRefs.length > 0;
|
|
2471
2570
|
if (hasStaleRefs) {
|
|
2472
|
-
|
|
2571
|
+
log(`${colors.muted("Stale refs")} ${colors.warning(`${staleRefs.length} found`)}`);
|
|
2473
2572
|
}
|
|
2474
|
-
summaryBuilder.print();
|
|
2475
2573
|
if (hasTypecheckErrors) {
|
|
2476
2574
|
log("");
|
|
2477
2575
|
for (const err of typecheckErrors.slice(0, 5)) {
|
|
@@ -2520,13 +2618,10 @@ function displayTextOutput(options, deps) {
|
|
|
2520
2618
|
}
|
|
2521
2619
|
}
|
|
2522
2620
|
log("");
|
|
2523
|
-
const failed =
|
|
2621
|
+
const failed = healthFailed || apiSurfaceFailed || hasTypecheckErrors || hasStaleRefs;
|
|
2524
2622
|
if (!failed) {
|
|
2525
2623
|
const thresholdParts = [];
|
|
2526
|
-
thresholdParts.push(`
|
|
2527
|
-
if (maxDrift !== undefined) {
|
|
2528
|
-
thresholdParts.push(`drift ${driftScore}% ≤ ${maxDrift}%`);
|
|
2529
|
-
}
|
|
2624
|
+
thresholdParts.push(`health ${healthScore}% ≥ ${minHealth}%`);
|
|
2530
2625
|
if (minApiSurface !== undefined) {
|
|
2531
2626
|
thresholdParts.push(`api-surface ${apiSurfaceScore}% ≥ ${minApiSurface}%`);
|
|
2532
2627
|
}
|
|
@@ -2534,13 +2629,13 @@ function displayTextOutput(options, deps) {
|
|
|
2534
2629
|
if (apiSurfaceWarn) {
|
|
2535
2630
|
log(colors.warning(`${sym.warning} API Surface ${apiSurfaceScore}% below warning threshold ${warnBelowApiSurface}%`));
|
|
2536
2631
|
}
|
|
2632
|
+
if (!verbose && health) {
|
|
2633
|
+
log(colors.muted("Use --verbose for detailed breakdown"));
|
|
2634
|
+
}
|
|
2537
2635
|
return true;
|
|
2538
2636
|
}
|
|
2539
|
-
if (
|
|
2540
|
-
log(colors.error(`${sym.error}
|
|
2541
|
-
}
|
|
2542
|
-
if (driftFailed) {
|
|
2543
|
-
log(colors.error(`${sym.error} Drift ${driftScore}% exceeds maximum ${maxDrift}%`));
|
|
2637
|
+
if (healthFailed) {
|
|
2638
|
+
log(colors.error(`${sym.error} Health ${healthScore}% below minimum ${minHealth}%`));
|
|
2544
2639
|
}
|
|
2545
2640
|
if (apiSurfaceFailed) {
|
|
2546
2641
|
log(colors.error(`${sym.error} API Surface ${apiSurfaceScore}% below minimum ${minApiSurface}%`));
|
|
@@ -2551,8 +2646,10 @@ function displayTextOutput(options, deps) {
|
|
|
2551
2646
|
if (hasStaleRefs) {
|
|
2552
2647
|
log(colors.error(`${sym.error} ${staleRefs.length} stale references in docs`));
|
|
2553
2648
|
}
|
|
2554
|
-
|
|
2555
|
-
|
|
2649
|
+
if (health && health.accuracy.fixable > 0) {
|
|
2650
|
+
log("");
|
|
2651
|
+
log(colors.muted(`Use --fix to auto-fix ${health.accuracy.fixable} drift issue(s)`));
|
|
2652
|
+
}
|
|
2556
2653
|
return false;
|
|
2557
2654
|
}
|
|
2558
2655
|
function handleNonTextOutput(options, deps) {
|
|
@@ -2561,8 +2658,7 @@ function handleNonTextOutput(options, deps) {
|
|
|
2561
2658
|
openpkg,
|
|
2562
2659
|
doccov,
|
|
2563
2660
|
coverageScore,
|
|
2564
|
-
|
|
2565
|
-
maxDrift,
|
|
2661
|
+
minHealth,
|
|
2566
2662
|
minApiSurface,
|
|
2567
2663
|
driftExports,
|
|
2568
2664
|
typecheckErrors,
|
|
@@ -2575,6 +2671,7 @@ function handleNonTextOutput(options, deps) {
|
|
|
2575
2671
|
const stats = computeStats(openpkg, doccov);
|
|
2576
2672
|
const report = generateReportFromDocCov(openpkg, doccov);
|
|
2577
2673
|
const jsonContent = JSON.stringify(report, null, 2);
|
|
2674
|
+
const healthScore = doccov.summary.health?.score ?? coverageScore;
|
|
2578
2675
|
let formatContent;
|
|
2579
2676
|
switch (format) {
|
|
2580
2677
|
case "json":
|
|
@@ -2606,15 +2703,11 @@ function handleNonTextOutput(options, deps) {
|
|
|
2606
2703
|
cwd
|
|
2607
2704
|
});
|
|
2608
2705
|
}
|
|
2609
|
-
const
|
|
2610
|
-
const exportsWithDrift = new Set(driftExports.map((d) => d.name)).size;
|
|
2611
|
-
const driftScore = totalExportsForDrift === 0 ? 0 : Math.round(exportsWithDrift / totalExportsForDrift * 100);
|
|
2612
|
-
const coverageFailed = coverageScore < minCoverage;
|
|
2613
|
-
const driftFailed = maxDrift !== undefined && driftScore > maxDrift;
|
|
2706
|
+
const healthFailed = healthScore < minHealth;
|
|
2614
2707
|
const apiSurfaceScore = doccov.apiSurface?.completeness ?? 100;
|
|
2615
2708
|
const apiSurfaceFailed = minApiSurface !== undefined && apiSurfaceScore < minApiSurface;
|
|
2616
2709
|
const hasTypecheckErrors = typecheckErrors.length > 0;
|
|
2617
|
-
return !(
|
|
2710
|
+
return !(healthFailed || apiSurfaceFailed || hasTypecheckErrors);
|
|
2618
2711
|
}
|
|
2619
2712
|
function displayApiSurfaceOutput(doccov, deps) {
|
|
2620
2713
|
const { log } = deps;
|
|
@@ -2745,7 +2838,7 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2745
2838
|
...defaultDependencies,
|
|
2746
2839
|
...dependencies
|
|
2747
2840
|
};
|
|
2748
|
-
program.command("check [entry]").description("Check documentation coverage and output reports").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--min-
|
|
2841
|
+
program.command("check [entry]").description("Check documentation coverage and output reports").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--min-health <percentage>", "Minimum health score (0-100)", (value) => Number(value)).option("--min-coverage <percentage>", "[deprecated] Use --min-health instead", (value) => Number(value)).option("--max-drift <percentage>", "[deprecated] Use --min-health instead", (value) => Number(value)).option("--examples [mode]", "Example validation: presence, typecheck, run (comma-separated). Bare flag runs all.").option("--skip-resolve", "Skip external type resolution from node_modules").option("--docs <glob>", "Glob pattern for markdown docs to check for stale refs", collect, []).option("--fix", "Auto-fix drift issues").option("--preview", "Preview fixes with diff output (implies --fix)").option("--format <format>", "Output format: text, json, markdown, html, github", "text").option("-o, --output <file>", "Custom output path (overrides default .doccov/ path)").option("--stdout", "Output to stdout instead of writing to .doccov/").option("--update-snapshot", "Force regenerate .doccov/report.json").option("--limit <n>", "Max exports to show in report tables", "20").option("--max-type-depth <number>", "Maximum depth for type conversion (default: 20)", (value) => {
|
|
2749
2842
|
const n = parseInt(value, 10);
|
|
2750
2843
|
if (Number.isNaN(n) || n < 1)
|
|
2751
2844
|
throw new Error("--max-type-depth must be a positive integer");
|
|
@@ -2772,8 +2865,22 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2772
2865
|
}
|
|
2773
2866
|
hasExamples = validations.length > 0;
|
|
2774
2867
|
}
|
|
2775
|
-
|
|
2776
|
-
|
|
2868
|
+
if (options.minCoverage !== undefined) {
|
|
2869
|
+
log(chalk7.yellow("Warning: --min-coverage is deprecated. Use --min-health instead."));
|
|
2870
|
+
}
|
|
2871
|
+
if (options.maxDrift !== undefined) {
|
|
2872
|
+
log(chalk7.yellow("Warning: --max-drift is deprecated. Use --min-health instead."));
|
|
2873
|
+
}
|
|
2874
|
+
if (config?.check?.minCoverage !== undefined) {
|
|
2875
|
+
log(chalk7.yellow("Warning: config.check.minCoverage is deprecated. Use minHealth."));
|
|
2876
|
+
}
|
|
2877
|
+
if (config?.check?.maxDrift !== undefined) {
|
|
2878
|
+
log(chalk7.yellow("Warning: config.check.maxDrift is deprecated. Use minHealth."));
|
|
2879
|
+
}
|
|
2880
|
+
const DEFAULT_MIN_HEALTH = 80;
|
|
2881
|
+
const minHealthRaw = options.minHealth ?? config?.check?.minHealth ?? DEFAULT_MIN_HEALTH;
|
|
2882
|
+
const minHealth = clampPercentage(minHealthRaw);
|
|
2883
|
+
const minCoverageRaw = options.minCoverage ?? config?.check?.minCoverage ?? DEFAULT_MIN_HEALTH;
|
|
2777
2884
|
const minCoverage = clampPercentage(minCoverageRaw);
|
|
2778
2885
|
const maxDriftRaw = options.maxDrift ?? config?.check?.maxDrift;
|
|
2779
2886
|
const maxDrift = maxDriftRaw !== undefined ? clampPercentage(maxDriftRaw) : undefined;
|
|
@@ -2796,7 +2903,7 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2796
2903
|
resolveExternalTypes,
|
|
2797
2904
|
maxDepth: options.maxTypeDepth,
|
|
2798
2905
|
useCache: options.cache !== false,
|
|
2799
|
-
cwd:
|
|
2906
|
+
cwd: targetDir
|
|
2800
2907
|
});
|
|
2801
2908
|
const analyzeOptions = resolvedFilters.visibility ? { filters: { visibility: resolvedFilters.visibility } } : {};
|
|
2802
2909
|
const specResult = await analyzer.analyzeFileWithDiagnostics(entryFile, analyzeOptions);
|
|
@@ -2841,8 +2948,9 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2841
2948
|
const coverageScore = doccov.summary.score;
|
|
2842
2949
|
const allDriftExports = [...collectDrift(openpkg.exports ?? [], doccov), ...runtimeDrifts];
|
|
2843
2950
|
let driftExports = hasExamples ? allDriftExports : allDriftExports.filter((d) => d.category !== "example");
|
|
2951
|
+
const healthScore = doccov.summary.health?.score ?? coverageScore;
|
|
2844
2952
|
if (shouldFix && driftExports.length > 0) {
|
|
2845
|
-
const fixResult = await handleFixes(openpkg, doccov, { isPreview, targetDir, entryFile }, { log, error });
|
|
2953
|
+
const fixResult = await handleFixes(openpkg, doccov, { isPreview, targetDir, entryFile, healthScore }, { log, error });
|
|
2846
2954
|
if (!isPreview) {
|
|
2847
2955
|
driftExports = driftExports.filter((d) => !fixResult.fixedDriftKeys.has(`${d.name}:${d.issue}`));
|
|
2848
2956
|
}
|
|
@@ -2865,15 +2973,14 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2865
2973
|
openpkg,
|
|
2866
2974
|
doccov,
|
|
2867
2975
|
coverageScore,
|
|
2868
|
-
|
|
2869
|
-
maxDrift,
|
|
2976
|
+
minHealth,
|
|
2870
2977
|
minApiSurface,
|
|
2871
2978
|
driftExports,
|
|
2872
2979
|
typecheckErrors,
|
|
2873
2980
|
limit: parseInt(options.limit, 10) || 20,
|
|
2874
2981
|
stdout: options.stdout,
|
|
2875
2982
|
outputPath: options.output,
|
|
2876
|
-
cwd:
|
|
2983
|
+
cwd: targetDir
|
|
2877
2984
|
}, { log });
|
|
2878
2985
|
if (!passed2) {
|
|
2879
2986
|
process.exit(1);
|
|
@@ -2884,8 +2991,7 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2884
2991
|
openpkg,
|
|
2885
2992
|
doccov,
|
|
2886
2993
|
coverageScore,
|
|
2887
|
-
|
|
2888
|
-
maxDrift,
|
|
2994
|
+
minHealth,
|
|
2889
2995
|
minApiSurface,
|
|
2890
2996
|
warnBelowApiSurface,
|
|
2891
2997
|
driftExports,
|
|
@@ -3534,12 +3640,11 @@ import {
|
|
|
3534
3640
|
resolveTarget as resolveTarget3
|
|
3535
3641
|
} from "@doccov/sdk";
|
|
3536
3642
|
import { validateDocCovSpec } from "@doccov/spec";
|
|
3537
|
-
import {
|
|
3643
|
+
import {
|
|
3644
|
+
normalize,
|
|
3645
|
+
validateSpec
|
|
3646
|
+
} from "@openpkg-ts/spec";
|
|
3538
3647
|
import chalk11 from "chalk";
|
|
3539
|
-
// package.json
|
|
3540
|
-
var version = "0.25.11";
|
|
3541
|
-
|
|
3542
|
-
// src/commands/spec.ts
|
|
3543
3648
|
var defaultDependencies4 = {
|
|
3544
3649
|
createDocCov: (options) => new DocCov3(options),
|
|
3545
3650
|
writeFileSync: fs6.writeFileSync,
|
|
@@ -3593,24 +3698,13 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
3593
3698
|
cwd: options.cwd,
|
|
3594
3699
|
schemaExtraction: options.runtime ? "hybrid" : "static"
|
|
3595
3700
|
});
|
|
3596
|
-
const generationInput = {
|
|
3597
|
-
entryPoint: path8.relative(targetDir, entryFile),
|
|
3598
|
-
entryPointSource: entryPointInfo.source,
|
|
3599
|
-
isDeclarationOnly: entryPointInfo.isDeclarationOnly ?? false,
|
|
3600
|
-
generatorName: "@doccov/cli",
|
|
3601
|
-
generatorVersion: version,
|
|
3602
|
-
packageManager: packageInfo?.packageManager,
|
|
3603
|
-
isMonorepo: resolved.isMonorepo,
|
|
3604
|
-
targetPackage: packageInfo?.name
|
|
3605
|
-
};
|
|
3606
3701
|
const analyzeOptions = resolvedFilters.include || resolvedFilters.exclude || resolvedFilters.visibility ? {
|
|
3607
3702
|
filters: {
|
|
3608
3703
|
include: resolvedFilters.include,
|
|
3609
3704
|
exclude: resolvedFilters.exclude,
|
|
3610
3705
|
visibility: resolvedFilters.visibility
|
|
3611
|
-
}
|
|
3612
|
-
|
|
3613
|
-
} : { generationInput };
|
|
3706
|
+
}
|
|
3707
|
+
} : {};
|
|
3614
3708
|
const result = await doccov.analyzeFileWithDiagnostics(entryFile, analyzeOptions);
|
|
3615
3709
|
if (!result) {
|
|
3616
3710
|
spin.fail("Generation failed");
|
|
@@ -3667,7 +3761,11 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
3667
3761
|
log(chalk11.gray(` ${getArrayLength(normalized.types)} types`));
|
|
3668
3762
|
}
|
|
3669
3763
|
}
|
|
3670
|
-
const
|
|
3764
|
+
const isFullGenerationInfo = (gen) => {
|
|
3765
|
+
return gen !== undefined && "analysis" in gen && "environment" in gen;
|
|
3766
|
+
};
|
|
3767
|
+
const fullGen = isFullGenerationInfo(normalized.generation) ? normalized.generation : undefined;
|
|
3768
|
+
const schemaExtraction = fullGen?.analysis?.schemaExtraction;
|
|
3671
3769
|
if (options.runtime && (!schemaExtraction?.runtimeCount || schemaExtraction.runtimeCount === 0)) {
|
|
3672
3770
|
const pm = await detectPackageManager(fileSystem);
|
|
3673
3771
|
const buildCmd = pm.name === "npm" ? "npm run build" : `${pm.name} run build`;
|
|
@@ -3675,21 +3773,20 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
3675
3773
|
log(chalk11.yellow("⚠ Runtime extraction requested but no schemas extracted."));
|
|
3676
3774
|
log(chalk11.yellow(` Ensure project is built (${buildCmd}) and dist/ exists.`));
|
|
3677
3775
|
}
|
|
3678
|
-
if (options.verbose &&
|
|
3679
|
-
const gen = normalized.generation;
|
|
3776
|
+
if (options.verbose && fullGen) {
|
|
3680
3777
|
log("");
|
|
3681
3778
|
log(chalk11.bold("Generation Info"));
|
|
3682
|
-
log(chalk11.gray(` Timestamp: ${
|
|
3683
|
-
log(chalk11.gray(` Generator: ${
|
|
3684
|
-
log(chalk11.gray(` Entry point: ${
|
|
3685
|
-
log(chalk11.gray(` Detected via: ${
|
|
3686
|
-
log(chalk11.gray(` Declaration only: ${
|
|
3687
|
-
log(chalk11.gray(` External types: ${
|
|
3688
|
-
if (
|
|
3689
|
-
log(chalk11.gray(` Max type depth: ${
|
|
3779
|
+
log(chalk11.gray(` Timestamp: ${fullGen.timestamp}`));
|
|
3780
|
+
log(chalk11.gray(` Generator: ${fullGen.generator.name}@${fullGen.generator.version}`));
|
|
3781
|
+
log(chalk11.gray(` Entry point: ${fullGen.analysis.entryPoint}`));
|
|
3782
|
+
log(chalk11.gray(` Detected via: ${fullGen.analysis.entryPointSource}`));
|
|
3783
|
+
log(chalk11.gray(` Declaration only: ${fullGen.analysis.isDeclarationOnly ? "yes" : "no"}`));
|
|
3784
|
+
log(chalk11.gray(` External types: ${fullGen.analysis.resolvedExternalTypes ? "resolved" : "skipped"}`));
|
|
3785
|
+
if (fullGen.analysis.maxTypeDepth) {
|
|
3786
|
+
log(chalk11.gray(` Max type depth: ${fullGen.analysis.maxTypeDepth}`));
|
|
3690
3787
|
}
|
|
3691
|
-
if (
|
|
3692
|
-
const se =
|
|
3788
|
+
if (fullGen.analysis.schemaExtraction) {
|
|
3789
|
+
const se = fullGen.analysis.schemaExtraction;
|
|
3693
3790
|
log(chalk11.gray(` Schema extraction: ${se.method}`));
|
|
3694
3791
|
if (se.runtimeCount) {
|
|
3695
3792
|
log(chalk11.gray(` Runtime schemas: ${se.runtimeCount} (${se.vendors?.join(", ")})`));
|
|
@@ -3697,20 +3794,20 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
3697
3794
|
}
|
|
3698
3795
|
log("");
|
|
3699
3796
|
log(chalk11.bold("Environment"));
|
|
3700
|
-
log(chalk11.gray(` node_modules: ${
|
|
3701
|
-
if (
|
|
3702
|
-
log(chalk11.gray(` Package manager: ${
|
|
3797
|
+
log(chalk11.gray(` node_modules: ${fullGen.environment.hasNodeModules ? "found" : "not found"}`));
|
|
3798
|
+
if (fullGen.environment.packageManager) {
|
|
3799
|
+
log(chalk11.gray(` Package manager: ${fullGen.environment.packageManager}`));
|
|
3703
3800
|
}
|
|
3704
|
-
if (
|
|
3801
|
+
if (fullGen.environment.isMonorepo) {
|
|
3705
3802
|
log(chalk11.gray(` Monorepo: yes`));
|
|
3706
3803
|
}
|
|
3707
|
-
if (
|
|
3708
|
-
log(chalk11.gray(` Target package: ${
|
|
3804
|
+
if (fullGen.environment.targetPackage) {
|
|
3805
|
+
log(chalk11.gray(` Target package: ${fullGen.environment.targetPackage}`));
|
|
3709
3806
|
}
|
|
3710
|
-
if (
|
|
3807
|
+
if (fullGen.issues.length > 0) {
|
|
3711
3808
|
log("");
|
|
3712
3809
|
log(chalk11.bold("Issues"));
|
|
3713
|
-
for (const issue of
|
|
3810
|
+
for (const issue of fullGen.issues) {
|
|
3714
3811
|
const prefix2 = issue.severity === "error" ? chalk11.red(">") : issue.severity === "warning" ? chalk11.yellow(">") : chalk11.cyan(">");
|
|
3715
3812
|
log(`${prefix2} [${issue.code}] ${issue.message}`);
|
|
3716
3813
|
if (issue.suggestion) {
|
|
@@ -3771,9 +3868,9 @@ function getColorForScore(score) {
|
|
|
3771
3868
|
function formatSnapshot(snapshot) {
|
|
3772
3869
|
const color = getColorForScore(snapshot.coverageScore);
|
|
3773
3870
|
const date = formatDate(snapshot.timestamp);
|
|
3774
|
-
const
|
|
3871
|
+
const version = snapshot.version ? ` v${snapshot.version}` : "";
|
|
3775
3872
|
const commit = snapshot.commit ? chalk12.gray(` (${snapshot.commit.slice(0, 7)})`) : "";
|
|
3776
|
-
return `${chalk12.gray(date)} ${color(`${snapshot.coverageScore}%`)} ${snapshot.documentedExports}/${snapshot.totalExports} exports${
|
|
3873
|
+
return `${chalk12.gray(date)} ${color(`${snapshot.coverageScore}%`)} ${snapshot.documentedExports}/${snapshot.totalExports} exports${version}${commit}`;
|
|
3777
3874
|
}
|
|
3778
3875
|
function formatWeekDate(timestamp) {
|
|
3779
3876
|
const date = new Date(timestamp);
|
package/dist/config/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ declare const apiSurfaceConfigSchema: z.ZodObject<{
|
|
|
25
25
|
*/
|
|
26
26
|
declare const checkConfigSchema: z.ZodObject<{
|
|
27
27
|
examples: z.ZodOptional<typeof exampleModesSchema>;
|
|
28
|
+
minHealth: z.ZodOptional<z.ZodNumber>;
|
|
28
29
|
minCoverage: z.ZodOptional<z.ZodNumber>;
|
|
29
30
|
maxDrift: z.ZodOptional<z.ZodNumber>;
|
|
30
31
|
minApiSurface: z.ZodOptional<z.ZodNumber>;
|
package/dist/config/index.js
CHANGED
|
@@ -30,6 +30,7 @@ var apiSurfaceConfigSchema = z.object({
|
|
|
30
30
|
});
|
|
31
31
|
var checkConfigSchema = z.object({
|
|
32
32
|
examples: exampleModesSchema.optional(),
|
|
33
|
+
minHealth: z.number().min(0).max(100).optional(),
|
|
33
34
|
minCoverage: z.number().min(0).max(100).optional(),
|
|
34
35
|
maxDrift: z.number().min(0).max(100).optional(),
|
|
35
36
|
minApiSurface: z.number().min(0).max(100).optional(),
|
|
@@ -68,6 +69,7 @@ var normalizeConfig = (input) => {
|
|
|
68
69
|
if (input.check) {
|
|
69
70
|
check = {
|
|
70
71
|
examples: input.check.examples,
|
|
72
|
+
minHealth: input.check.minHealth,
|
|
71
73
|
minCoverage: input.check.minCoverage,
|
|
72
74
|
maxDrift: input.check.maxDrift,
|
|
73
75
|
minApiSurface: input.check.minApiSurface,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doccov/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.1",
|
|
4
4
|
"description": "DocCov CLI - Documentation coverage and drift detection for TypeScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@ai-sdk/anthropic": "^1.0.0",
|
|
50
50
|
"@ai-sdk/openai": "^1.0.0",
|
|
51
|
-
"@doccov/sdk": "^0.
|
|
52
|
-
"@doccov/spec": "^0.
|
|
51
|
+
"@doccov/sdk": "^0.27.0",
|
|
52
|
+
"@doccov/spec": "^0.27.0",
|
|
53
53
|
"@inquirer/prompts": "^7.8.0",
|
|
54
54
|
"@openpkg-ts/spec": "^0.12.0",
|
|
55
55
|
"ai": "^4.0.0",
|