@doccov/cli 0.25.11 → 0.27.0
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 +435 -115
- package/dist/config/index.d.ts +11 -0
- package/dist/config/index.js +13 -2
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -25,10 +25,18 @@ var exampleModesSchema = z.union([
|
|
|
25
25
|
z.array(exampleModeSchema),
|
|
26
26
|
z.string()
|
|
27
27
|
]);
|
|
28
|
+
var apiSurfaceConfigSchema = z.object({
|
|
29
|
+
minCompleteness: z.number().min(0).max(100).optional(),
|
|
30
|
+
warnBelow: z.number().min(0).max(100).optional(),
|
|
31
|
+
ignore: z.array(z.string()).optional()
|
|
32
|
+
});
|
|
28
33
|
var checkConfigSchema = z.object({
|
|
29
34
|
examples: exampleModesSchema.optional(),
|
|
35
|
+
minHealth: z.number().min(0).max(100).optional(),
|
|
30
36
|
minCoverage: z.number().min(0).max(100).optional(),
|
|
31
|
-
maxDrift: z.number().min(0).max(100).optional()
|
|
37
|
+
maxDrift: z.number().min(0).max(100).optional(),
|
|
38
|
+
minApiSurface: z.number().min(0).max(100).optional(),
|
|
39
|
+
apiSurface: apiSurfaceConfigSchema.optional()
|
|
32
40
|
});
|
|
33
41
|
var docCovConfigSchema = z.object({
|
|
34
42
|
include: stringList.optional(),
|
|
@@ -63,8 +71,11 @@ var normalizeConfig = (input) => {
|
|
|
63
71
|
if (input.check) {
|
|
64
72
|
check = {
|
|
65
73
|
examples: input.check.examples,
|
|
74
|
+
minHealth: input.check.minHealth,
|
|
66
75
|
minCoverage: input.check.minCoverage,
|
|
67
|
-
maxDrift: input.check.maxDrift
|
|
76
|
+
maxDrift: input.check.maxDrift,
|
|
77
|
+
minApiSurface: input.check.minApiSurface,
|
|
78
|
+
apiSurface: input.check.apiSurface
|
|
68
79
|
};
|
|
69
80
|
}
|
|
70
81
|
return {
|
|
@@ -151,6 +162,16 @@ import * as path10 from "node:path";
|
|
|
151
162
|
import { fileURLToPath } from "node:url";
|
|
152
163
|
import { Command } from "commander";
|
|
153
164
|
|
|
165
|
+
// src/commands/check/index.ts
|
|
166
|
+
import {
|
|
167
|
+
buildDocCovSpec,
|
|
168
|
+
DocCov,
|
|
169
|
+
NodeFileSystem,
|
|
170
|
+
parseExamplesFlag,
|
|
171
|
+
resolveTarget
|
|
172
|
+
} from "@doccov/sdk";
|
|
173
|
+
import chalk7 from "chalk";
|
|
174
|
+
|
|
154
175
|
// ../cli-utils/dist/index.js
|
|
155
176
|
import chalk from "chalk";
|
|
156
177
|
import chalk2 from "chalk";
|
|
@@ -1141,16 +1162,6 @@ function summary(options) {
|
|
|
1141
1162
|
return new Summary(options);
|
|
1142
1163
|
}
|
|
1143
1164
|
|
|
1144
|
-
// src/commands/check/index.ts
|
|
1145
|
-
import {
|
|
1146
|
-
buildDocCovSpec,
|
|
1147
|
-
DocCov,
|
|
1148
|
-
NodeFileSystem,
|
|
1149
|
-
parseExamplesFlag,
|
|
1150
|
-
resolveTarget
|
|
1151
|
-
} from "@doccov/sdk";
|
|
1152
|
-
import chalk7 from "chalk";
|
|
1153
|
-
|
|
1154
1165
|
// src/utils/filter-options.ts
|
|
1155
1166
|
import { mergeFilters, parseListFlag } from "@doccov/sdk";
|
|
1156
1167
|
import chalk3 from "chalk";
|
|
@@ -1218,12 +1229,15 @@ import * as fs2 from "node:fs";
|
|
|
1218
1229
|
import * as path3 from "node:path";
|
|
1219
1230
|
import {
|
|
1220
1231
|
applyEdits,
|
|
1232
|
+
applyForgottenExportFixes,
|
|
1221
1233
|
categorizeDrifts,
|
|
1222
1234
|
createSourceFile,
|
|
1223
1235
|
findJSDocLocation,
|
|
1224
1236
|
generateFixesForExport,
|
|
1237
|
+
generateForgottenExportFixes,
|
|
1225
1238
|
mergeFixes,
|
|
1226
1239
|
parseJSDocToPatch,
|
|
1240
|
+
previewForgottenExportFixes,
|
|
1227
1241
|
serializeJSDoc
|
|
1228
1242
|
} from "@doccov/sdk";
|
|
1229
1243
|
import chalk5 from "chalk";
|
|
@@ -1296,12 +1310,12 @@ async function handleFixes(openpkg, doccov, options, deps) {
|
|
|
1296
1310
|
const fixedDriftKeys = new Set;
|
|
1297
1311
|
const allDrifts = collectDriftsFromExports(openpkg.exports ?? [], doccov);
|
|
1298
1312
|
if (allDrifts.length === 0) {
|
|
1299
|
-
return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
|
|
1313
|
+
return { fixedDriftKeys, editsApplied: 0, filesModified: 0, forgottenExportsFixed: 0 };
|
|
1300
1314
|
}
|
|
1301
1315
|
const { fixable, nonFixable } = categorizeDrifts(allDrifts.map((d) => d.drift));
|
|
1302
1316
|
if (fixable.length === 0) {
|
|
1303
1317
|
log(chalk5.yellow(`Found ${nonFixable.length} drift issue(s), but none are auto-fixable.`));
|
|
1304
|
-
return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
|
|
1318
|
+
return { fixedDriftKeys, editsApplied: 0, filesModified: 0, forgottenExportsFixed: 0 };
|
|
1305
1319
|
}
|
|
1306
1320
|
log("");
|
|
1307
1321
|
log(chalk5.bold(`Found ${fixable.length} fixable issue(s)`));
|
|
@@ -1330,11 +1344,11 @@ async function handleFixes(openpkg, doccov, options, deps) {
|
|
|
1330
1344
|
editsByFile.set(edit.filePath, fileEdits);
|
|
1331
1345
|
}
|
|
1332
1346
|
if (edits.length === 0) {
|
|
1333
|
-
return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
|
|
1347
|
+
return { fixedDriftKeys, editsApplied: 0, filesModified: 0, forgottenExportsFixed: 0 };
|
|
1334
1348
|
}
|
|
1335
1349
|
if (isPreview) {
|
|
1336
1350
|
displayPreview(editsByFile, targetDir, log);
|
|
1337
|
-
return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
|
|
1351
|
+
return { fixedDriftKeys, editsApplied: 0, filesModified: 0, forgottenExportsFixed: 0 };
|
|
1338
1352
|
}
|
|
1339
1353
|
const applyResult = await applyEdits(edits);
|
|
1340
1354
|
if (applyResult.errors.length > 0) {
|
|
@@ -1350,12 +1364,78 @@ async function handleFixes(openpkg, doccov, options, deps) {
|
|
|
1350
1364
|
const fixCount = fileEdits.reduce((s, e) => s + e.fixes.length, 0);
|
|
1351
1365
|
log(chalk5.dim(` ${relativePath} (${fixCount} fixes)`));
|
|
1352
1366
|
}
|
|
1367
|
+
if (options.healthScore !== undefined) {
|
|
1368
|
+
log("");
|
|
1369
|
+
log(chalk5.cyan("Run doccov check again to see updated health score"));
|
|
1370
|
+
}
|
|
1353
1371
|
return {
|
|
1354
1372
|
fixedDriftKeys,
|
|
1355
1373
|
editsApplied: totalFixes,
|
|
1356
|
-
filesModified: applyResult.filesModified
|
|
1374
|
+
filesModified: applyResult.filesModified,
|
|
1375
|
+
forgottenExportsFixed: 0
|
|
1357
1376
|
};
|
|
1358
1377
|
}
|
|
1378
|
+
async function handleForgottenExportFixes(doccov, options, deps) {
|
|
1379
|
+
const { isPreview, targetDir, entryFile } = options;
|
|
1380
|
+
const { log, error } = deps;
|
|
1381
|
+
if (!doccov.apiSurface || doccov.apiSurface.forgotten.length === 0) {
|
|
1382
|
+
return { fixesApplied: 0, filesModified: 0 };
|
|
1383
|
+
}
|
|
1384
|
+
const fixable = doccov.apiSurface.forgotten.filter((f) => !f.isExternal && f.fix);
|
|
1385
|
+
if (fixable.length === 0) {
|
|
1386
|
+
return { fixesApplied: 0, filesModified: 0 };
|
|
1387
|
+
}
|
|
1388
|
+
const fixes = generateForgottenExportFixes(doccov.apiSurface, {
|
|
1389
|
+
baseDir: targetDir,
|
|
1390
|
+
entryFile: entryFile ?? "src/index.ts"
|
|
1391
|
+
});
|
|
1392
|
+
if (fixes.length === 0) {
|
|
1393
|
+
return { fixesApplied: 0, filesModified: 0 };
|
|
1394
|
+
}
|
|
1395
|
+
log("");
|
|
1396
|
+
log(chalk5.bold(`Found ${fixes.length} forgotten export(s) to fix`));
|
|
1397
|
+
if (isPreview) {
|
|
1398
|
+
displayForgottenExportPreview(fixes, targetDir, log);
|
|
1399
|
+
return { fixesApplied: 0, filesModified: 0 };
|
|
1400
|
+
}
|
|
1401
|
+
const result = await applyForgottenExportFixes(fixes);
|
|
1402
|
+
if (result.errors.length > 0) {
|
|
1403
|
+
for (const err of result.errors) {
|
|
1404
|
+
error(chalk5.red(` ${err.file}: ${err.error}`));
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
if (result.fixesApplied > 0) {
|
|
1408
|
+
log("");
|
|
1409
|
+
log(chalk5.green(`✓ Added ${result.fixesApplied} export(s) to ${result.filesModified} file(s)`));
|
|
1410
|
+
const grouped = new Map;
|
|
1411
|
+
for (const fix of fixes) {
|
|
1412
|
+
const relativePath = path3.relative(targetDir, fix.targetFile);
|
|
1413
|
+
const types = grouped.get(relativePath) ?? [];
|
|
1414
|
+
types.push(fix.typeName);
|
|
1415
|
+
grouped.set(relativePath, types);
|
|
1416
|
+
}
|
|
1417
|
+
for (const [file, types] of grouped) {
|
|
1418
|
+
log(chalk5.dim(` ${file}: ${types.join(", ")}`));
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
return { fixesApplied: result.fixesApplied, filesModified: result.filesModified };
|
|
1422
|
+
}
|
|
1423
|
+
function displayForgottenExportPreview(fixes, targetDir, log) {
|
|
1424
|
+
log(chalk5.bold("Preview - forgotten exports that would be added:"));
|
|
1425
|
+
log("");
|
|
1426
|
+
const previews = previewForgottenExportFixes(fixes);
|
|
1427
|
+
for (const [filePath, preview] of previews) {
|
|
1428
|
+
const relativePath = path3.relative(targetDir, filePath);
|
|
1429
|
+
log(chalk5.cyan(`${relativePath}:${preview.insertLine + 1}`));
|
|
1430
|
+
log("");
|
|
1431
|
+
for (const stmt of preview.statements) {
|
|
1432
|
+
log(chalk5.green(` + ${stmt}`));
|
|
1433
|
+
}
|
|
1434
|
+
log("");
|
|
1435
|
+
}
|
|
1436
|
+
log(chalk5.yellow(`${fixes.length} export(s) would be added.`));
|
|
1437
|
+
log(chalk5.gray("Run with --fix to apply these changes."));
|
|
1438
|
+
}
|
|
1359
1439
|
function generateEditForExport(exp, drifts, targetDir, log) {
|
|
1360
1440
|
if (!exp.source?.file) {
|
|
1361
1441
|
log(chalk5.gray(` Skipping ${exp.name}: no source location`));
|
|
@@ -1726,9 +1806,31 @@ function renderGithubSummary(stats, options = {}) {
|
|
|
1726
1806
|
const coverageScore = options.coverageScore ?? stats.coverageScore;
|
|
1727
1807
|
const driftCount = options.driftCount ?? stats.driftCount;
|
|
1728
1808
|
const qualityIssues = options.qualityIssues ?? 0;
|
|
1729
|
-
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}%
|
|
1730
1814
|
|
|
1731
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}%
|
|
1831
|
+
|
|
1832
|
+
`;
|
|
1833
|
+
}
|
|
1732
1834
|
output += `| Metric | Value |
|
|
1733
1835
|
|--------|-------|
|
|
1734
1836
|
`;
|
|
@@ -1740,10 +1842,12 @@ function renderGithubSummary(stats, options = {}) {
|
|
|
1740
1842
|
`;
|
|
1741
1843
|
output += `| Quality Issues | ${qualityIssues} |
|
|
1742
1844
|
`;
|
|
1743
|
-
|
|
1744
|
-
|
|
1845
|
+
if (!stats.health) {
|
|
1846
|
+
const status = coverageScore >= 80 ? "✅" : coverageScore >= 50 ? "⚠️" : "❌";
|
|
1847
|
+
output += `
|
|
1745
1848
|
${status} Coverage ${coverageScore >= 80 ? "passing" : coverageScore >= 50 ? "needs improvement" : "failing"}
|
|
1746
1849
|
`;
|
|
1850
|
+
}
|
|
1747
1851
|
return output;
|
|
1748
1852
|
}
|
|
1749
1853
|
// src/reports/markdown.ts
|
|
@@ -1757,6 +1861,19 @@ function renderMarkdown(stats, options = {}) {
|
|
|
1757
1861
|
const lines = [];
|
|
1758
1862
|
lines.push(`# DocCov Report: ${stats.packageName}@${stats.version}`);
|
|
1759
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
|
+
}
|
|
1760
1877
|
lines.push(`**Coverage: ${stats.coverageScore}%** \`${bar2(stats.coverageScore)}\``);
|
|
1761
1878
|
lines.push("");
|
|
1762
1879
|
lines.push("| Metric | Value |");
|
|
@@ -1836,6 +1953,24 @@ function renderMarkdown(stats, options = {}) {
|
|
|
1836
1953
|
lines.push("");
|
|
1837
1954
|
}
|
|
1838
1955
|
}
|
|
1956
|
+
if (stats.apiSurface && stats.apiSurface.forgotten.length > 0) {
|
|
1957
|
+
lines.push("");
|
|
1958
|
+
lines.push("## API Surface");
|
|
1959
|
+
lines.push("");
|
|
1960
|
+
lines.push(`**${stats.apiSurface.completeness}% complete** (${stats.apiSurface.forgotten.length} forgotten exports)`);
|
|
1961
|
+
lines.push("");
|
|
1962
|
+
lines.push("| Type | Defined In | Referenced By |");
|
|
1963
|
+
lines.push("|------|------------|---------------|");
|
|
1964
|
+
for (const f of stats.apiSurface.forgotten.slice(0, limit)) {
|
|
1965
|
+
const definedIn = f.definedIn ? `${f.definedIn.file}${f.definedIn.line ? `:${f.definedIn.line}` : ""}` : "-";
|
|
1966
|
+
const refs = f.referencedBy.slice(0, 2).map((r) => `${r.exportName} (${r.location})`).join(", ");
|
|
1967
|
+
const moreRefs = f.referencedBy.length > 2 ? ` +${f.referencedBy.length - 2}` : "";
|
|
1968
|
+
lines.push(`| \`${f.name}\` | ${definedIn} | ${refs}${moreRefs} |`);
|
|
1969
|
+
}
|
|
1970
|
+
if (stats.apiSurface.forgotten.length > limit) {
|
|
1971
|
+
lines.push(`| ... | | ${stats.apiSurface.forgotten.length - limit} more |`);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1839
1974
|
lines.push("");
|
|
1840
1975
|
lines.push("---");
|
|
1841
1976
|
lines.push("*Generated by [DocCov](https://doccov.com)*");
|
|
@@ -2253,7 +2388,9 @@ function computeStats(openpkg, doccov) {
|
|
|
2253
2388
|
exports: sortedExports,
|
|
2254
2389
|
driftIssues,
|
|
2255
2390
|
driftByCategory,
|
|
2256
|
-
driftSummary
|
|
2391
|
+
driftSummary,
|
|
2392
|
+
apiSurface: doccov.apiSurface,
|
|
2393
|
+
health: doccov.summary.health
|
|
2257
2394
|
};
|
|
2258
2395
|
}
|
|
2259
2396
|
// src/reports/writer.ts
|
|
@@ -2295,26 +2432,97 @@ function writeReports(options) {
|
|
|
2295
2432
|
return results;
|
|
2296
2433
|
}
|
|
2297
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
|
+
}
|
|
2298
2497
|
function displayTextOutput(options, deps) {
|
|
2299
2498
|
const {
|
|
2300
2499
|
openpkg,
|
|
2500
|
+
doccov,
|
|
2301
2501
|
coverageScore,
|
|
2302
|
-
|
|
2303
|
-
|
|
2502
|
+
minHealth,
|
|
2503
|
+
minApiSurface,
|
|
2504
|
+
warnBelowApiSurface,
|
|
2304
2505
|
driftExports,
|
|
2305
2506
|
typecheckErrors,
|
|
2306
2507
|
staleRefs,
|
|
2307
2508
|
exampleResult,
|
|
2308
2509
|
specWarnings,
|
|
2309
|
-
specInfos
|
|
2510
|
+
specInfos,
|
|
2511
|
+
verbose
|
|
2310
2512
|
} = options;
|
|
2311
2513
|
const { log } = deps;
|
|
2312
2514
|
const sym = getSymbols(supportsUnicode());
|
|
2515
|
+
const health = doccov.summary.health;
|
|
2516
|
+
const healthScore = health?.score ?? coverageScore;
|
|
2313
2517
|
const totalExportsForDrift = openpkg.exports?.length ?? 0;
|
|
2314
2518
|
const exportsWithDrift = new Set(driftExports.map((d) => d.name)).size;
|
|
2315
2519
|
const driftScore = totalExportsForDrift === 0 ? 0 : Math.round(exportsWithDrift / totalExportsForDrift * 100);
|
|
2316
|
-
const
|
|
2317
|
-
const
|
|
2520
|
+
const apiSurface = doccov.apiSurface;
|
|
2521
|
+
const apiSurfaceScore = apiSurface?.completeness ?? 100;
|
|
2522
|
+
const forgottenCount = apiSurface?.forgotten?.length ?? 0;
|
|
2523
|
+
const healthFailed = healthScore < minHealth;
|
|
2524
|
+
const apiSurfaceFailed = minApiSurface !== undefined && apiSurfaceScore < minApiSurface;
|
|
2525
|
+
const apiSurfaceWarn = warnBelowApiSurface !== undefined && apiSurfaceScore < warnBelowApiSurface && !apiSurfaceFailed;
|
|
2318
2526
|
const hasTypecheckErrors = typecheckErrors.length > 0;
|
|
2319
2527
|
if (specWarnings.length > 0 || specInfos.length > 0) {
|
|
2320
2528
|
log("");
|
|
@@ -2333,31 +2541,35 @@ function displayTextOutput(options, deps) {
|
|
|
2333
2541
|
}
|
|
2334
2542
|
const pkgName = openpkg.meta?.name ?? "unknown";
|
|
2335
2543
|
const pkgVersion = openpkg.meta?.version ?? "";
|
|
2336
|
-
const totalExports = openpkg.exports?.length ?? 0;
|
|
2337
2544
|
log("");
|
|
2338
|
-
log(colors.bold(`${pkgName}${pkgVersion ?
|
|
2545
|
+
log(colors.bold(`${pkgName}${pkgVersion ? ` v${pkgVersion}` : ""}`));
|
|
2339
2546
|
log("");
|
|
2340
|
-
const
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
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
|
+
}
|
|
2345
2557
|
} else {
|
|
2558
|
+
const summaryBuilder = summary({ keyWidth: 10 });
|
|
2559
|
+
summaryBuilder.addKeyValue("Exports", openpkg.exports?.length ?? 0);
|
|
2560
|
+
summaryBuilder.addKeyValue("Coverage", `${coverageScore}%`);
|
|
2346
2561
|
summaryBuilder.addKeyValue("Drift", `${driftScore}%`);
|
|
2562
|
+
summaryBuilder.print();
|
|
2347
2563
|
}
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
}
|
|
2353
|
-
summaryBuilder.addKeyValue("Examples", "validated", "pass");
|
|
2354
|
-
}
|
|
2564
|
+
log("");
|
|
2565
|
+
if (forgottenCount > 0 || minApiSurface !== undefined || warnBelowApiSurface !== undefined) {
|
|
2566
|
+
const surfaceLabel = forgottenCount > 0 ? `${apiSurfaceScore}% (${forgottenCount} forgotten)` : `${apiSurfaceScore}%`;
|
|
2567
|
+
const surfaceColor = apiSurfaceFailed ? colors.error : apiSurfaceWarn || forgottenCount > 0 ? colors.warning : colors.success;
|
|
2568
|
+
log(`${colors.muted("API Surface")} ${surfaceColor(surfaceLabel)}`);
|
|
2355
2569
|
}
|
|
2356
|
-
const hasStaleRefs = staleRefs.length > 0;
|
|
2357
2570
|
if (hasStaleRefs) {
|
|
2358
|
-
|
|
2571
|
+
log(`${colors.muted("Stale refs")} ${colors.warning(`${staleRefs.length} found`)}`);
|
|
2359
2572
|
}
|
|
2360
|
-
summaryBuilder.print();
|
|
2361
2573
|
if (hasTypecheckErrors) {
|
|
2362
2574
|
log("");
|
|
2363
2575
|
for (const err of typecheckErrors.slice(0, 5)) {
|
|
@@ -2378,22 +2590,55 @@ function displayTextOutput(options, deps) {
|
|
|
2378
2590
|
log(colors.muted(` ... and ${staleRefs.length - 5} more`));
|
|
2379
2591
|
}
|
|
2380
2592
|
}
|
|
2593
|
+
if (verbose && forgottenCount > 0 && apiSurface?.forgotten) {
|
|
2594
|
+
log("");
|
|
2595
|
+
log(colors.bold(`Forgotten Exports (${forgottenCount})`));
|
|
2596
|
+
log("");
|
|
2597
|
+
for (const forgotten of apiSurface.forgotten.slice(0, 10)) {
|
|
2598
|
+
log(` ${colors.warning(forgotten.name)}`);
|
|
2599
|
+
if (forgotten.definedIn) {
|
|
2600
|
+
log(colors.muted(` Defined in: ${forgotten.definedIn.file}${forgotten.definedIn.line ? `:${forgotten.definedIn.line}` : ""}`));
|
|
2601
|
+
}
|
|
2602
|
+
if (forgotten.referencedBy.length > 0) {
|
|
2603
|
+
log(colors.muted(" Referenced by:"));
|
|
2604
|
+
for (const ref of forgotten.referencedBy.slice(0, 3)) {
|
|
2605
|
+
log(colors.muted(` - ${ref.exportName} (${ref.location})`));
|
|
2606
|
+
}
|
|
2607
|
+
if (forgotten.referencedBy.length > 3) {
|
|
2608
|
+
log(colors.muted(` ... and ${forgotten.referencedBy.length - 3} more`));
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
if (forgotten.fix) {
|
|
2612
|
+
log(colors.info(` Fix: Add to ${forgotten.fix.targetFile}:`));
|
|
2613
|
+
log(colors.info(` ${forgotten.fix.exportStatement}`));
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
if (apiSurface.forgotten.length > 10) {
|
|
2617
|
+
log(colors.muted(` ... and ${apiSurface.forgotten.length - 10} more`));
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2381
2620
|
log("");
|
|
2382
|
-
const failed =
|
|
2621
|
+
const failed = healthFailed || apiSurfaceFailed || hasTypecheckErrors || hasStaleRefs;
|
|
2383
2622
|
if (!failed) {
|
|
2384
2623
|
const thresholdParts = [];
|
|
2385
|
-
thresholdParts.push(`
|
|
2386
|
-
if (
|
|
2387
|
-
thresholdParts.push(`
|
|
2624
|
+
thresholdParts.push(`health ${healthScore}% ≥ ${minHealth}%`);
|
|
2625
|
+
if (minApiSurface !== undefined) {
|
|
2626
|
+
thresholdParts.push(`api-surface ${apiSurfaceScore}% ≥ ${minApiSurface}%`);
|
|
2388
2627
|
}
|
|
2389
2628
|
log(colors.success(`${sym.success} Check passed (${thresholdParts.join(", ")})`));
|
|
2629
|
+
if (apiSurfaceWarn) {
|
|
2630
|
+
log(colors.warning(`${sym.warning} API Surface ${apiSurfaceScore}% below warning threshold ${warnBelowApiSurface}%`));
|
|
2631
|
+
}
|
|
2632
|
+
if (!verbose && health) {
|
|
2633
|
+
log(colors.muted("Use --verbose for detailed breakdown"));
|
|
2634
|
+
}
|
|
2390
2635
|
return true;
|
|
2391
2636
|
}
|
|
2392
|
-
if (
|
|
2393
|
-
log(colors.error(`${sym.error}
|
|
2637
|
+
if (healthFailed) {
|
|
2638
|
+
log(colors.error(`${sym.error} Health ${healthScore}% below minimum ${minHealth}%`));
|
|
2394
2639
|
}
|
|
2395
|
-
if (
|
|
2396
|
-
log(colors.error(`${sym.error}
|
|
2640
|
+
if (apiSurfaceFailed) {
|
|
2641
|
+
log(colors.error(`${sym.error} API Surface ${apiSurfaceScore}% below minimum ${minApiSurface}%`));
|
|
2397
2642
|
}
|
|
2398
2643
|
if (hasTypecheckErrors) {
|
|
2399
2644
|
log(colors.error(`${sym.error} ${typecheckErrors.length} example type errors`));
|
|
@@ -2401,8 +2646,10 @@ function displayTextOutput(options, deps) {
|
|
|
2401
2646
|
if (hasStaleRefs) {
|
|
2402
2647
|
log(colors.error(`${sym.error} ${staleRefs.length} stale references in docs`));
|
|
2403
2648
|
}
|
|
2404
|
-
|
|
2405
|
-
|
|
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
|
+
}
|
|
2406
2653
|
return false;
|
|
2407
2654
|
}
|
|
2408
2655
|
function handleNonTextOutput(options, deps) {
|
|
@@ -2411,8 +2658,8 @@ function handleNonTextOutput(options, deps) {
|
|
|
2411
2658
|
openpkg,
|
|
2412
2659
|
doccov,
|
|
2413
2660
|
coverageScore,
|
|
2414
|
-
|
|
2415
|
-
|
|
2661
|
+
minHealth,
|
|
2662
|
+
minApiSurface,
|
|
2416
2663
|
driftExports,
|
|
2417
2664
|
typecheckErrors,
|
|
2418
2665
|
limit,
|
|
@@ -2424,6 +2671,7 @@ function handleNonTextOutput(options, deps) {
|
|
|
2424
2671
|
const stats = computeStats(openpkg, doccov);
|
|
2425
2672
|
const report = generateReportFromDocCov(openpkg, doccov);
|
|
2426
2673
|
const jsonContent = JSON.stringify(report, null, 2);
|
|
2674
|
+
const healthScore = doccov.summary.health?.score ?? coverageScore;
|
|
2427
2675
|
let formatContent;
|
|
2428
2676
|
switch (format) {
|
|
2429
2677
|
case "json":
|
|
@@ -2455,13 +2703,57 @@ function handleNonTextOutput(options, deps) {
|
|
|
2455
2703
|
cwd
|
|
2456
2704
|
});
|
|
2457
2705
|
}
|
|
2458
|
-
const
|
|
2459
|
-
const
|
|
2460
|
-
const
|
|
2461
|
-
const coverageFailed = coverageScore < minCoverage;
|
|
2462
|
-
const driftFailed = maxDrift !== undefined && driftScore > maxDrift;
|
|
2706
|
+
const healthFailed = healthScore < minHealth;
|
|
2707
|
+
const apiSurfaceScore = doccov.apiSurface?.completeness ?? 100;
|
|
2708
|
+
const apiSurfaceFailed = minApiSurface !== undefined && apiSurfaceScore < minApiSurface;
|
|
2463
2709
|
const hasTypecheckErrors = typecheckErrors.length > 0;
|
|
2464
|
-
return !(
|
|
2710
|
+
return !(healthFailed || apiSurfaceFailed || hasTypecheckErrors);
|
|
2711
|
+
}
|
|
2712
|
+
function displayApiSurfaceOutput(doccov, deps) {
|
|
2713
|
+
const { log } = deps;
|
|
2714
|
+
const apiSurface = doccov.apiSurface;
|
|
2715
|
+
log("");
|
|
2716
|
+
log(colors.bold("API Surface Analysis"));
|
|
2717
|
+
log("");
|
|
2718
|
+
if (!apiSurface) {
|
|
2719
|
+
log(colors.muted("No API surface data available"));
|
|
2720
|
+
return;
|
|
2721
|
+
}
|
|
2722
|
+
const sym = getSymbols(supportsUnicode());
|
|
2723
|
+
const summaryBuilder = summary({ keyWidth: 12 });
|
|
2724
|
+
summaryBuilder.addKeyValue("Referenced", apiSurface.totalReferenced);
|
|
2725
|
+
summaryBuilder.addKeyValue("Exported", apiSurface.exported);
|
|
2726
|
+
summaryBuilder.addKeyValue("Forgotten", apiSurface.forgotten.length);
|
|
2727
|
+
summaryBuilder.addKeyValue("Completeness", `${apiSurface.completeness}%`, apiSurface.forgotten.length > 0 ? "warn" : "pass");
|
|
2728
|
+
summaryBuilder.print();
|
|
2729
|
+
if (apiSurface.forgotten.length > 0) {
|
|
2730
|
+
log("");
|
|
2731
|
+
log(colors.bold(`Forgotten Exports (${apiSurface.forgotten.length})`));
|
|
2732
|
+
log("");
|
|
2733
|
+
for (const forgotten of apiSurface.forgotten) {
|
|
2734
|
+
log(` ${colors.warning(forgotten.name)}`);
|
|
2735
|
+
if (forgotten.definedIn) {
|
|
2736
|
+
log(colors.muted(` Defined in: ${forgotten.definedIn.file}${forgotten.definedIn.line ? `:${forgotten.definedIn.line}` : ""}`));
|
|
2737
|
+
}
|
|
2738
|
+
if (forgotten.referencedBy.length > 0) {
|
|
2739
|
+
log(colors.muted(" Referenced by:"));
|
|
2740
|
+
for (const ref of forgotten.referencedBy.slice(0, 5)) {
|
|
2741
|
+
log(colors.muted(` - ${ref.exportName} (${ref.location})`));
|
|
2742
|
+
}
|
|
2743
|
+
if (forgotten.referencedBy.length > 5) {
|
|
2744
|
+
log(colors.muted(` ... and ${forgotten.referencedBy.length - 5} more`));
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
if (forgotten.fix) {
|
|
2748
|
+
log(colors.info(` Fix: Add to ${forgotten.fix.targetFile}:`));
|
|
2749
|
+
log(colors.info(` ${forgotten.fix.exportStatement}`));
|
|
2750
|
+
}
|
|
2751
|
+
log("");
|
|
2752
|
+
}
|
|
2753
|
+
} else {
|
|
2754
|
+
log("");
|
|
2755
|
+
log(colors.success(`${sym.success} All referenced types are exported`));
|
|
2756
|
+
}
|
|
2465
2757
|
}
|
|
2466
2758
|
|
|
2467
2759
|
// src/commands/check/validation.ts
|
|
@@ -2546,12 +2838,12 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2546
2838
|
...defaultDependencies,
|
|
2547
2839
|
...dependencies
|
|
2548
2840
|
};
|
|
2549
|
-
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) => {
|
|
2550
2842
|
const n = parseInt(value, 10);
|
|
2551
2843
|
if (Number.isNaN(n) || n < 1)
|
|
2552
2844
|
throw new Error("--max-type-depth must be a positive integer");
|
|
2553
2845
|
return n;
|
|
2554
|
-
}).option("--no-cache", "Bypass spec cache and force regeneration").option("--visibility <tags>", "Filter by release stage: public,beta,alpha,internal (comma-separated)").action(async (entry, options) => {
|
|
2846
|
+
}).option("--no-cache", "Bypass spec cache and force regeneration").option("--visibility <tags>", "Filter by release stage: public,beta,alpha,internal (comma-separated)").option("--min-api-surface <percentage>", "Minimum API surface completeness percentage (0-100)", (value) => Number(value)).option("--api-surface", "Show only API surface / forgotten exports info").option("-v, --verbose", "Show detailed output including forgotten exports").action(async (entry, options) => {
|
|
2555
2847
|
try {
|
|
2556
2848
|
const spin = spinner("Analyzing...");
|
|
2557
2849
|
let validations = parseExamplesFlag(options.examples);
|
|
@@ -2573,11 +2865,30 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2573
2865
|
}
|
|
2574
2866
|
hasExamples = validations.length > 0;
|
|
2575
2867
|
}
|
|
2576
|
-
|
|
2577
|
-
|
|
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;
|
|
2578
2884
|
const minCoverage = clampPercentage(minCoverageRaw);
|
|
2579
2885
|
const maxDriftRaw = options.maxDrift ?? config?.check?.maxDrift;
|
|
2580
2886
|
const maxDrift = maxDriftRaw !== undefined ? clampPercentage(maxDriftRaw) : undefined;
|
|
2887
|
+
const apiSurfaceConfig = config?.check?.apiSurface;
|
|
2888
|
+
const minApiSurfaceRaw = options.minApiSurface ?? apiSurfaceConfig?.minCompleteness ?? config?.check?.minApiSurface;
|
|
2889
|
+
const minApiSurface = minApiSurfaceRaw !== undefined ? clampPercentage(minApiSurfaceRaw) : undefined;
|
|
2890
|
+
const warnBelowApiSurface = apiSurfaceConfig?.warnBelow ? clampPercentage(apiSurfaceConfig.warnBelow) : undefined;
|
|
2891
|
+
const apiSurfaceIgnore = apiSurfaceConfig?.ignore ?? [];
|
|
2581
2892
|
const cliFilters = {
|
|
2582
2893
|
include: undefined,
|
|
2583
2894
|
exclude: undefined,
|
|
@@ -2604,7 +2915,9 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2604
2915
|
const doccov = buildDocCovSpec({
|
|
2605
2916
|
openpkg,
|
|
2606
2917
|
openpkgPath: entryFile,
|
|
2607
|
-
packagePath: targetDir
|
|
2918
|
+
packagePath: targetDir,
|
|
2919
|
+
forgottenExports: specResult.forgottenExports,
|
|
2920
|
+
apiSurfaceIgnore
|
|
2608
2921
|
});
|
|
2609
2922
|
const format = options.format ?? "text";
|
|
2610
2923
|
const specWarnings = specResult.diagnostics.filter((d) => d.severity === "warning");
|
|
@@ -2635,21 +2948,33 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2635
2948
|
const coverageScore = doccov.summary.score;
|
|
2636
2949
|
const allDriftExports = [...collectDrift(openpkg.exports ?? [], doccov), ...runtimeDrifts];
|
|
2637
2950
|
let driftExports = hasExamples ? allDriftExports : allDriftExports.filter((d) => d.category !== "example");
|
|
2951
|
+
const healthScore = doccov.summary.health?.score ?? coverageScore;
|
|
2638
2952
|
if (shouldFix && driftExports.length > 0) {
|
|
2639
|
-
const fixResult = await handleFixes(openpkg, doccov, { isPreview, targetDir }, { log, error });
|
|
2953
|
+
const fixResult = await handleFixes(openpkg, doccov, { isPreview, targetDir, entryFile, healthScore }, { log, error });
|
|
2640
2954
|
if (!isPreview) {
|
|
2641
2955
|
driftExports = driftExports.filter((d) => !fixResult.fixedDriftKeys.has(`${d.name}:${d.issue}`));
|
|
2642
2956
|
}
|
|
2643
2957
|
}
|
|
2958
|
+
if (shouldFix && doccov.apiSurface?.forgotten.length) {
|
|
2959
|
+
await handleForgottenExportFixes(doccov, { isPreview, targetDir, entryFile }, { log, error });
|
|
2960
|
+
}
|
|
2644
2961
|
spin.success("Analysis complete");
|
|
2962
|
+
if (options.apiSurface) {
|
|
2963
|
+
displayApiSurfaceOutput(doccov, { log });
|
|
2964
|
+
const apiSurfaceScore = doccov.apiSurface?.completeness ?? 100;
|
|
2965
|
+
if (minApiSurface !== undefined && apiSurfaceScore < minApiSurface) {
|
|
2966
|
+
process.exit(1);
|
|
2967
|
+
}
|
|
2968
|
+
return;
|
|
2969
|
+
}
|
|
2645
2970
|
if (format !== "text") {
|
|
2646
2971
|
const passed2 = handleNonTextOutput({
|
|
2647
2972
|
format,
|
|
2648
2973
|
openpkg,
|
|
2649
2974
|
doccov,
|
|
2650
2975
|
coverageScore,
|
|
2651
|
-
|
|
2652
|
-
|
|
2976
|
+
minHealth,
|
|
2977
|
+
minApiSurface,
|
|
2653
2978
|
driftExports,
|
|
2654
2979
|
typecheckErrors,
|
|
2655
2980
|
limit: parseInt(options.limit, 10) || 20,
|
|
@@ -2666,14 +2991,16 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2666
2991
|
openpkg,
|
|
2667
2992
|
doccov,
|
|
2668
2993
|
coverageScore,
|
|
2669
|
-
|
|
2670
|
-
|
|
2994
|
+
minHealth,
|
|
2995
|
+
minApiSurface,
|
|
2996
|
+
warnBelowApiSurface,
|
|
2671
2997
|
driftExports,
|
|
2672
2998
|
typecheckErrors,
|
|
2673
2999
|
staleRefs,
|
|
2674
3000
|
exampleResult,
|
|
2675
3001
|
specWarnings,
|
|
2676
|
-
specInfos
|
|
3002
|
+
specInfos,
|
|
3003
|
+
verbose: options.verbose ?? false
|
|
2677
3004
|
}, { log });
|
|
2678
3005
|
if (!passed) {
|
|
2679
3006
|
process.exit(1);
|
|
@@ -3098,7 +3425,8 @@ function registerInfoCommand(program) {
|
|
|
3098
3425
|
const doccov = buildDocCovSpec2({
|
|
3099
3426
|
openpkg,
|
|
3100
3427
|
openpkgPath: entryFile,
|
|
3101
|
-
packagePath: targetDir
|
|
3428
|
+
packagePath: targetDir,
|
|
3429
|
+
forgottenExports: specResult.forgottenExports
|
|
3102
3430
|
});
|
|
3103
3431
|
const stats = computeStats(openpkg, doccov);
|
|
3104
3432
|
spin.success("Analysis complete");
|
|
@@ -3312,12 +3640,11 @@ import {
|
|
|
3312
3640
|
resolveTarget as resolveTarget3
|
|
3313
3641
|
} from "@doccov/sdk";
|
|
3314
3642
|
import { validateDocCovSpec } from "@doccov/spec";
|
|
3315
|
-
import {
|
|
3643
|
+
import {
|
|
3644
|
+
normalize,
|
|
3645
|
+
validateSpec
|
|
3646
|
+
} from "@openpkg-ts/spec";
|
|
3316
3647
|
import chalk11 from "chalk";
|
|
3317
|
-
// package.json
|
|
3318
|
-
var version = "0.25.9";
|
|
3319
|
-
|
|
3320
|
-
// src/commands/spec.ts
|
|
3321
3648
|
var defaultDependencies4 = {
|
|
3322
3649
|
createDocCov: (options) => new DocCov3(options),
|
|
3323
3650
|
writeFileSync: fs6.writeFileSync,
|
|
@@ -3371,24 +3698,13 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
3371
3698
|
cwd: options.cwd,
|
|
3372
3699
|
schemaExtraction: options.runtime ? "hybrid" : "static"
|
|
3373
3700
|
});
|
|
3374
|
-
const generationInput = {
|
|
3375
|
-
entryPoint: path8.relative(targetDir, entryFile),
|
|
3376
|
-
entryPointSource: entryPointInfo.source,
|
|
3377
|
-
isDeclarationOnly: entryPointInfo.isDeclarationOnly ?? false,
|
|
3378
|
-
generatorName: "@doccov/cli",
|
|
3379
|
-
generatorVersion: version,
|
|
3380
|
-
packageManager: packageInfo?.packageManager,
|
|
3381
|
-
isMonorepo: resolved.isMonorepo,
|
|
3382
|
-
targetPackage: packageInfo?.name
|
|
3383
|
-
};
|
|
3384
3701
|
const analyzeOptions = resolvedFilters.include || resolvedFilters.exclude || resolvedFilters.visibility ? {
|
|
3385
3702
|
filters: {
|
|
3386
3703
|
include: resolvedFilters.include,
|
|
3387
3704
|
exclude: resolvedFilters.exclude,
|
|
3388
3705
|
visibility: resolvedFilters.visibility
|
|
3389
|
-
}
|
|
3390
|
-
|
|
3391
|
-
} : { generationInput };
|
|
3706
|
+
}
|
|
3707
|
+
} : {};
|
|
3392
3708
|
const result = await doccov.analyzeFileWithDiagnostics(entryFile, analyzeOptions);
|
|
3393
3709
|
if (!result) {
|
|
3394
3710
|
spin.fail("Generation failed");
|
|
@@ -3409,7 +3725,8 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
3409
3725
|
doccovSpec = buildDocCovSpec3({
|
|
3410
3726
|
openpkgPath: "openpkg.json",
|
|
3411
3727
|
openpkg: normalized,
|
|
3412
|
-
packagePath: targetDir
|
|
3728
|
+
packagePath: targetDir,
|
|
3729
|
+
forgottenExports: result.forgottenExports
|
|
3413
3730
|
});
|
|
3414
3731
|
const doccovValidation = validateDocCovSpec(doccovSpec);
|
|
3415
3732
|
if (!doccovValidation.ok) {
|
|
@@ -3444,7 +3761,11 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
3444
3761
|
log(chalk11.gray(` ${getArrayLength(normalized.types)} types`));
|
|
3445
3762
|
}
|
|
3446
3763
|
}
|
|
3447
|
-
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;
|
|
3448
3769
|
if (options.runtime && (!schemaExtraction?.runtimeCount || schemaExtraction.runtimeCount === 0)) {
|
|
3449
3770
|
const pm = await detectPackageManager(fileSystem);
|
|
3450
3771
|
const buildCmd = pm.name === "npm" ? "npm run build" : `${pm.name} run build`;
|
|
@@ -3452,21 +3773,20 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
3452
3773
|
log(chalk11.yellow("⚠ Runtime extraction requested but no schemas extracted."));
|
|
3453
3774
|
log(chalk11.yellow(` Ensure project is built (${buildCmd}) and dist/ exists.`));
|
|
3454
3775
|
}
|
|
3455
|
-
if (options.verbose &&
|
|
3456
|
-
const gen = normalized.generation;
|
|
3776
|
+
if (options.verbose && fullGen) {
|
|
3457
3777
|
log("");
|
|
3458
3778
|
log(chalk11.bold("Generation Info"));
|
|
3459
|
-
log(chalk11.gray(` Timestamp: ${
|
|
3460
|
-
log(chalk11.gray(` Generator: ${
|
|
3461
|
-
log(chalk11.gray(` Entry point: ${
|
|
3462
|
-
log(chalk11.gray(` Detected via: ${
|
|
3463
|
-
log(chalk11.gray(` Declaration only: ${
|
|
3464
|
-
log(chalk11.gray(` External types: ${
|
|
3465
|
-
if (
|
|
3466
|
-
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}`));
|
|
3467
3787
|
}
|
|
3468
|
-
if (
|
|
3469
|
-
const se =
|
|
3788
|
+
if (fullGen.analysis.schemaExtraction) {
|
|
3789
|
+
const se = fullGen.analysis.schemaExtraction;
|
|
3470
3790
|
log(chalk11.gray(` Schema extraction: ${se.method}`));
|
|
3471
3791
|
if (se.runtimeCount) {
|
|
3472
3792
|
log(chalk11.gray(` Runtime schemas: ${se.runtimeCount} (${se.vendors?.join(", ")})`));
|
|
@@ -3474,20 +3794,20 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
3474
3794
|
}
|
|
3475
3795
|
log("");
|
|
3476
3796
|
log(chalk11.bold("Environment"));
|
|
3477
|
-
log(chalk11.gray(` node_modules: ${
|
|
3478
|
-
if (
|
|
3479
|
-
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}`));
|
|
3480
3800
|
}
|
|
3481
|
-
if (
|
|
3801
|
+
if (fullGen.environment.isMonorepo) {
|
|
3482
3802
|
log(chalk11.gray(` Monorepo: yes`));
|
|
3483
3803
|
}
|
|
3484
|
-
if (
|
|
3485
|
-
log(chalk11.gray(` Target package: ${
|
|
3804
|
+
if (fullGen.environment.targetPackage) {
|
|
3805
|
+
log(chalk11.gray(` Target package: ${fullGen.environment.targetPackage}`));
|
|
3486
3806
|
}
|
|
3487
|
-
if (
|
|
3807
|
+
if (fullGen.issues.length > 0) {
|
|
3488
3808
|
log("");
|
|
3489
3809
|
log(chalk11.bold("Issues"));
|
|
3490
|
-
for (const issue of
|
|
3810
|
+
for (const issue of fullGen.issues) {
|
|
3491
3811
|
const prefix2 = issue.severity === "error" ? chalk11.red(">") : issue.severity === "warning" ? chalk11.yellow(">") : chalk11.cyan(">");
|
|
3492
3812
|
log(`${prefix2} [${issue.code}] ${issue.message}`);
|
|
3493
3813
|
if (issue.suggestion) {
|
|
@@ -3548,9 +3868,9 @@ function getColorForScore(score) {
|
|
|
3548
3868
|
function formatSnapshot(snapshot) {
|
|
3549
3869
|
const color = getColorForScore(snapshot.coverageScore);
|
|
3550
3870
|
const date = formatDate(snapshot.timestamp);
|
|
3551
|
-
const
|
|
3871
|
+
const version = snapshot.version ? ` v${snapshot.version}` : "";
|
|
3552
3872
|
const commit = snapshot.commit ? chalk12.gray(` (${snapshot.commit.slice(0, 7)})`) : "";
|
|
3553
|
-
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}`;
|
|
3554
3874
|
}
|
|
3555
3875
|
function formatWeekDate(timestamp) {
|
|
3556
3876
|
const date = new Date(timestamp);
|
package/dist/config/index.d.ts
CHANGED
|
@@ -13,12 +13,23 @@ declare const exampleModeSchema: z.ZodEnum<["presence", "typecheck", "run"]>;
|
|
|
13
13
|
/** Example validation modes - can be single, array, or comma-separated */
|
|
14
14
|
declare const exampleModesSchema: z.ZodUnion<[typeof exampleModeSchema, z.ZodArray<typeof exampleModeSchema>, z.ZodString]>;
|
|
15
15
|
/**
|
|
16
|
+
* API surface configuration schema.
|
|
17
|
+
*/
|
|
18
|
+
declare const apiSurfaceConfigSchema: z.ZodObject<{
|
|
19
|
+
minCompleteness: z.ZodOptional<z.ZodNumber>;
|
|
20
|
+
warnBelow: z.ZodOptional<z.ZodNumber>;
|
|
21
|
+
ignore: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
16
24
|
* Check command configuration schema.
|
|
17
25
|
*/
|
|
18
26
|
declare const checkConfigSchema: z.ZodObject<{
|
|
19
27
|
examples: z.ZodOptional<typeof exampleModesSchema>;
|
|
28
|
+
minHealth: z.ZodOptional<z.ZodNumber>;
|
|
20
29
|
minCoverage: z.ZodOptional<z.ZodNumber>;
|
|
21
30
|
maxDrift: z.ZodOptional<z.ZodNumber>;
|
|
31
|
+
minApiSurface: z.ZodOptional<z.ZodNumber>;
|
|
32
|
+
apiSurface: z.ZodOptional<typeof apiSurfaceConfigSchema>;
|
|
22
33
|
}>;
|
|
23
34
|
declare const docCovConfigSchema: z.ZodObject<{
|
|
24
35
|
include: z.ZodOptional<typeof stringList>;
|
package/dist/config/index.js
CHANGED
|
@@ -23,10 +23,18 @@ var exampleModesSchema = z.union([
|
|
|
23
23
|
z.array(exampleModeSchema),
|
|
24
24
|
z.string()
|
|
25
25
|
]);
|
|
26
|
+
var apiSurfaceConfigSchema = z.object({
|
|
27
|
+
minCompleteness: z.number().min(0).max(100).optional(),
|
|
28
|
+
warnBelow: z.number().min(0).max(100).optional(),
|
|
29
|
+
ignore: z.array(z.string()).optional()
|
|
30
|
+
});
|
|
26
31
|
var checkConfigSchema = z.object({
|
|
27
32
|
examples: exampleModesSchema.optional(),
|
|
33
|
+
minHealth: z.number().min(0).max(100).optional(),
|
|
28
34
|
minCoverage: z.number().min(0).max(100).optional(),
|
|
29
|
-
maxDrift: z.number().min(0).max(100).optional()
|
|
35
|
+
maxDrift: z.number().min(0).max(100).optional(),
|
|
36
|
+
minApiSurface: z.number().min(0).max(100).optional(),
|
|
37
|
+
apiSurface: apiSurfaceConfigSchema.optional()
|
|
30
38
|
});
|
|
31
39
|
var docCovConfigSchema = z.object({
|
|
32
40
|
include: stringList.optional(),
|
|
@@ -61,8 +69,11 @@ var normalizeConfig = (input) => {
|
|
|
61
69
|
if (input.check) {
|
|
62
70
|
check = {
|
|
63
71
|
examples: input.check.examples,
|
|
72
|
+
minHealth: input.check.minHealth,
|
|
64
73
|
minCoverage: input.check.minCoverage,
|
|
65
|
-
maxDrift: input.check.maxDrift
|
|
74
|
+
maxDrift: input.check.maxDrift,
|
|
75
|
+
minApiSurface: input.check.minApiSurface,
|
|
76
|
+
apiSurface: input.check.apiSurface
|
|
66
77
|
};
|
|
67
78
|
}
|
|
68
79
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doccov/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
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",
|