@doccov/cli 0.24.1 → 0.25.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.
Files changed (2) hide show
  1. package/dist/cli.js +94 -81
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -153,8 +153,8 @@ import { Command } from "commander";
153
153
 
154
154
  // src/commands/check/index.ts
155
155
  import {
156
+ buildDocCovSpec,
156
157
  DocCov,
157
- enrichSpec,
158
158
  NodeFileSystem,
159
159
  parseExamplesFlag,
160
160
  resolveTarget
@@ -292,15 +292,13 @@ import chalk3 from "chalk";
292
292
  // src/commands/check/utils.ts
293
293
  import * as fs from "node:fs";
294
294
  import * as path2 from "node:path";
295
- import {
296
- DRIFT_CATEGORIES,
297
- parseMarkdownFiles
298
- } from "@doccov/sdk";
295
+ import { getExportDrift, parseMarkdownFiles } from "@doccov/sdk";
296
+ import { DRIFT_CATEGORIES } from "@doccov/spec";
299
297
  import { glob } from "glob";
300
- function collectDriftsFromExports(exports) {
298
+ function collectDriftsFromExports(exports, doccov) {
301
299
  const results = [];
302
300
  for (const exp of exports) {
303
- for (const drift of exp.docs?.drift ?? []) {
301
+ for (const drift of getExportDrift(exp, doccov)) {
304
302
  results.push({ export: exp, drift });
305
303
  }
306
304
  }
@@ -315,16 +313,16 @@ function groupByExport(drifts) {
315
313
  }
316
314
  return map;
317
315
  }
318
- function collectDrift(exportsList) {
316
+ function collectDrift(exports, doccov) {
319
317
  const drifts = [];
320
- for (const entry of exportsList) {
321
- const drift = entry.docs?.drift;
322
- if (!drift || drift.length === 0) {
318
+ for (const exp of exports) {
319
+ const exportDrifts = getExportDrift(exp, doccov);
320
+ if (exportDrifts.length === 0) {
323
321
  continue;
324
322
  }
325
- for (const d of drift) {
323
+ for (const d of exportDrifts) {
326
324
  drifts.push({
327
- name: entry.name,
325
+ name: exp.name,
328
326
  type: d.type,
329
327
  issue: d.issue ?? "Documentation drift detected.",
330
328
  suggestion: d.suggestion,
@@ -353,11 +351,11 @@ async function loadMarkdownFiles(patterns, cwd) {
353
351
  }
354
352
 
355
353
  // src/commands/check/fix-handler.ts
356
- async function handleFixes(spec, options, deps) {
354
+ async function handleFixes(openpkg, doccov, options, deps) {
357
355
  const { isPreview, targetDir } = options;
358
356
  const { log, error } = deps;
359
357
  const fixedDriftKeys = new Set;
360
- const allDrifts = collectDriftsFromExports(spec.exports ?? []);
358
+ const allDrifts = collectDriftsFromExports(openpkg.exports ?? [], doccov);
361
359
  if (allDrifts.length === 0) {
362
360
  return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
363
361
  }
@@ -443,8 +441,7 @@ function generateEditForExport(exp, drifts, targetDir, log) {
443
441
  if (location.hasExisting && location.existingJSDoc) {
444
442
  existingPatch = parseJSDocToPatch(location.existingJSDoc);
445
443
  }
446
- const expWithDrift = { ...exp, docs: { ...exp.docs, drift: drifts } };
447
- const fixes = generateFixesForExport(expWithDrift, existingPatch);
444
+ const fixes = generateFixesForExport(exp, existingPatch, drifts);
448
445
  if (fixes.length === 0)
449
446
  return null;
450
447
  const mergedPatch = mergeFixes(fixes, existingPatch);
@@ -499,7 +496,7 @@ function displayPreview(editsByFile, targetDir, log) {
499
496
  }
500
497
 
501
498
  // src/commands/check/output.ts
502
- import { generateReport } from "@doccov/sdk";
499
+ import { generateReportFromDocCov } from "@doccov/sdk";
503
500
  import chalk5 from "chalk";
504
501
 
505
502
  // src/reports/changelog-renderer.ts
@@ -937,6 +934,7 @@ function renderHtml(stats, options = {}) {
937
934
  </html>`;
938
935
  }
939
936
  // src/reports/pr-comment.ts
937
+ import { getExportDrift as getExportDrift3 } from "@doccov/sdk";
940
938
  function extractReturnType(schema) {
941
939
  if (!schema)
942
940
  return "void";
@@ -954,7 +952,7 @@ function extractReturnType(schema) {
954
952
  return "unknown";
955
953
  }
956
954
  function renderPRComment(data, opts = {}) {
957
- const { diff, headSpec } = data;
955
+ const { diff, headSpec, doccov } = data;
958
956
  const limit = opts.limit ?? 10;
959
957
  const lines = [];
960
958
  const hasStaleRefs = (opts.staleDocsRefs?.length ?? 0) > 0;
@@ -987,7 +985,7 @@ function renderPRComment(data, opts = {}) {
987
985
  lines.push("");
988
986
  lines.push("### Doc drift detected");
989
987
  lines.push("");
990
- renderDriftIssues(lines, diff.newUndocumented, headSpec, opts, limit);
988
+ renderDriftIssues(lines, diff.newUndocumented, headSpec, doccov, opts, limit);
991
989
  }
992
990
  if (opts.staleDocsRefs && opts.staleDocsRefs.length > 0) {
993
991
  lines.push("");
@@ -1069,18 +1067,16 @@ function renderUndocumentedExports(lines, undocumented, headSpec, opts, limit) {
1069
1067
  lines.push(`_...and ${undocumented.length - count} more undocumented exports_`);
1070
1068
  }
1071
1069
  }
1072
- function renderDriftIssues(lines, _undocumented, headSpec, opts, limit) {
1070
+ function renderDriftIssues(lines, _undocumented, headSpec, doccov, opts, limit) {
1073
1071
  const driftIssues = [];
1074
1072
  for (const exp of headSpec.exports) {
1075
- const drifts = exp.docs?.drift;
1076
- if (drifts) {
1077
- for (const d of drifts) {
1078
- driftIssues.push({
1079
- exportName: exp.name,
1080
- file: exp.source?.file,
1081
- drift: d
1082
- });
1083
- }
1073
+ const drifts = doccov ? getExportDrift3(exp, doccov) : [];
1074
+ for (const d of drifts) {
1075
+ driftIssues.push({
1076
+ exportName: exp.name,
1077
+ file: exp.source?.file,
1078
+ drift: d
1079
+ });
1084
1080
  }
1085
1081
  }
1086
1082
  if (driftIssues.length === 0) {
@@ -1222,12 +1218,10 @@ function renderDetailsTable(lines, diff) {
1222
1218
  }
1223
1219
  }
1224
1220
  // src/reports/stats.ts
1225
- import {
1226
- DRIFT_CATEGORIES as DRIFT_CATEGORIES2,
1227
- isFixableDrift
1228
- } from "@doccov/sdk";
1229
- function computeStats(spec) {
1230
- const exports = spec.exports ?? [];
1221
+ import { getExportAnalysis, getExportDrift as getExportDrift4, isFixableDrift } from "@doccov/sdk";
1222
+ import { DRIFT_CATEGORIES as DRIFT_CATEGORIES2 } from "@doccov/spec";
1223
+ function computeStats(openpkg, doccov) {
1224
+ const exports = openpkg.exports ?? [];
1231
1225
  const signals = {
1232
1226
  description: { covered: 0, total: 0 },
1233
1227
  params: { covered: 0, total: 0 },
@@ -1245,8 +1239,9 @@ function computeStats(spec) {
1245
1239
  let partiallyDocumented = 0;
1246
1240
  let undocumented = 0;
1247
1241
  for (const exp of exports) {
1248
- const score = exp.docs?.coverageScore ?? 0;
1249
- const missing = exp.docs?.missing ?? [];
1242
+ const analysis = getExportAnalysis(exp, doccov);
1243
+ const score = analysis?.coverageScore ?? 0;
1244
+ const missing = analysis?.missing ?? [];
1250
1245
  for (const sig of ["description", "params", "returns", "examples"]) {
1251
1246
  signals[sig].total++;
1252
1247
  if (!missing.includes(sig))
@@ -1262,7 +1257,7 @@ function computeStats(spec) {
1262
1257
  partiallyDocumented++;
1263
1258
  else
1264
1259
  undocumented++;
1265
- for (const d of exp.docs?.drift ?? []) {
1260
+ for (const d of getExportDrift4(exp, doccov)) {
1266
1261
  const item = {
1267
1262
  exportName: exp.name,
1268
1263
  type: d.type,
@@ -1283,12 +1278,15 @@ function computeStats(spec) {
1283
1278
  count,
1284
1279
  avgScore: Math.round(totalScore / count)
1285
1280
  })).sort((a, b) => b.count - a.count);
1286
- const sortedExports = exports.map((e) => ({
1287
- name: e.name,
1288
- kind: e.kind,
1289
- score: e.docs?.coverageScore ?? 0,
1290
- missing: e.docs?.missing ?? []
1291
- })).sort((a, b) => a.score - b.score);
1281
+ const sortedExports = exports.map((e) => {
1282
+ const analysis = getExportAnalysis(e, doccov);
1283
+ return {
1284
+ name: e.name,
1285
+ kind: e.kind,
1286
+ score: analysis?.coverageScore ?? 0,
1287
+ missing: analysis?.missing ?? []
1288
+ };
1289
+ }).sort((a, b) => a.score - b.score);
1292
1290
  const driftSummary = {
1293
1291
  total: driftIssues.length,
1294
1292
  byCategory: {
@@ -1301,9 +1299,9 @@ function computeStats(spec) {
1301
1299
  const exportsWithDrift = new Set(driftIssues.map((d) => d.exportName)).size;
1302
1300
  const driftScore = exports.length === 0 ? 0 : Math.round(exportsWithDrift / exports.length * 100);
1303
1301
  return {
1304
- packageName: spec.meta.name ?? "unknown",
1305
- version: spec.meta.version ?? "0.0.0",
1306
- coverageScore: spec.docs?.coverageScore ?? 0,
1302
+ packageName: openpkg.meta.name ?? "unknown",
1303
+ version: openpkg.meta.version ?? "0.0.0",
1304
+ coverageScore: doccov.summary.score,
1307
1305
  driftScore,
1308
1306
  totalExports: exports.length,
1309
1307
  fullyDocumented,
@@ -1359,7 +1357,8 @@ function writeReports(options) {
1359
1357
  // src/commands/check/output.ts
1360
1358
  function displayTextOutput(options, deps) {
1361
1359
  const {
1362
- spec,
1360
+ openpkg,
1361
+ doccov,
1363
1362
  coverageScore,
1364
1363
  minCoverage,
1365
1364
  maxDrift,
@@ -1371,7 +1370,7 @@ function displayTextOutput(options, deps) {
1371
1370
  specInfos
1372
1371
  } = options;
1373
1372
  const { log } = deps;
1374
- const totalExportsForDrift = spec.exports?.length ?? 0;
1373
+ const totalExportsForDrift = openpkg.exports?.length ?? 0;
1375
1374
  const exportsWithDrift = new Set(driftExports.map((d) => d.name)).size;
1376
1375
  const driftScore = totalExportsForDrift === 0 ? 0 : Math.round(exportsWithDrift / totalExportsForDrift * 100);
1377
1376
  const coverageFailed = coverageScore < minCoverage;
@@ -1392,9 +1391,9 @@ function displayTextOutput(options, deps) {
1392
1391
  }
1393
1392
  }
1394
1393
  }
1395
- const pkgName = spec.meta?.name ?? "unknown";
1396
- const pkgVersion = spec.meta?.version ?? "";
1397
- const totalExports = spec.exports?.length ?? 0;
1394
+ const pkgName = openpkg.meta?.name ?? "unknown";
1395
+ const pkgVersion = openpkg.meta?.version ?? "";
1396
+ const totalExports = openpkg.exports?.length ?? 0;
1398
1397
  log("");
1399
1398
  log(chalk5.bold(`${pkgName}${pkgVersion ? `@${pkgVersion}` : ""}`));
1400
1399
  log("");
@@ -1463,8 +1462,8 @@ function displayTextOutput(options, deps) {
1463
1462
  function handleNonTextOutput(options, deps) {
1464
1463
  const {
1465
1464
  format,
1466
- spec,
1467
- rawSpec,
1465
+ openpkg,
1466
+ doccov,
1468
1467
  coverageScore,
1469
1468
  minCoverage,
1470
1469
  maxDrift,
@@ -1476,8 +1475,8 @@ function handleNonTextOutput(options, deps) {
1476
1475
  cwd
1477
1476
  } = options;
1478
1477
  const { log } = deps;
1479
- const stats = computeStats(spec);
1480
- const report = generateReport(rawSpec);
1478
+ const stats = computeStats(openpkg, doccov);
1479
+ const report = generateReportFromDocCov(openpkg, doccov);
1481
1480
  const jsonContent = JSON.stringify(report, null, 2);
1482
1481
  let formatContent;
1483
1482
  switch (format) {
@@ -1510,7 +1509,7 @@ function handleNonTextOutput(options, deps) {
1510
1509
  cwd
1511
1510
  });
1512
1511
  }
1513
- const totalExportsForDrift = spec.exports?.length ?? 0;
1512
+ const totalExportsForDrift = openpkg.exports?.length ?? 0;
1514
1513
  const exportsWithDrift = new Set(driftExports.map((d) => d.name)).size;
1515
1514
  const driftScore = totalExportsForDrift === 0 ? 0 : Math.round(exportsWithDrift / totalExportsForDrift * 100);
1516
1515
  const coverageFailed = coverageScore < minCoverage;
@@ -1521,14 +1520,14 @@ function handleNonTextOutput(options, deps) {
1521
1520
 
1522
1521
  // src/commands/check/validation.ts
1523
1522
  import { validateExamples } from "@doccov/sdk";
1524
- async function runExampleValidation(spec, options) {
1523
+ async function runExampleValidation(openpkg, options) {
1525
1524
  const { validations, targetDir, timeout = 5000, installTimeout = 60000 } = options;
1526
1525
  const typecheckErrors = [];
1527
1526
  const runtimeDrifts = [];
1528
- const result = await validateExamples(spec.exports ?? [], {
1527
+ const result = await validateExamples(openpkg.exports ?? [], {
1529
1528
  validations,
1530
1529
  packagePath: targetDir,
1531
- exportNames: (spec.exports ?? []).map((e) => e.name),
1530
+ exportNames: (openpkg.exports ?? []).map((e) => e.name),
1532
1531
  timeout,
1533
1532
  installTimeout
1534
1533
  });
@@ -1654,19 +1653,24 @@ function registerCheckCommand(program, dependencies = {}) {
1654
1653
  }
1655
1654
  steps.next();
1656
1655
  const resolveExternalTypes = !options.skipResolve;
1657
- const doccov = createDocCov({
1656
+ const analyzer = createDocCov({
1658
1657
  resolveExternalTypes,
1659
1658
  maxDepth: options.maxTypeDepth,
1660
1659
  useCache: options.cache !== false,
1661
1660
  cwd: options.cwd
1662
1661
  });
1663
1662
  const analyzeOptions = resolvedFilters.visibility ? { filters: { visibility: resolvedFilters.visibility } } : {};
1664
- const specResult = await doccov.analyzeFileWithDiagnostics(entryFile, analyzeOptions);
1663
+ const specResult = await analyzer.analyzeFileWithDiagnostics(entryFile, analyzeOptions);
1665
1664
  if (!specResult) {
1666
1665
  throw new Error("Failed to analyze documentation coverage.");
1667
1666
  }
1668
1667
  steps.next();
1669
- const spec = enrichSpec(specResult.spec);
1668
+ const openpkg = specResult.spec;
1669
+ const doccov = buildDocCovSpec({
1670
+ openpkg,
1671
+ openpkgPath: entryFile,
1672
+ packagePath: targetDir
1673
+ });
1670
1674
  const format = options.format ?? "text";
1671
1675
  steps.next();
1672
1676
  const specWarnings = specResult.diagnostics.filter((d) => d.severity === "warning");
@@ -1677,7 +1681,7 @@ function registerCheckCommand(program, dependencies = {}) {
1677
1681
  let typecheckErrors = [];
1678
1682
  let runtimeDrifts = [];
1679
1683
  if (hasExamples) {
1680
- const validation = await runExampleValidation(spec, {
1684
+ const validation = await runExampleValidation(openpkg, {
1681
1685
  validations,
1682
1686
  targetDir
1683
1687
  });
@@ -1693,13 +1697,16 @@ function registerCheckCommand(program, dependencies = {}) {
1693
1697
  const staleRefs = await validateMarkdownDocs({
1694
1698
  docsPatterns,
1695
1699
  targetDir,
1696
- exportNames: (spec.exports ?? []).map((e) => e.name)
1700
+ exportNames: (openpkg.exports ?? []).map((e) => e.name)
1697
1701
  });
1698
- const coverageScore = spec.docs?.coverageScore ?? 0;
1699
- const allDriftExports = [...collectDrift(spec.exports ?? []), ...runtimeDrifts];
1702
+ const coverageScore = doccov.summary.score;
1703
+ const allDriftExports = [
1704
+ ...collectDrift(openpkg.exports ?? [], doccov),
1705
+ ...runtimeDrifts
1706
+ ];
1700
1707
  let driftExports = hasExamples ? allDriftExports : allDriftExports.filter((d) => d.category !== "example");
1701
1708
  if (shouldFix && driftExports.length > 0) {
1702
- const fixResult = await handleFixes(spec, { isPreview, targetDir }, { log, error });
1709
+ const fixResult = await handleFixes(openpkg, doccov, { isPreview, targetDir }, { log, error });
1703
1710
  if (!isPreview) {
1704
1711
  driftExports = driftExports.filter((d) => !fixResult.fixedDriftKeys.has(`${d.name}:${d.issue}`));
1705
1712
  }
@@ -1708,8 +1715,8 @@ function registerCheckCommand(program, dependencies = {}) {
1708
1715
  if (format !== "text") {
1709
1716
  const passed2 = handleNonTextOutput({
1710
1717
  format,
1711
- spec,
1712
- rawSpec: specResult.spec,
1718
+ openpkg,
1719
+ doccov,
1713
1720
  coverageScore,
1714
1721
  minCoverage,
1715
1722
  maxDrift,
@@ -1726,7 +1733,8 @@ function registerCheckCommand(program, dependencies = {}) {
1726
1733
  return;
1727
1734
  }
1728
1735
  const passed = displayTextOutput({
1729
- spec,
1736
+ openpkg,
1737
+ doccov,
1730
1738
  coverageScore,
1731
1739
  minCoverage,
1732
1740
  maxDrift,
@@ -2131,7 +2139,7 @@ function printGitHubAnnotations(diff, log) {
2131
2139
  }
2132
2140
 
2133
2141
  // src/commands/info.ts
2134
- import { DocCov as DocCov2, enrichSpec as enrichSpec2, NodeFileSystem as NodeFileSystem2, resolveTarget as resolveTarget2 } from "@doccov/sdk";
2142
+ import { buildDocCovSpec as buildDocCovSpec2, DocCov as DocCov2, NodeFileSystem as NodeFileSystem2, resolveTarget as resolveTarget2 } from "@doccov/sdk";
2135
2143
  import chalk8 from "chalk";
2136
2144
  function registerInfoCommand(program) {
2137
2145
  program.command("info [entry]").description("Show brief documentation coverage summary").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--skip-resolve", "Skip external type resolution from node_modules").action(async (entry, options) => {
@@ -2142,17 +2150,22 @@ function registerInfoCommand(program) {
2142
2150
  package: options.package,
2143
2151
  entry
2144
2152
  });
2145
- const { entryFile } = resolved;
2153
+ const { entryFile, targetDir } = resolved;
2146
2154
  const resolveExternalTypes = !options.skipResolve;
2147
- const doccov = new DocCov2({
2155
+ const analyzer = new DocCov2({
2148
2156
  resolveExternalTypes
2149
2157
  });
2150
- const specResult = await doccov.analyzeFileWithDiagnostics(entryFile);
2158
+ const specResult = await analyzer.analyzeFileWithDiagnostics(entryFile);
2151
2159
  if (!specResult) {
2152
2160
  throw new Error("Failed to analyze documentation coverage.");
2153
2161
  }
2154
- const spec = enrichSpec2(specResult.spec);
2155
- const stats = computeStats(spec);
2162
+ const openpkg = specResult.spec;
2163
+ const doccov = buildDocCovSpec2({
2164
+ openpkg,
2165
+ openpkgPath: entryFile,
2166
+ packagePath: targetDir
2167
+ });
2168
+ const stats = computeStats(openpkg, doccov);
2156
2169
  console.log("");
2157
2170
  console.log(chalk8.bold(`${stats.packageName}@${stats.version}`));
2158
2171
  console.log("");
@@ -2353,7 +2366,7 @@ var detectRepoInfo = (cwd, fileExists2, readFileSync4) => {
2353
2366
  import * as fs6 from "node:fs";
2354
2367
  import * as path8 from "node:path";
2355
2368
  import {
2356
- buildDocCovSpec,
2369
+ buildDocCovSpec as buildDocCovSpec3,
2357
2370
  DocCov as DocCov3,
2358
2371
  detectPackageManager,
2359
2372
  NodeFileSystem as NodeFileSystem3,
@@ -2364,7 +2377,7 @@ import { validateDocCovSpec } from "@doccov/spec";
2364
2377
  import { normalize, validateSpec } from "@openpkg-ts/spec";
2365
2378
  import chalk10 from "chalk";
2366
2379
  // package.json
2367
- var version = "0.24.0";
2380
+ var version = "0.24.1";
2368
2381
 
2369
2382
  // src/commands/spec.ts
2370
2383
  var defaultDependencies4 = {
@@ -2467,7 +2480,7 @@ function registerSpecCommand(program, dependencies = {}) {
2467
2480
  steps.next();
2468
2481
  let doccovSpec = null;
2469
2482
  if (!options.openpkgOnly) {
2470
- doccovSpec = buildDocCovSpec({
2483
+ doccovSpec = buildDocCovSpec3({
2471
2484
  openpkgPath: "openpkg.json",
2472
2485
  openpkg: normalized,
2473
2486
  packagePath: targetDir
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doccov/cli",
3
- "version": "0.24.1",
3
+ "version": "0.25.0",
4
4
  "description": "DocCov CLI - Documentation coverage and drift detection for TypeScript",
5
5
  "keywords": [
6
6
  "typescript",
@@ -48,7 +48,7 @@
48
48
  "dependencies": {
49
49
  "@ai-sdk/anthropic": "^1.0.0",
50
50
  "@ai-sdk/openai": "^1.0.0",
51
- "@doccov/sdk": "^0.24.1",
51
+ "@doccov/sdk": "^0.25.0",
52
52
  "@doccov/spec": "^0.24.1",
53
53
  "@inquirer/prompts": "^7.8.0",
54
54
  "@openpkg-ts/spec": "^0.11.1",