@doccov/cli 0.25.11 → 0.26.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 +251 -28
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.js +11 -2
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -25,10 +25,17 @@ 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(),
|
|
30
35
|
minCoverage: z.number().min(0).max(100).optional(),
|
|
31
|
-
maxDrift: z.number().min(0).max(100).optional()
|
|
36
|
+
maxDrift: z.number().min(0).max(100).optional(),
|
|
37
|
+
minApiSurface: z.number().min(0).max(100).optional(),
|
|
38
|
+
apiSurface: apiSurfaceConfigSchema.optional()
|
|
32
39
|
});
|
|
33
40
|
var docCovConfigSchema = z.object({
|
|
34
41
|
include: stringList.optional(),
|
|
@@ -64,7 +71,9 @@ var normalizeConfig = (input) => {
|
|
|
64
71
|
check = {
|
|
65
72
|
examples: input.check.examples,
|
|
66
73
|
minCoverage: input.check.minCoverage,
|
|
67
|
-
maxDrift: input.check.maxDrift
|
|
74
|
+
maxDrift: input.check.maxDrift,
|
|
75
|
+
minApiSurface: input.check.minApiSurface,
|
|
76
|
+
apiSurface: input.check.apiSurface
|
|
68
77
|
};
|
|
69
78
|
}
|
|
70
79
|
return {
|
|
@@ -151,6 +160,16 @@ import * as path10 from "node:path";
|
|
|
151
160
|
import { fileURLToPath } from "node:url";
|
|
152
161
|
import { Command } from "commander";
|
|
153
162
|
|
|
163
|
+
// src/commands/check/index.ts
|
|
164
|
+
import {
|
|
165
|
+
buildDocCovSpec,
|
|
166
|
+
DocCov,
|
|
167
|
+
NodeFileSystem,
|
|
168
|
+
parseExamplesFlag,
|
|
169
|
+
resolveTarget
|
|
170
|
+
} from "@doccov/sdk";
|
|
171
|
+
import chalk7 from "chalk";
|
|
172
|
+
|
|
154
173
|
// ../cli-utils/dist/index.js
|
|
155
174
|
import chalk from "chalk";
|
|
156
175
|
import chalk2 from "chalk";
|
|
@@ -1141,16 +1160,6 @@ function summary(options) {
|
|
|
1141
1160
|
return new Summary(options);
|
|
1142
1161
|
}
|
|
1143
1162
|
|
|
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
1163
|
// src/utils/filter-options.ts
|
|
1155
1164
|
import { mergeFilters, parseListFlag } from "@doccov/sdk";
|
|
1156
1165
|
import chalk3 from "chalk";
|
|
@@ -1218,12 +1227,15 @@ import * as fs2 from "node:fs";
|
|
|
1218
1227
|
import * as path3 from "node:path";
|
|
1219
1228
|
import {
|
|
1220
1229
|
applyEdits,
|
|
1230
|
+
applyForgottenExportFixes,
|
|
1221
1231
|
categorizeDrifts,
|
|
1222
1232
|
createSourceFile,
|
|
1223
1233
|
findJSDocLocation,
|
|
1224
1234
|
generateFixesForExport,
|
|
1235
|
+
generateForgottenExportFixes,
|
|
1225
1236
|
mergeFixes,
|
|
1226
1237
|
parseJSDocToPatch,
|
|
1238
|
+
previewForgottenExportFixes,
|
|
1227
1239
|
serializeJSDoc
|
|
1228
1240
|
} from "@doccov/sdk";
|
|
1229
1241
|
import chalk5 from "chalk";
|
|
@@ -1296,12 +1308,12 @@ async function handleFixes(openpkg, doccov, options, deps) {
|
|
|
1296
1308
|
const fixedDriftKeys = new Set;
|
|
1297
1309
|
const allDrifts = collectDriftsFromExports(openpkg.exports ?? [], doccov);
|
|
1298
1310
|
if (allDrifts.length === 0) {
|
|
1299
|
-
return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
|
|
1311
|
+
return { fixedDriftKeys, editsApplied: 0, filesModified: 0, forgottenExportsFixed: 0 };
|
|
1300
1312
|
}
|
|
1301
1313
|
const { fixable, nonFixable } = categorizeDrifts(allDrifts.map((d) => d.drift));
|
|
1302
1314
|
if (fixable.length === 0) {
|
|
1303
1315
|
log(chalk5.yellow(`Found ${nonFixable.length} drift issue(s), but none are auto-fixable.`));
|
|
1304
|
-
return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
|
|
1316
|
+
return { fixedDriftKeys, editsApplied: 0, filesModified: 0, forgottenExportsFixed: 0 };
|
|
1305
1317
|
}
|
|
1306
1318
|
log("");
|
|
1307
1319
|
log(chalk5.bold(`Found ${fixable.length} fixable issue(s)`));
|
|
@@ -1330,11 +1342,11 @@ async function handleFixes(openpkg, doccov, options, deps) {
|
|
|
1330
1342
|
editsByFile.set(edit.filePath, fileEdits);
|
|
1331
1343
|
}
|
|
1332
1344
|
if (edits.length === 0) {
|
|
1333
|
-
return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
|
|
1345
|
+
return { fixedDriftKeys, editsApplied: 0, filesModified: 0, forgottenExportsFixed: 0 };
|
|
1334
1346
|
}
|
|
1335
1347
|
if (isPreview) {
|
|
1336
1348
|
displayPreview(editsByFile, targetDir, log);
|
|
1337
|
-
return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
|
|
1349
|
+
return { fixedDriftKeys, editsApplied: 0, filesModified: 0, forgottenExportsFixed: 0 };
|
|
1338
1350
|
}
|
|
1339
1351
|
const applyResult = await applyEdits(edits);
|
|
1340
1352
|
if (applyResult.errors.length > 0) {
|
|
@@ -1353,9 +1365,71 @@ async function handleFixes(openpkg, doccov, options, deps) {
|
|
|
1353
1365
|
return {
|
|
1354
1366
|
fixedDriftKeys,
|
|
1355
1367
|
editsApplied: totalFixes,
|
|
1356
|
-
filesModified: applyResult.filesModified
|
|
1368
|
+
filesModified: applyResult.filesModified,
|
|
1369
|
+
forgottenExportsFixed: 0
|
|
1357
1370
|
};
|
|
1358
1371
|
}
|
|
1372
|
+
async function handleForgottenExportFixes(doccov, options, deps) {
|
|
1373
|
+
const { isPreview, targetDir, entryFile } = options;
|
|
1374
|
+
const { log, error } = deps;
|
|
1375
|
+
if (!doccov.apiSurface || doccov.apiSurface.forgotten.length === 0) {
|
|
1376
|
+
return { fixesApplied: 0, filesModified: 0 };
|
|
1377
|
+
}
|
|
1378
|
+
const fixable = doccov.apiSurface.forgotten.filter((f) => !f.isExternal && f.fix);
|
|
1379
|
+
if (fixable.length === 0) {
|
|
1380
|
+
return { fixesApplied: 0, filesModified: 0 };
|
|
1381
|
+
}
|
|
1382
|
+
const fixes = generateForgottenExportFixes(doccov.apiSurface, {
|
|
1383
|
+
baseDir: targetDir,
|
|
1384
|
+
entryFile: entryFile ?? "src/index.ts"
|
|
1385
|
+
});
|
|
1386
|
+
if (fixes.length === 0) {
|
|
1387
|
+
return { fixesApplied: 0, filesModified: 0 };
|
|
1388
|
+
}
|
|
1389
|
+
log("");
|
|
1390
|
+
log(chalk5.bold(`Found ${fixes.length} forgotten export(s) to fix`));
|
|
1391
|
+
if (isPreview) {
|
|
1392
|
+
displayForgottenExportPreview(fixes, targetDir, log);
|
|
1393
|
+
return { fixesApplied: 0, filesModified: 0 };
|
|
1394
|
+
}
|
|
1395
|
+
const result = await applyForgottenExportFixes(fixes);
|
|
1396
|
+
if (result.errors.length > 0) {
|
|
1397
|
+
for (const err of result.errors) {
|
|
1398
|
+
error(chalk5.red(` ${err.file}: ${err.error}`));
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
if (result.fixesApplied > 0) {
|
|
1402
|
+
log("");
|
|
1403
|
+
log(chalk5.green(`✓ Added ${result.fixesApplied} export(s) to ${result.filesModified} file(s)`));
|
|
1404
|
+
const grouped = new Map;
|
|
1405
|
+
for (const fix of fixes) {
|
|
1406
|
+
const relativePath = path3.relative(targetDir, fix.targetFile);
|
|
1407
|
+
const types = grouped.get(relativePath) ?? [];
|
|
1408
|
+
types.push(fix.typeName);
|
|
1409
|
+
grouped.set(relativePath, types);
|
|
1410
|
+
}
|
|
1411
|
+
for (const [file, types] of grouped) {
|
|
1412
|
+
log(chalk5.dim(` ${file}: ${types.join(", ")}`));
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
return { fixesApplied: result.fixesApplied, filesModified: result.filesModified };
|
|
1416
|
+
}
|
|
1417
|
+
function displayForgottenExportPreview(fixes, targetDir, log) {
|
|
1418
|
+
log(chalk5.bold("Preview - forgotten exports that would be added:"));
|
|
1419
|
+
log("");
|
|
1420
|
+
const previews = previewForgottenExportFixes(fixes);
|
|
1421
|
+
for (const [filePath, preview] of previews) {
|
|
1422
|
+
const relativePath = path3.relative(targetDir, filePath);
|
|
1423
|
+
log(chalk5.cyan(`${relativePath}:${preview.insertLine + 1}`));
|
|
1424
|
+
log("");
|
|
1425
|
+
for (const stmt of preview.statements) {
|
|
1426
|
+
log(chalk5.green(` + ${stmt}`));
|
|
1427
|
+
}
|
|
1428
|
+
log("");
|
|
1429
|
+
}
|
|
1430
|
+
log(chalk5.yellow(`${fixes.length} export(s) would be added.`));
|
|
1431
|
+
log(chalk5.gray("Run with --fix to apply these changes."));
|
|
1432
|
+
}
|
|
1359
1433
|
function generateEditForExport(exp, drifts, targetDir, log) {
|
|
1360
1434
|
if (!exp.source?.file) {
|
|
1361
1435
|
log(chalk5.gray(` Skipping ${exp.name}: no source location`));
|
|
@@ -1836,6 +1910,24 @@ function renderMarkdown(stats, options = {}) {
|
|
|
1836
1910
|
lines.push("");
|
|
1837
1911
|
}
|
|
1838
1912
|
}
|
|
1913
|
+
if (stats.apiSurface && stats.apiSurface.forgotten.length > 0) {
|
|
1914
|
+
lines.push("");
|
|
1915
|
+
lines.push("## API Surface");
|
|
1916
|
+
lines.push("");
|
|
1917
|
+
lines.push(`**${stats.apiSurface.completeness}% complete** (${stats.apiSurface.forgotten.length} forgotten exports)`);
|
|
1918
|
+
lines.push("");
|
|
1919
|
+
lines.push("| Type | Defined In | Referenced By |");
|
|
1920
|
+
lines.push("|------|------------|---------------|");
|
|
1921
|
+
for (const f of stats.apiSurface.forgotten.slice(0, limit)) {
|
|
1922
|
+
const definedIn = f.definedIn ? `${f.definedIn.file}${f.definedIn.line ? `:${f.definedIn.line}` : ""}` : "-";
|
|
1923
|
+
const refs = f.referencedBy.slice(0, 2).map((r) => `${r.exportName} (${r.location})`).join(", ");
|
|
1924
|
+
const moreRefs = f.referencedBy.length > 2 ? ` +${f.referencedBy.length - 2}` : "";
|
|
1925
|
+
lines.push(`| \`${f.name}\` | ${definedIn} | ${refs}${moreRefs} |`);
|
|
1926
|
+
}
|
|
1927
|
+
if (stats.apiSurface.forgotten.length > limit) {
|
|
1928
|
+
lines.push(`| ... | | ${stats.apiSurface.forgotten.length - limit} more |`);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1839
1931
|
lines.push("");
|
|
1840
1932
|
lines.push("---");
|
|
1841
1933
|
lines.push("*Generated by [DocCov](https://doccov.com)*");
|
|
@@ -2253,7 +2345,8 @@ function computeStats(openpkg, doccov) {
|
|
|
2253
2345
|
exports: sortedExports,
|
|
2254
2346
|
driftIssues,
|
|
2255
2347
|
driftByCategory,
|
|
2256
|
-
driftSummary
|
|
2348
|
+
driftSummary,
|
|
2349
|
+
apiSurface: doccov.apiSurface
|
|
2257
2350
|
};
|
|
2258
2351
|
}
|
|
2259
2352
|
// src/reports/writer.ts
|
|
@@ -2298,23 +2391,32 @@ function writeReports(options) {
|
|
|
2298
2391
|
function displayTextOutput(options, deps) {
|
|
2299
2392
|
const {
|
|
2300
2393
|
openpkg,
|
|
2394
|
+
doccov,
|
|
2301
2395
|
coverageScore,
|
|
2302
2396
|
minCoverage,
|
|
2303
2397
|
maxDrift,
|
|
2398
|
+
minApiSurface,
|
|
2399
|
+
warnBelowApiSurface,
|
|
2304
2400
|
driftExports,
|
|
2305
2401
|
typecheckErrors,
|
|
2306
2402
|
staleRefs,
|
|
2307
2403
|
exampleResult,
|
|
2308
2404
|
specWarnings,
|
|
2309
|
-
specInfos
|
|
2405
|
+
specInfos,
|
|
2406
|
+
verbose
|
|
2310
2407
|
} = options;
|
|
2311
2408
|
const { log } = deps;
|
|
2312
2409
|
const sym = getSymbols(supportsUnicode());
|
|
2313
2410
|
const totalExportsForDrift = openpkg.exports?.length ?? 0;
|
|
2314
2411
|
const exportsWithDrift = new Set(driftExports.map((d) => d.name)).size;
|
|
2315
2412
|
const driftScore = totalExportsForDrift === 0 ? 0 : Math.round(exportsWithDrift / totalExportsForDrift * 100);
|
|
2413
|
+
const apiSurface = doccov.apiSurface;
|
|
2414
|
+
const apiSurfaceScore = apiSurface?.completeness ?? 100;
|
|
2415
|
+
const forgottenCount = apiSurface?.forgotten?.length ?? 0;
|
|
2316
2416
|
const coverageFailed = coverageScore < minCoverage;
|
|
2317
2417
|
const driftFailed = maxDrift !== undefined && driftScore > maxDrift;
|
|
2418
|
+
const apiSurfaceFailed = minApiSurface !== undefined && apiSurfaceScore < minApiSurface;
|
|
2419
|
+
const apiSurfaceWarn = warnBelowApiSurface !== undefined && apiSurfaceScore < warnBelowApiSurface && !apiSurfaceFailed;
|
|
2318
2420
|
const hasTypecheckErrors = typecheckErrors.length > 0;
|
|
2319
2421
|
if (specWarnings.length > 0 || specInfos.length > 0) {
|
|
2320
2422
|
log("");
|
|
@@ -2345,6 +2447,18 @@ function displayTextOutput(options, deps) {
|
|
|
2345
2447
|
} else {
|
|
2346
2448
|
summaryBuilder.addKeyValue("Drift", `${driftScore}%`);
|
|
2347
2449
|
}
|
|
2450
|
+
if (forgottenCount > 0 || minApiSurface !== undefined || warnBelowApiSurface !== undefined) {
|
|
2451
|
+
const surfaceLabel = forgottenCount > 0 ? `${apiSurfaceScore}% (${forgottenCount} forgotten)` : `${apiSurfaceScore}%`;
|
|
2452
|
+
if (apiSurfaceFailed) {
|
|
2453
|
+
summaryBuilder.addKeyValue("API Surface", surfaceLabel, "fail");
|
|
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
|
+
}
|
|
2348
2462
|
if (exampleResult) {
|
|
2349
2463
|
const typecheckCount = exampleResult.typecheck?.errors.length ?? 0;
|
|
2350
2464
|
if (typecheckCount > 0) {
|
|
@@ -2378,15 +2492,48 @@ function displayTextOutput(options, deps) {
|
|
|
2378
2492
|
log(colors.muted(` ... and ${staleRefs.length - 5} more`));
|
|
2379
2493
|
}
|
|
2380
2494
|
}
|
|
2495
|
+
if (verbose && forgottenCount > 0 && apiSurface?.forgotten) {
|
|
2496
|
+
log("");
|
|
2497
|
+
log(colors.bold(`Forgotten Exports (${forgottenCount})`));
|
|
2498
|
+
log("");
|
|
2499
|
+
for (const forgotten of apiSurface.forgotten.slice(0, 10)) {
|
|
2500
|
+
log(` ${colors.warning(forgotten.name)}`);
|
|
2501
|
+
if (forgotten.definedIn) {
|
|
2502
|
+
log(colors.muted(` Defined in: ${forgotten.definedIn.file}${forgotten.definedIn.line ? `:${forgotten.definedIn.line}` : ""}`));
|
|
2503
|
+
}
|
|
2504
|
+
if (forgotten.referencedBy.length > 0) {
|
|
2505
|
+
log(colors.muted(" Referenced by:"));
|
|
2506
|
+
for (const ref of forgotten.referencedBy.slice(0, 3)) {
|
|
2507
|
+
log(colors.muted(` - ${ref.exportName} (${ref.location})`));
|
|
2508
|
+
}
|
|
2509
|
+
if (forgotten.referencedBy.length > 3) {
|
|
2510
|
+
log(colors.muted(` ... and ${forgotten.referencedBy.length - 3} more`));
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
if (forgotten.fix) {
|
|
2514
|
+
log(colors.info(` Fix: Add to ${forgotten.fix.targetFile}:`));
|
|
2515
|
+
log(colors.info(` ${forgotten.fix.exportStatement}`));
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
if (apiSurface.forgotten.length > 10) {
|
|
2519
|
+
log(colors.muted(` ... and ${apiSurface.forgotten.length - 10} more`));
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2381
2522
|
log("");
|
|
2382
|
-
const failed = coverageFailed || driftFailed || hasTypecheckErrors || hasStaleRefs;
|
|
2523
|
+
const failed = coverageFailed || driftFailed || apiSurfaceFailed || hasTypecheckErrors || hasStaleRefs;
|
|
2383
2524
|
if (!failed) {
|
|
2384
2525
|
const thresholdParts = [];
|
|
2385
2526
|
thresholdParts.push(`coverage ${coverageScore}% ≥ ${minCoverage}%`);
|
|
2386
2527
|
if (maxDrift !== undefined) {
|
|
2387
2528
|
thresholdParts.push(`drift ${driftScore}% ≤ ${maxDrift}%`);
|
|
2388
2529
|
}
|
|
2530
|
+
if (minApiSurface !== undefined) {
|
|
2531
|
+
thresholdParts.push(`api-surface ${apiSurfaceScore}% ≥ ${minApiSurface}%`);
|
|
2532
|
+
}
|
|
2389
2533
|
log(colors.success(`${sym.success} Check passed (${thresholdParts.join(", ")})`));
|
|
2534
|
+
if (apiSurfaceWarn) {
|
|
2535
|
+
log(colors.warning(`${sym.warning} API Surface ${apiSurfaceScore}% below warning threshold ${warnBelowApiSurface}%`));
|
|
2536
|
+
}
|
|
2390
2537
|
return true;
|
|
2391
2538
|
}
|
|
2392
2539
|
if (coverageFailed) {
|
|
@@ -2395,6 +2542,9 @@ function displayTextOutput(options, deps) {
|
|
|
2395
2542
|
if (driftFailed) {
|
|
2396
2543
|
log(colors.error(`${sym.error} Drift ${driftScore}% exceeds maximum ${maxDrift}%`));
|
|
2397
2544
|
}
|
|
2545
|
+
if (apiSurfaceFailed) {
|
|
2546
|
+
log(colors.error(`${sym.error} API Surface ${apiSurfaceScore}% below minimum ${minApiSurface}%`));
|
|
2547
|
+
}
|
|
2398
2548
|
if (hasTypecheckErrors) {
|
|
2399
2549
|
log(colors.error(`${sym.error} ${typecheckErrors.length} example type errors`));
|
|
2400
2550
|
}
|
|
@@ -2413,6 +2563,7 @@ function handleNonTextOutput(options, deps) {
|
|
|
2413
2563
|
coverageScore,
|
|
2414
2564
|
minCoverage,
|
|
2415
2565
|
maxDrift,
|
|
2566
|
+
minApiSurface,
|
|
2416
2567
|
driftExports,
|
|
2417
2568
|
typecheckErrors,
|
|
2418
2569
|
limit,
|
|
@@ -2460,8 +2611,56 @@ function handleNonTextOutput(options, deps) {
|
|
|
2460
2611
|
const driftScore = totalExportsForDrift === 0 ? 0 : Math.round(exportsWithDrift / totalExportsForDrift * 100);
|
|
2461
2612
|
const coverageFailed = coverageScore < minCoverage;
|
|
2462
2613
|
const driftFailed = maxDrift !== undefined && driftScore > maxDrift;
|
|
2614
|
+
const apiSurfaceScore = doccov.apiSurface?.completeness ?? 100;
|
|
2615
|
+
const apiSurfaceFailed = minApiSurface !== undefined && apiSurfaceScore < minApiSurface;
|
|
2463
2616
|
const hasTypecheckErrors = typecheckErrors.length > 0;
|
|
2464
|
-
return !(coverageFailed || driftFailed || hasTypecheckErrors);
|
|
2617
|
+
return !(coverageFailed || driftFailed || apiSurfaceFailed || hasTypecheckErrors);
|
|
2618
|
+
}
|
|
2619
|
+
function displayApiSurfaceOutput(doccov, deps) {
|
|
2620
|
+
const { log } = deps;
|
|
2621
|
+
const apiSurface = doccov.apiSurface;
|
|
2622
|
+
log("");
|
|
2623
|
+
log(colors.bold("API Surface Analysis"));
|
|
2624
|
+
log("");
|
|
2625
|
+
if (!apiSurface) {
|
|
2626
|
+
log(colors.muted("No API surface data available"));
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
const sym = getSymbols(supportsUnicode());
|
|
2630
|
+
const summaryBuilder = summary({ keyWidth: 12 });
|
|
2631
|
+
summaryBuilder.addKeyValue("Referenced", apiSurface.totalReferenced);
|
|
2632
|
+
summaryBuilder.addKeyValue("Exported", apiSurface.exported);
|
|
2633
|
+
summaryBuilder.addKeyValue("Forgotten", apiSurface.forgotten.length);
|
|
2634
|
+
summaryBuilder.addKeyValue("Completeness", `${apiSurface.completeness}%`, apiSurface.forgotten.length > 0 ? "warn" : "pass");
|
|
2635
|
+
summaryBuilder.print();
|
|
2636
|
+
if (apiSurface.forgotten.length > 0) {
|
|
2637
|
+
log("");
|
|
2638
|
+
log(colors.bold(`Forgotten Exports (${apiSurface.forgotten.length})`));
|
|
2639
|
+
log("");
|
|
2640
|
+
for (const forgotten of apiSurface.forgotten) {
|
|
2641
|
+
log(` ${colors.warning(forgotten.name)}`);
|
|
2642
|
+
if (forgotten.definedIn) {
|
|
2643
|
+
log(colors.muted(` Defined in: ${forgotten.definedIn.file}${forgotten.definedIn.line ? `:${forgotten.definedIn.line}` : ""}`));
|
|
2644
|
+
}
|
|
2645
|
+
if (forgotten.referencedBy.length > 0) {
|
|
2646
|
+
log(colors.muted(" Referenced by:"));
|
|
2647
|
+
for (const ref of forgotten.referencedBy.slice(0, 5)) {
|
|
2648
|
+
log(colors.muted(` - ${ref.exportName} (${ref.location})`));
|
|
2649
|
+
}
|
|
2650
|
+
if (forgotten.referencedBy.length > 5) {
|
|
2651
|
+
log(colors.muted(` ... and ${forgotten.referencedBy.length - 5} more`));
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
if (forgotten.fix) {
|
|
2655
|
+
log(colors.info(` Fix: Add to ${forgotten.fix.targetFile}:`));
|
|
2656
|
+
log(colors.info(` ${forgotten.fix.exportStatement}`));
|
|
2657
|
+
}
|
|
2658
|
+
log("");
|
|
2659
|
+
}
|
|
2660
|
+
} else {
|
|
2661
|
+
log("");
|
|
2662
|
+
log(colors.success(`${sym.success} All referenced types are exported`));
|
|
2663
|
+
}
|
|
2465
2664
|
}
|
|
2466
2665
|
|
|
2467
2666
|
// src/commands/check/validation.ts
|
|
@@ -2551,7 +2750,7 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2551
2750
|
if (Number.isNaN(n) || n < 1)
|
|
2552
2751
|
throw new Error("--max-type-depth must be a positive integer");
|
|
2553
2752
|
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) => {
|
|
2753
|
+
}).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
2754
|
try {
|
|
2556
2755
|
const spin = spinner("Analyzing...");
|
|
2557
2756
|
let validations = parseExamplesFlag(options.examples);
|
|
@@ -2578,6 +2777,11 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2578
2777
|
const minCoverage = clampPercentage(minCoverageRaw);
|
|
2579
2778
|
const maxDriftRaw = options.maxDrift ?? config?.check?.maxDrift;
|
|
2580
2779
|
const maxDrift = maxDriftRaw !== undefined ? clampPercentage(maxDriftRaw) : undefined;
|
|
2780
|
+
const apiSurfaceConfig = config?.check?.apiSurface;
|
|
2781
|
+
const minApiSurfaceRaw = options.minApiSurface ?? apiSurfaceConfig?.minCompleteness ?? config?.check?.minApiSurface;
|
|
2782
|
+
const minApiSurface = minApiSurfaceRaw !== undefined ? clampPercentage(minApiSurfaceRaw) : undefined;
|
|
2783
|
+
const warnBelowApiSurface = apiSurfaceConfig?.warnBelow ? clampPercentage(apiSurfaceConfig.warnBelow) : undefined;
|
|
2784
|
+
const apiSurfaceIgnore = apiSurfaceConfig?.ignore ?? [];
|
|
2581
2785
|
const cliFilters = {
|
|
2582
2786
|
include: undefined,
|
|
2583
2787
|
exclude: undefined,
|
|
@@ -2604,7 +2808,9 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2604
2808
|
const doccov = buildDocCovSpec({
|
|
2605
2809
|
openpkg,
|
|
2606
2810
|
openpkgPath: entryFile,
|
|
2607
|
-
packagePath: targetDir
|
|
2811
|
+
packagePath: targetDir,
|
|
2812
|
+
forgottenExports: specResult.forgottenExports,
|
|
2813
|
+
apiSurfaceIgnore
|
|
2608
2814
|
});
|
|
2609
2815
|
const format = options.format ?? "text";
|
|
2610
2816
|
const specWarnings = specResult.diagnostics.filter((d) => d.severity === "warning");
|
|
@@ -2636,12 +2842,23 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2636
2842
|
const allDriftExports = [...collectDrift(openpkg.exports ?? [], doccov), ...runtimeDrifts];
|
|
2637
2843
|
let driftExports = hasExamples ? allDriftExports : allDriftExports.filter((d) => d.category !== "example");
|
|
2638
2844
|
if (shouldFix && driftExports.length > 0) {
|
|
2639
|
-
const fixResult = await handleFixes(openpkg, doccov, { isPreview, targetDir }, { log, error });
|
|
2845
|
+
const fixResult = await handleFixes(openpkg, doccov, { isPreview, targetDir, entryFile }, { log, error });
|
|
2640
2846
|
if (!isPreview) {
|
|
2641
2847
|
driftExports = driftExports.filter((d) => !fixResult.fixedDriftKeys.has(`${d.name}:${d.issue}`));
|
|
2642
2848
|
}
|
|
2643
2849
|
}
|
|
2850
|
+
if (shouldFix && doccov.apiSurface?.forgotten.length) {
|
|
2851
|
+
await handleForgottenExportFixes(doccov, { isPreview, targetDir, entryFile }, { log, error });
|
|
2852
|
+
}
|
|
2644
2853
|
spin.success("Analysis complete");
|
|
2854
|
+
if (options.apiSurface) {
|
|
2855
|
+
displayApiSurfaceOutput(doccov, { log });
|
|
2856
|
+
const apiSurfaceScore = doccov.apiSurface?.completeness ?? 100;
|
|
2857
|
+
if (minApiSurface !== undefined && apiSurfaceScore < minApiSurface) {
|
|
2858
|
+
process.exit(1);
|
|
2859
|
+
}
|
|
2860
|
+
return;
|
|
2861
|
+
}
|
|
2645
2862
|
if (format !== "text") {
|
|
2646
2863
|
const passed2 = handleNonTextOutput({
|
|
2647
2864
|
format,
|
|
@@ -2650,6 +2867,7 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2650
2867
|
coverageScore,
|
|
2651
2868
|
minCoverage,
|
|
2652
2869
|
maxDrift,
|
|
2870
|
+
minApiSurface,
|
|
2653
2871
|
driftExports,
|
|
2654
2872
|
typecheckErrors,
|
|
2655
2873
|
limit: parseInt(options.limit, 10) || 20,
|
|
@@ -2668,12 +2886,15 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2668
2886
|
coverageScore,
|
|
2669
2887
|
minCoverage,
|
|
2670
2888
|
maxDrift,
|
|
2889
|
+
minApiSurface,
|
|
2890
|
+
warnBelowApiSurface,
|
|
2671
2891
|
driftExports,
|
|
2672
2892
|
typecheckErrors,
|
|
2673
2893
|
staleRefs,
|
|
2674
2894
|
exampleResult,
|
|
2675
2895
|
specWarnings,
|
|
2676
|
-
specInfos
|
|
2896
|
+
specInfos,
|
|
2897
|
+
verbose: options.verbose ?? false
|
|
2677
2898
|
}, { log });
|
|
2678
2899
|
if (!passed) {
|
|
2679
2900
|
process.exit(1);
|
|
@@ -3098,7 +3319,8 @@ function registerInfoCommand(program) {
|
|
|
3098
3319
|
const doccov = buildDocCovSpec2({
|
|
3099
3320
|
openpkg,
|
|
3100
3321
|
openpkgPath: entryFile,
|
|
3101
|
-
packagePath: targetDir
|
|
3322
|
+
packagePath: targetDir,
|
|
3323
|
+
forgottenExports: specResult.forgottenExports
|
|
3102
3324
|
});
|
|
3103
3325
|
const stats = computeStats(openpkg, doccov);
|
|
3104
3326
|
spin.success("Analysis complete");
|
|
@@ -3315,7 +3537,7 @@ import { validateDocCovSpec } from "@doccov/spec";
|
|
|
3315
3537
|
import { normalize, validateSpec } from "@openpkg-ts/spec";
|
|
3316
3538
|
import chalk11 from "chalk";
|
|
3317
3539
|
// package.json
|
|
3318
|
-
var version = "0.25.
|
|
3540
|
+
var version = "0.25.11";
|
|
3319
3541
|
|
|
3320
3542
|
// src/commands/spec.ts
|
|
3321
3543
|
var defaultDependencies4 = {
|
|
@@ -3409,7 +3631,8 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
3409
3631
|
doccovSpec = buildDocCovSpec3({
|
|
3410
3632
|
openpkgPath: "openpkg.json",
|
|
3411
3633
|
openpkg: normalized,
|
|
3412
|
-
packagePath: targetDir
|
|
3634
|
+
packagePath: targetDir,
|
|
3635
|
+
forgottenExports: result.forgottenExports
|
|
3413
3636
|
});
|
|
3414
3637
|
const doccovValidation = validateDocCovSpec(doccovSpec);
|
|
3415
3638
|
if (!doccovValidation.ok) {
|
package/dist/config/index.d.ts
CHANGED
|
@@ -13,12 +13,22 @@ 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>;
|
|
20
28
|
minCoverage: z.ZodOptional<z.ZodNumber>;
|
|
21
29
|
maxDrift: z.ZodOptional<z.ZodNumber>;
|
|
30
|
+
minApiSurface: z.ZodOptional<z.ZodNumber>;
|
|
31
|
+
apiSurface: z.ZodOptional<typeof apiSurfaceConfigSchema>;
|
|
22
32
|
}>;
|
|
23
33
|
declare const docCovConfigSchema: z.ZodObject<{
|
|
24
34
|
include: z.ZodOptional<typeof stringList>;
|
package/dist/config/index.js
CHANGED
|
@@ -23,10 +23,17 @@ 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(),
|
|
28
33
|
minCoverage: z.number().min(0).max(100).optional(),
|
|
29
|
-
maxDrift: z.number().min(0).max(100).optional()
|
|
34
|
+
maxDrift: z.number().min(0).max(100).optional(),
|
|
35
|
+
minApiSurface: z.number().min(0).max(100).optional(),
|
|
36
|
+
apiSurface: apiSurfaceConfigSchema.optional()
|
|
30
37
|
});
|
|
31
38
|
var docCovConfigSchema = z.object({
|
|
32
39
|
include: stringList.optional(),
|
|
@@ -62,7 +69,9 @@ var normalizeConfig = (input) => {
|
|
|
62
69
|
check = {
|
|
63
70
|
examples: input.check.examples,
|
|
64
71
|
minCoverage: input.check.minCoverage,
|
|
65
|
-
maxDrift: input.check.maxDrift
|
|
72
|
+
maxDrift: input.check.maxDrift,
|
|
73
|
+
minApiSurface: input.check.minApiSurface,
|
|
74
|
+
apiSurface: input.check.apiSurface
|
|
66
75
|
};
|
|
67
76
|
}
|
|
68
77
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doccov/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.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.26.0",
|
|
52
|
+
"@doccov/spec": "^0.26.0",
|
|
53
53
|
"@inquirer/prompts": "^7.8.0",
|
|
54
54
|
"@openpkg-ts/spec": "^0.12.0",
|
|
55
55
|
"ai": "^4.0.0",
|