@doccov/cli 0.6.0 → 0.7.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 CHANGED
@@ -168,8 +168,8 @@ ${formatIssues(issues)}`);
168
168
  // src/config/index.ts
169
169
  var defineConfig = (config) => config;
170
170
  // src/cli.ts
171
- import { readFileSync as readFileSync5 } from "node:fs";
172
- import * as path11 from "node:path";
171
+ import { readFileSync as readFileSync4 } from "node:fs";
172
+ import * as path8 from "node:path";
173
173
  import { fileURLToPath } from "node:url";
174
174
  import { Command } from "commander";
175
175
 
@@ -181,12 +181,9 @@ import {
181
181
  categorizeDrifts,
182
182
  createSourceFile,
183
183
  DocCov,
184
- detectEntryPoint,
185
184
  detectExampleAssertionFailures,
186
185
  detectExampleRuntimeErrors,
187
- detectMonorepo,
188
186
  findJSDocLocation,
189
- findPackageByName,
190
187
  generateFixesForExport,
191
188
  getDefaultConfig as getLintDefaultConfig,
192
189
  hasNonAssertionComments,
@@ -195,6 +192,7 @@ import {
195
192
  NodeFileSystem,
196
193
  parseAssertions,
197
194
  parseJSDocToPatch,
195
+ resolveTarget,
198
196
  runExamplesWithPackage,
199
197
  serializeJSDoc,
200
198
  typecheckExamples
@@ -284,12 +282,6 @@ function collectDriftsFromExports(exports) {
284
282
  }
285
283
  return results;
286
284
  }
287
- function filterDriftsByType(drifts, onlyTypes) {
288
- if (!onlyTypes)
289
- return drifts;
290
- const allowedTypes = new Set(onlyTypes.split(",").map((t) => t.trim()));
291
- return drifts.filter((d) => allowedTypes.has(d.drift.type));
292
- }
293
285
  function groupByExport(drifts) {
294
286
  const map = new Map;
295
287
  for (const { export: exp, drift } of drifts) {
@@ -304,38 +296,20 @@ function registerCheckCommand(program, dependencies = {}) {
304
296
  ...defaultDependencies,
305
297
  ...dependencies
306
298
  };
307
- program.command("check [entry]").description("Fail if documentation coverage falls below a threshold").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--min-coverage <percentage>", "Minimum docs coverage percentage (0-100)", (value) => Number(value)).option("--require-examples", "Require at least one @example for every export").option("--exec", "Execute @example blocks at runtime").option("--no-lint", "Skip lint checks").option("--no-typecheck", "Skip example type checking").option("--ignore-drift", "Do not fail on documentation drift").option("--skip-resolve", "Skip external type resolution from node_modules").option("--fix", "Auto-fix drift and lint issues").option("--write", "Alias for --fix").option("--only <types>", "Only fix specific drift types (comma-separated)").option("--dry-run", "Preview fixes without writing (requires --fix)").action(async (entry, options) => {
299
+ program.command("check [entry]").description("Fail if documentation coverage falls below a threshold").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--min-coverage <percentage>", "Minimum docs coverage percentage (0-100)", (value) => Number(value)).option("--require-examples", "Require at least one @example for every export").option("--exec", "Execute @example blocks at runtime").option("--no-lint", "Skip lint checks").option("--no-typecheck", "Skip example type checking").option("--ignore-drift", "Do not fail on documentation drift").option("--skip-resolve", "Skip external type resolution from node_modules").option("--fix", "Auto-fix drift and lint issues").option("--write", "Alias for --fix").option("--dry-run", "Preview fixes without writing (requires --fix)").action(async (entry, options) => {
308
300
  try {
309
- let targetDir = options.cwd;
310
- let entryFile = entry;
311
301
  const fileSystem = new NodeFileSystem(options.cwd);
312
- if (options.package) {
313
- const mono = await detectMonorepo(fileSystem);
314
- if (!mono.isMonorepo) {
315
- throw new Error(`Not a monorepo. Remove --package flag for single-package repos.`);
316
- }
317
- const pkg = findPackageByName(mono.packages, options.package);
318
- if (!pkg) {
319
- const available = mono.packages.map((p) => p.name).join(", ");
320
- throw new Error(`Package "${options.package}" not found. Available: ${available}`);
321
- }
322
- targetDir = path2.join(options.cwd, pkg.path);
323
- log(chalk.gray(`Found package at ${pkg.path}`));
302
+ const resolved = await resolveTarget(fileSystem, {
303
+ cwd: options.cwd,
304
+ package: options.package,
305
+ entry
306
+ });
307
+ const { targetDir, entryFile, packageInfo, entryPointInfo } = resolved;
308
+ if (packageInfo) {
309
+ log(chalk.gray(`Found package at ${packageInfo.path}`));
324
310
  }
325
- if (!entryFile) {
326
- const targetFs = new NodeFileSystem(targetDir);
327
- const detected = await detectEntryPoint(targetFs);
328
- entryFile = path2.join(targetDir, detected.path);
329
- log(chalk.gray(`Auto-detected entry point: ${detected.path} (from ${detected.source})`));
330
- } else {
331
- entryFile = path2.resolve(targetDir, entryFile);
332
- if (fs.existsSync(entryFile) && fs.statSync(entryFile).isDirectory()) {
333
- targetDir = entryFile;
334
- const dirFs = new NodeFileSystem(entryFile);
335
- const detected = await detectEntryPoint(dirFs);
336
- entryFile = path2.join(entryFile, detected.path);
337
- log(chalk.gray(`Auto-detected entry point: ${detected.path}`));
338
- }
311
+ if (!entry) {
312
+ log(chalk.gray(`Auto-detected entry point: ${entryPointInfo.path} (from ${entryPointInfo.source})`));
339
313
  }
340
314
  const minCoverage = clampCoverage(options.minCoverage ?? 80);
341
315
  const resolveExternalTypes = !options.skipResolve;
@@ -537,11 +511,8 @@ function registerCheckCommand(program, dependencies = {}) {
537
511
  const fixedDriftKeys = new Set;
538
512
  if (shouldFix && driftExports.length > 0) {
539
513
  const allDrifts = collectDriftsFromExports(spec.exports ?? []);
540
- const filteredDrifts = filterDriftsByType(allDrifts, options.only);
541
- if (filteredDrifts.length === 0 && options.only) {
542
- log(chalk.yellow("No matching drift issues for the specified types."));
543
- } else if (filteredDrifts.length > 0) {
544
- const { fixable, nonFixable } = categorizeDrifts(filteredDrifts.map((d) => d.drift));
514
+ if (allDrifts.length > 0) {
515
+ const { fixable, nonFixable } = categorizeDrifts(allDrifts.map((d) => d.drift));
545
516
  if (fixable.length === 0) {
546
517
  log(chalk.yellow(`Found ${nonFixable.length} drift issue(s), but none are auto-fixable.`));
547
518
  } else {
@@ -551,7 +522,7 @@ function registerCheckCommand(program, dependencies = {}) {
551
522
  log(chalk.gray(`(${nonFixable.length} non-fixable issue(s) skipped)`));
552
523
  }
553
524
  log("");
554
- const groupedDrifts = groupByExport(filteredDrifts.filter((d) => fixable.includes(d.drift)));
525
+ const groupedDrifts = groupByExport(allDrifts.filter((d) => fixable.includes(d.drift)));
555
526
  const edits = [];
556
527
  const editsByFile = new Map;
557
528
  for (const [exp, drifts] of groupedDrifts) {
@@ -1363,59 +1334,207 @@ import * as fs3 from "node:fs";
1363
1334
  import * as path4 from "node:path";
1364
1335
  import {
1365
1336
  DocCov as DocCov2,
1366
- detectEntryPoint as detectEntryPoint2,
1367
- detectMonorepo as detectMonorepo2,
1368
- findPackageByName as findPackageByName2,
1369
- NodeFileSystem as NodeFileSystem2
1337
+ NodeFileSystem as NodeFileSystem2,
1338
+ resolveTarget as resolveTarget2
1370
1339
  } from "@doccov/sdk";
1371
1340
  import { normalize, validateSpec } from "@openpkg-ts/spec";
1372
1341
  import chalk4 from "chalk";
1373
1342
 
1343
+ // src/reports/markdown.ts
1344
+ function bar(pct, width = 10) {
1345
+ const filled = Math.round(pct / 100 * width);
1346
+ return "█".repeat(filled) + "░".repeat(width - filled);
1347
+ }
1348
+ function renderMarkdown(stats, options = {}) {
1349
+ const limit = options.limit ?? 20;
1350
+ const lines = [];
1351
+ lines.push(`# DocCov Report: ${stats.packageName}@${stats.version}`);
1352
+ lines.push("");
1353
+ lines.push(`**Coverage: ${stats.coverageScore}%** \`${bar(stats.coverageScore)}\``);
1354
+ lines.push("");
1355
+ lines.push("| Metric | Value |");
1356
+ lines.push("|--------|-------|");
1357
+ lines.push(`| Exports | ${stats.totalExports} |`);
1358
+ lines.push(`| Fully documented | ${stats.fullyDocumented} |`);
1359
+ lines.push(`| Partially documented | ${stats.partiallyDocumented} |`);
1360
+ lines.push(`| Undocumented | ${stats.undocumented} |`);
1361
+ lines.push(`| Drift issues | ${stats.driftCount} |`);
1362
+ lines.push("");
1363
+ lines.push("## Coverage by Signal");
1364
+ lines.push("");
1365
+ lines.push("| Signal | Coverage |");
1366
+ lines.push("|--------|----------|");
1367
+ for (const [sig, s] of Object.entries(stats.signalCoverage)) {
1368
+ lines.push(`| ${sig} | ${s.pct}% \`${bar(s.pct, 8)}\` |`);
1369
+ }
1370
+ if (stats.byKind.length > 0) {
1371
+ lines.push("");
1372
+ lines.push("## Coverage by Kind");
1373
+ lines.push("");
1374
+ lines.push("| Kind | Count | Avg Score |");
1375
+ lines.push("|------|-------|-----------|");
1376
+ for (const k of stats.byKind) {
1377
+ lines.push(`| ${k.kind} | ${k.count} | ${k.avgScore}% |`);
1378
+ }
1379
+ }
1380
+ const lowExports = stats.exports.filter((e) => e.score < 100).slice(0, limit);
1381
+ if (lowExports.length > 0) {
1382
+ lines.push("");
1383
+ lines.push("## Lowest Coverage Exports");
1384
+ lines.push("");
1385
+ lines.push("| Export | Kind | Score | Missing |");
1386
+ lines.push("|--------|------|-------|---------|");
1387
+ for (const e of lowExports) {
1388
+ lines.push(`| \`${e.name}\` | ${e.kind} | ${e.score}% | ${e.missing.join(", ") || "-"} |`);
1389
+ }
1390
+ const totalLow = stats.exports.filter((e) => e.score < 100).length;
1391
+ if (totalLow > limit) {
1392
+ lines.push(`| ... | | | ${totalLow - limit} more |`);
1393
+ }
1394
+ }
1395
+ if (stats.driftIssues.length > 0) {
1396
+ lines.push("");
1397
+ lines.push("## Drift Issues");
1398
+ lines.push("");
1399
+ lines.push("| Export | Type | Issue |");
1400
+ lines.push("|--------|------|-------|");
1401
+ for (const d of stats.driftIssues.slice(0, limit)) {
1402
+ const hint = d.suggestion ? ` → ${d.suggestion}` : "";
1403
+ lines.push(`| \`${d.exportName}\` | ${d.type} | ${d.issue}${hint} |`);
1404
+ }
1405
+ }
1406
+ lines.push("");
1407
+ lines.push("---");
1408
+ lines.push("*Generated by [DocCov](https://doccov.com)*");
1409
+ return lines.join(`
1410
+ `);
1411
+ }
1412
+
1413
+ // src/reports/html.ts
1414
+ function escapeHtml(s) {
1415
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1416
+ }
1417
+ function renderHtml(stats, options = {}) {
1418
+ const md = renderMarkdown(stats, options);
1419
+ return `<!DOCTYPE html>
1420
+ <html lang="en">
1421
+ <head>
1422
+ <meta charset="UTF-8">
1423
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1424
+ <title>DocCov Report: ${escapeHtml(stats.packageName)}</title>
1425
+ <style>
1426
+ :root { --bg: #0d1117; --fg: #c9d1d9; --border: #30363d; --accent: #58a6ff; }
1427
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg); color: var(--fg); max-width: 900px; margin: 0 auto; padding: 2rem; line-height: 1.6; }
1428
+ h1, h2 { border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; }
1429
+ table { border-collapse: collapse; width: 100%; margin: 1rem 0; }
1430
+ th, td { border: 1px solid var(--border); padding: 0.5rem 1rem; text-align: left; }
1431
+ th { background: #161b22; }
1432
+ code { background: #161b22; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; }
1433
+ a { color: var(--accent); }
1434
+ </style>
1435
+ </head>
1436
+ <body>
1437
+ <pre style="white-space: pre-wrap; font-family: inherit;">${escapeHtml(md)}</pre>
1438
+ </body>
1439
+ </html>`;
1440
+ }
1441
+ // src/reports/stats.ts
1442
+ function computeStats(spec) {
1443
+ const exports = spec.exports ?? [];
1444
+ const signals = {
1445
+ description: { covered: 0, total: 0 },
1446
+ params: { covered: 0, total: 0 },
1447
+ returns: { covered: 0, total: 0 },
1448
+ examples: { covered: 0, total: 0 }
1449
+ };
1450
+ const kindMap = new Map;
1451
+ const driftIssues = [];
1452
+ let fullyDocumented = 0;
1453
+ let partiallyDocumented = 0;
1454
+ let undocumented = 0;
1455
+ for (const exp of exports) {
1456
+ const score = exp.docs?.coverageScore ?? 0;
1457
+ const missing = exp.docs?.missing ?? [];
1458
+ for (const sig of ["description", "params", "returns", "examples"]) {
1459
+ signals[sig].total++;
1460
+ if (!missing.includes(sig))
1461
+ signals[sig].covered++;
1462
+ }
1463
+ const kindEntry = kindMap.get(exp.kind) ?? { count: 0, totalScore: 0 };
1464
+ kindEntry.count++;
1465
+ kindEntry.totalScore += score;
1466
+ kindMap.set(exp.kind, kindEntry);
1467
+ if (score === 100)
1468
+ fullyDocumented++;
1469
+ else if (score > 0)
1470
+ partiallyDocumented++;
1471
+ else
1472
+ undocumented++;
1473
+ for (const d of exp.docs?.drift ?? []) {
1474
+ driftIssues.push({
1475
+ exportName: exp.name,
1476
+ type: d.type,
1477
+ issue: d.issue,
1478
+ suggestion: d.suggestion
1479
+ });
1480
+ }
1481
+ }
1482
+ const signalCoverage = Object.fromEntries(Object.entries(signals).map(([k, v]) => [
1483
+ k,
1484
+ { ...v, pct: v.total ? Math.round(v.covered / v.total * 100) : 0 }
1485
+ ]));
1486
+ const byKind = Array.from(kindMap.entries()).map(([kind, { count, totalScore }]) => ({
1487
+ kind,
1488
+ count,
1489
+ avgScore: Math.round(totalScore / count)
1490
+ })).sort((a, b) => b.count - a.count);
1491
+ const sortedExports = exports.map((e) => ({
1492
+ name: e.name,
1493
+ kind: e.kind,
1494
+ score: e.docs?.coverageScore ?? 0,
1495
+ missing: e.docs?.missing ?? []
1496
+ })).sort((a, b) => a.score - b.score);
1497
+ return {
1498
+ packageName: spec.meta.name ?? "unknown",
1499
+ version: spec.meta.version ?? "0.0.0",
1500
+ coverageScore: spec.docs?.coverageScore ?? 0,
1501
+ totalExports: exports.length,
1502
+ fullyDocumented,
1503
+ partiallyDocumented,
1504
+ undocumented,
1505
+ driftCount: driftIssues.length,
1506
+ signalCoverage,
1507
+ byKind,
1508
+ exports: sortedExports,
1509
+ driftIssues
1510
+ };
1511
+ }
1374
1512
  // src/utils/filter-options.ts
1513
+ import { mergeFilters, parseListFlag } from "@doccov/sdk";
1375
1514
  import chalk3 from "chalk";
1376
- var unique = (values) => Array.from(new Set(values));
1377
- var parseListFlag = (value) => {
1378
- if (!value) {
1379
- return;
1380
- }
1381
- const rawItems = Array.isArray(value) ? value : [value];
1382
- const normalized = rawItems.flatMap((item) => String(item).split(",")).map((item) => item.trim()).filter(Boolean);
1383
- return normalized.length > 0 ? unique(normalized) : undefined;
1384
- };
1385
1515
  var formatList = (label, values) => `${label}: ${values.map((value) => chalk3.cyan(value)).join(", ")}`;
1386
1516
  var mergeFilterOptions = (config, cliOptions) => {
1387
1517
  const messages = [];
1388
- const configInclude = config?.include;
1389
- const configExclude = config?.exclude;
1390
- const cliInclude = cliOptions.include;
1391
- const cliExclude = cliOptions.exclude;
1392
- let include = configInclude;
1393
- let exclude = configExclude;
1394
- let source = include || exclude ? "config" : undefined;
1395
- if (configInclude) {
1396
- messages.push(formatList("include filters from config", configInclude));
1397
- }
1398
- if (configExclude) {
1399
- messages.push(formatList("exclude filters from config", configExclude));
1400
- }
1401
- if (cliInclude) {
1402
- include = include ? include.filter((item) => cliInclude.includes(item)) : cliInclude;
1403
- source = include ? "combined" : "cli";
1404
- messages.push(formatList("apply include filters from CLI", cliInclude));
1405
- }
1406
- if (cliExclude) {
1407
- exclude = exclude ? unique([...exclude, ...cliExclude]) : cliExclude;
1408
- source = source ? "combined" : "cli";
1409
- messages.push(formatList("apply exclude filters from CLI", cliExclude));
1410
- }
1411
- include = include ? unique(include) : undefined;
1412
- exclude = exclude ? unique(exclude) : undefined;
1413
- if (!include && !exclude) {
1518
+ if (config?.include) {
1519
+ messages.push(formatList("include filters from config", config.include));
1520
+ }
1521
+ if (config?.exclude) {
1522
+ messages.push(formatList("exclude filters from config", config.exclude));
1523
+ }
1524
+ if (cliOptions.include) {
1525
+ messages.push(formatList("apply include filters from CLI", cliOptions.include));
1526
+ }
1527
+ if (cliOptions.exclude) {
1528
+ messages.push(formatList("apply exclude filters from CLI", cliOptions.exclude));
1529
+ }
1530
+ const resolved = mergeFilters(config, cliOptions);
1531
+ if (!resolved.include && !resolved.exclude) {
1414
1532
  return { messages };
1415
1533
  }
1534
+ const source = resolved.source === "override" ? "cli" : resolved.source;
1416
1535
  return {
1417
- include,
1418
- exclude,
1536
+ include: resolved.include,
1537
+ exclude: resolved.exclude,
1419
1538
  source,
1420
1539
  messages
1421
1540
  };
@@ -1453,37 +1572,20 @@ function registerGenerateCommand(program, dependencies = {}) {
1453
1572
  ...defaultDependencies3,
1454
1573
  ...dependencies
1455
1574
  };
1456
- program.command("generate [entry]").description("Generate OpenPkg specification for documentation coverage analysis").option("-o, --output <file>", "Output file", "openpkg.json").option("-p, --package <name>", "Target package name (for monorepos)").option("--cwd <dir>", "Working directory", process.cwd()).option("--skip-resolve", "Skip external type resolution from node_modules").option("--include <ids>", "Filter exports by identifier (comma-separated or repeated)").option("--exclude <ids>", "Exclude exports by identifier (comma-separated or repeated)").option("--show-diagnostics", "Print TypeScript diagnostics from analysis").option("--no-docs", "Omit docs coverage fields from output (pure structural spec)").option("-y, --yes", "Skip all prompts and use defaults").action(async (entry, options) => {
1575
+ program.command("generate [entry]").description("Generate OpenPkg specification for documentation coverage analysis").option("-o, --output <file>", "Output file", "openpkg.json").option("--format <format>", "Output format: json, markdown, html", "json").option("-p, --package <name>", "Target package name (for monorepos)").option("--cwd <dir>", "Working directory", process.cwd()).option("--skip-resolve", "Skip external type resolution from node_modules").option("--include <ids>", "Filter exports by identifier (comma-separated or repeated)").option("--exclude <ids>", "Exclude exports by identifier (comma-separated or repeated)").option("--show-diagnostics", "Print TypeScript diagnostics from analysis").option("--no-docs", "Omit docs coverage fields from output (pure structural spec)").option("--limit <n>", "Max exports to show in report tables (for markdown/html)", "20").option("-y, --yes", "Skip all prompts and use defaults").action(async (entry, options) => {
1457
1576
  try {
1458
- let targetDir = options.cwd;
1459
- let entryFile = entry;
1460
1577
  const fileSystem = new NodeFileSystem2(options.cwd);
1461
- if (options.package) {
1462
- const mono = await detectMonorepo2(fileSystem);
1463
- if (!mono.isMonorepo) {
1464
- throw new Error(`Not a monorepo. Remove --package flag for single-package repos.`);
1465
- }
1466
- const pkg = findPackageByName2(mono.packages, options.package);
1467
- if (!pkg) {
1468
- const available = mono.packages.map((p) => p.name).join(", ");
1469
- throw new Error(`Package "${options.package}" not found. Available: ${available}`);
1470
- }
1471
- targetDir = path4.join(options.cwd, pkg.path);
1472
- log(chalk4.gray(`Found package at ${pkg.path}`));
1578
+ const resolved = await resolveTarget2(fileSystem, {
1579
+ cwd: options.cwd,
1580
+ package: options.package,
1581
+ entry
1582
+ });
1583
+ const { targetDir, entryFile, packageInfo, entryPointInfo } = resolved;
1584
+ if (packageInfo) {
1585
+ log(chalk4.gray(`Found package at ${packageInfo.path}`));
1473
1586
  }
1474
- if (!entryFile) {
1475
- const targetFs = new NodeFileSystem2(targetDir);
1476
- const detected = await detectEntryPoint2(targetFs);
1477
- entryFile = path4.join(targetDir, detected.path);
1478
- log(chalk4.gray(`Auto-detected entry point: ${detected.path} (from ${detected.source})`));
1479
- } else {
1480
- entryFile = path4.resolve(targetDir, entryFile);
1481
- if (fs3.existsSync(entryFile) && fs3.statSync(entryFile).isDirectory()) {
1482
- const dirFs = new NodeFileSystem2(entryFile);
1483
- const detected = await detectEntryPoint2(dirFs);
1484
- entryFile = path4.join(entryFile, detected.path);
1485
- log(chalk4.gray(`Auto-detected entry point: ${detected.path} (from ${detected.source})`));
1486
- }
1587
+ if (!entry) {
1588
+ log(chalk4.gray(`Auto-detected entry point: ${entryPointInfo.path} (from ${entryPointInfo.source})`));
1487
1589
  }
1488
1590
  const resolveExternalTypes = !options.skipResolve;
1489
1591
  const cliFilters = {
@@ -1528,23 +1630,34 @@ function registerGenerateCommand(program, dependencies = {}) {
1528
1630
  if (!result) {
1529
1631
  throw new Error("Failed to produce an OpenPkg spec.");
1530
1632
  }
1531
- const outputPath = path4.resolve(process.cwd(), options.output);
1532
1633
  let normalized = normalize(result.spec);
1533
1634
  if (options.docs === false) {
1534
1635
  normalized = stripDocsFields(normalized);
1535
1636
  }
1536
1637
  const validation = validateSpec(normalized);
1537
1638
  if (!validation.ok) {
1538
- spinnerInstance.fail("Spec failed schema validation");
1639
+ error(chalk4.red("Spec failed schema validation"));
1539
1640
  for (const err of validation.errors) {
1540
1641
  error(chalk4.red(`schema: ${err.instancePath || "/"} ${err.message}`));
1541
1642
  }
1542
1643
  process.exit(1);
1543
1644
  }
1544
- writeFileSync2(outputPath, JSON.stringify(normalized, null, 2));
1545
- log(chalk4.green(`✓ Generated ${options.output}`));
1546
- log(chalk4.gray(` ${getArrayLength(normalized.exports)} exports`));
1547
- log(chalk4.gray(` ${getArrayLength(normalized.types)} types`));
1645
+ const format = options.format ?? "json";
1646
+ const outputPath = path4.resolve(process.cwd(), options.output);
1647
+ if (format === "markdown" || format === "html") {
1648
+ const stats = computeStats(normalized);
1649
+ const limit = parseInt(options.limit, 10) || 20;
1650
+ const reportOutput = format === "html" ? renderHtml(stats, { limit }) : renderMarkdown(stats, { limit });
1651
+ writeFileSync2(outputPath, reportOutput);
1652
+ log(chalk4.green(`✓ Generated ${format} report: ${options.output}`));
1653
+ log(chalk4.gray(` Coverage: ${stats.coverageScore}%`));
1654
+ log(chalk4.gray(` ${stats.totalExports} exports, ${stats.driftCount} drift issues`));
1655
+ } else {
1656
+ writeFileSync2(outputPath, JSON.stringify(normalized, null, 2));
1657
+ log(chalk4.green(`✓ Generated ${options.output}`));
1658
+ log(chalk4.gray(` ${getArrayLength(normalized.exports)} exports`));
1659
+ log(chalk4.gray(` ${getArrayLength(normalized.types)} types`));
1660
+ }
1548
1661
  if (options.showDiagnostics && result.diagnostics.length > 0) {
1549
1662
  log("");
1550
1663
  log(chalk4.bold("Diagnostics"));
@@ -1690,518 +1803,32 @@ var buildTemplate = (format) => {
1690
1803
  `);
1691
1804
  };
1692
1805
 
1693
- // src/commands/lint.ts
1694
- import * as fs5 from "node:fs";
1695
- import * as path6 from "node:path";
1806
+ // src/commands/scan.ts
1807
+ import * as fs6 from "node:fs";
1808
+ import * as fsPromises from "node:fs/promises";
1809
+ import * as os from "node:os";
1810
+ import * as path7 from "node:path";
1696
1811
  import {
1697
- applyEdits as applyEdits2,
1698
- createSourceFile as createSourceFile2,
1699
1812
  DocCov as DocCov3,
1700
- detectEntryPoint as detectEntryPoint3,
1701
- detectMonorepo as detectMonorepo3,
1702
- findJSDocLocation as findJSDocLocation2,
1703
- findPackageByName as findPackageByName3,
1704
- getDefaultConfig,
1705
- getRule,
1706
- lintExport as lintExport2,
1813
+ buildCloneUrl,
1814
+ buildDisplayUrl,
1815
+ detectBuildInfo,
1816
+ detectEntryPoint,
1817
+ detectMonorepo,
1818
+ detectPackageManager,
1819
+ extractSpecSummary,
1820
+ findPackageByName,
1821
+ formatPackageList,
1822
+ getInstallCommand,
1707
1823
  NodeFileSystem as NodeFileSystem3,
1708
- serializeJSDoc as serializeJSDoc2
1824
+ parseGitHubUrl
1709
1825
  } from "@doccov/sdk";
1710
1826
  import chalk6 from "chalk";
1711
- var defaultDependencies5 = {
1712
- createDocCov: (options) => new DocCov3(options),
1713
- log: console.log,
1714
- error: console.error
1715
- };
1716
- function getRawJSDoc(exp, targetDir) {
1717
- if (!exp.source?.file)
1718
- return;
1719
- const filePath = path6.resolve(targetDir, exp.source.file);
1720
- if (!fs5.existsSync(filePath))
1721
- return;
1722
- try {
1723
- const sourceFile = createSourceFile2(filePath);
1724
- const location = findJSDocLocation2(sourceFile, exp.name, exp.source.line);
1725
- return location?.existingJSDoc;
1726
- } catch {
1727
- return;
1728
- }
1729
- }
1730
- function registerLintCommand(program, dependencies = {}) {
1731
- const { createDocCov, log, error } = {
1732
- ...defaultDependencies5,
1733
- ...dependencies
1734
- };
1735
- program.command("lint [entry]").description("Lint documentation for style and quality issues").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--fix", "Auto-fix fixable issues").option("--write", "Alias for --fix").option("--rule <name>", "Run only a specific rule").option("--skip-resolve", "Skip external type resolution").action(async (entry, options) => {
1736
- try {
1737
- let targetDir = options.cwd;
1738
- let entryFile = entry;
1739
- const fileSystem = new NodeFileSystem3(options.cwd);
1740
- if (options.package) {
1741
- const mono = await detectMonorepo3(fileSystem);
1742
- if (!mono.isMonorepo) {
1743
- throw new Error("Not a monorepo. Remove --package flag.");
1744
- }
1745
- const pkg = findPackageByName3(mono.packages, options.package);
1746
- if (!pkg) {
1747
- const available = mono.packages.map((p) => p.name).join(", ");
1748
- throw new Error(`Package "${options.package}" not found. Available: ${available}`);
1749
- }
1750
- targetDir = path6.join(options.cwd, pkg.path);
1751
- log(chalk6.gray(`Found package at ${pkg.path}`));
1752
- }
1753
- if (!entryFile) {
1754
- const targetFs = new NodeFileSystem3(targetDir);
1755
- const detected = await detectEntryPoint3(targetFs);
1756
- entryFile = path6.join(targetDir, detected.path);
1757
- log(chalk6.gray(`Auto-detected entry point: ${detected.path}`));
1758
- } else {
1759
- entryFile = path6.resolve(targetDir, entryFile);
1760
- if (fs5.existsSync(entryFile) && fs5.statSync(entryFile).isDirectory()) {
1761
- targetDir = entryFile;
1762
- const dirFs = new NodeFileSystem3(entryFile);
1763
- const detected = await detectEntryPoint3(dirFs);
1764
- entryFile = path6.join(entryFile, detected.path);
1765
- log(chalk6.gray(`Auto-detected entry point: ${detected.path}`));
1766
- }
1767
- }
1768
- const resolveExternalTypes = !options.skipResolve;
1769
- process.stdout.write(chalk6.cyan(`> Analyzing documentation...
1770
- `));
1771
- const doccov = createDocCov({ resolveExternalTypes });
1772
- const specResult = await doccov.analyzeFileWithDiagnostics(entryFile);
1773
- if (!specResult) {
1774
- throw new Error("Failed to analyze documentation.");
1775
- }
1776
- process.stdout.write(chalk6.cyan(`> Running lint rules...
1777
- `));
1778
- let config = getDefaultConfig();
1779
- if (options.rule) {
1780
- const rule = getRule(options.rule);
1781
- if (!rule) {
1782
- throw new Error(`Unknown rule: ${options.rule}`);
1783
- }
1784
- const rules = {};
1785
- for (const key of Object.keys(config.rules)) {
1786
- rules[key] = "off";
1787
- }
1788
- rules[options.rule] = rule.defaultSeverity === "off" ? "warn" : rule.defaultSeverity;
1789
- config = { rules };
1790
- }
1791
- const exportsWithJSDoc = [];
1792
- for (const exp of specResult.spec.exports ?? []) {
1793
- const rawJSDoc = getRawJSDoc(exp, targetDir);
1794
- exportsWithJSDoc.push({
1795
- export: exp,
1796
- rawJSDoc,
1797
- filePath: exp.source?.file ? path6.resolve(targetDir, exp.source.file) : undefined
1798
- });
1799
- }
1800
- const allViolations = [];
1801
- for (const { export: exp, rawJSDoc, filePath } of exportsWithJSDoc) {
1802
- const violations = lintExport2(exp, rawJSDoc, config);
1803
- for (const violation of violations) {
1804
- allViolations.push({ export: exp, violation, filePath, rawJSDoc });
1805
- }
1806
- }
1807
- const shouldFix = options.fix || options.write;
1808
- const fixableViolations = allViolations.filter((v) => v.violation.fixable);
1809
- if (shouldFix && fixableViolations.length > 0) {
1810
- process.stdout.write(chalk6.cyan(`> Applying fixes...
1811
- `));
1812
- const edits = [];
1813
- for (const { export: exp, rawJSDoc, filePath } of fixableViolations) {
1814
- if (!filePath || !rawJSDoc)
1815
- continue;
1816
- if (filePath.endsWith(".d.ts"))
1817
- continue;
1818
- const sourceFile = createSourceFile2(filePath);
1819
- const location = findJSDocLocation2(sourceFile, exp.name, exp.source?.line);
1820
- if (!location)
1821
- continue;
1822
- const rule = getRule("consistent-param-style");
1823
- if (!rule?.fix)
1824
- continue;
1825
- const patch = rule.fix(exp, rawJSDoc);
1826
- if (!patch)
1827
- continue;
1828
- const newJSDoc = serializeJSDoc2(patch, location.indent);
1829
- edits.push({
1830
- filePath,
1831
- symbolName: exp.name,
1832
- startLine: location.startLine,
1833
- endLine: location.endLine,
1834
- hasExisting: location.hasExisting,
1835
- existingJSDoc: location.existingJSDoc,
1836
- newJSDoc,
1837
- indent: location.indent
1838
- });
1839
- }
1840
- if (edits.length > 0) {
1841
- const result = await applyEdits2(edits);
1842
- if (result.errors.length > 0) {
1843
- for (const err of result.errors) {
1844
- error(chalk6.red(` ${err.file}: ${err.error}`));
1845
- }
1846
- } else {
1847
- process.stdout.write(chalk6.green(`✓ Fixed ${result.editsApplied} issue(s) in ${result.filesModified} file(s)
1848
- `));
1849
- }
1850
- const fixedExports = new Set(edits.map((e) => e.symbolName));
1851
- const remaining = allViolations.filter((v) => !v.violation.fixable || !fixedExports.has(v.export.name));
1852
- allViolations.length = 0;
1853
- allViolations.push(...remaining);
1854
- }
1855
- }
1856
- if (allViolations.length === 0) {
1857
- log(chalk6.green("✓ No lint issues found"));
1858
- return;
1859
- }
1860
- const byFile = new Map;
1861
- for (const v of allViolations) {
1862
- const file = v.filePath ?? "unknown";
1863
- const existing = byFile.get(file) ?? [];
1864
- existing.push(v);
1865
- byFile.set(file, existing);
1866
- }
1867
- log("");
1868
- for (const [filePath, violations] of byFile) {
1869
- const relativePath = path6.relative(targetDir, filePath);
1870
- log(chalk6.underline(relativePath));
1871
- for (const { export: exp, violation } of violations) {
1872
- const line = exp.source?.line ?? 0;
1873
- const severity = violation.severity === "error" ? chalk6.red("error") : chalk6.yellow("warning");
1874
- const fixable = violation.fixable ? chalk6.gray(" (fixable)") : "";
1875
- log(` ${line}:1 ${severity} ${violation.message} ${chalk6.gray(violation.rule)}${fixable}`);
1876
- }
1877
- log("");
1878
- }
1879
- const errorCount = allViolations.filter((v) => v.violation.severity === "error").length;
1880
- const warnCount = allViolations.filter((v) => v.violation.severity === "warn").length;
1881
- const fixableCount = allViolations.filter((v) => v.violation.fixable).length;
1882
- const summary = [];
1883
- if (errorCount > 0)
1884
- summary.push(chalk6.red(`${errorCount} error(s)`));
1885
- if (warnCount > 0)
1886
- summary.push(chalk6.yellow(`${warnCount} warning(s)`));
1887
- if (fixableCount > 0 && !shouldFix) {
1888
- summary.push(chalk6.gray(`${fixableCount} fixable with --fix`));
1889
- }
1890
- log(summary.join(", "));
1891
- if (errorCount > 0) {
1892
- process.exit(1);
1893
- }
1894
- } catch (commandError) {
1895
- error(chalk6.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
1896
- process.exit(1);
1897
- }
1898
- });
1899
- }
1900
-
1901
- // src/commands/report.ts
1902
- import * as fs6 from "node:fs";
1903
- import * as path7 from "node:path";
1904
- import {
1905
- DocCov as DocCov4,
1906
- detectEntryPoint as detectEntryPoint4,
1907
- detectMonorepo as detectMonorepo4,
1908
- findPackageByName as findPackageByName4,
1909
- NodeFileSystem as NodeFileSystem4
1910
- } from "@doccov/sdk";
1911
- import chalk7 from "chalk";
1912
-
1913
- // src/reports/markdown.ts
1914
- function bar(pct, width = 10) {
1915
- const filled = Math.round(pct / 100 * width);
1916
- return "█".repeat(filled) + "░".repeat(width - filled);
1917
- }
1918
- function renderMarkdown(stats, options = {}) {
1919
- const limit = options.limit ?? 20;
1920
- const lines = [];
1921
- lines.push(`# DocCov Report: ${stats.packageName}@${stats.version}`);
1922
- lines.push("");
1923
- lines.push(`**Coverage: ${stats.coverageScore}%** \`${bar(stats.coverageScore)}\``);
1924
- lines.push("");
1925
- lines.push("| Metric | Value |");
1926
- lines.push("|--------|-------|");
1927
- lines.push(`| Exports | ${stats.totalExports} |`);
1928
- lines.push(`| Fully documented | ${stats.fullyDocumented} |`);
1929
- lines.push(`| Partially documented | ${stats.partiallyDocumented} |`);
1930
- lines.push(`| Undocumented | ${stats.undocumented} |`);
1931
- lines.push(`| Drift issues | ${stats.driftCount} |`);
1932
- lines.push("");
1933
- lines.push("## Coverage by Signal");
1934
- lines.push("");
1935
- lines.push("| Signal | Coverage |");
1936
- lines.push("|--------|----------|");
1937
- for (const [sig, s] of Object.entries(stats.signalCoverage)) {
1938
- lines.push(`| ${sig} | ${s.pct}% \`${bar(s.pct, 8)}\` |`);
1939
- }
1940
- if (stats.byKind.length > 0) {
1941
- lines.push("");
1942
- lines.push("## Coverage by Kind");
1943
- lines.push("");
1944
- lines.push("| Kind | Count | Avg Score |");
1945
- lines.push("|------|-------|-----------|");
1946
- for (const k of stats.byKind) {
1947
- lines.push(`| ${k.kind} | ${k.count} | ${k.avgScore}% |`);
1948
- }
1949
- }
1950
- const lowExports = stats.exports.filter((e) => e.score < 100).slice(0, limit);
1951
- if (lowExports.length > 0) {
1952
- lines.push("");
1953
- lines.push("## Lowest Coverage Exports");
1954
- lines.push("");
1955
- lines.push("| Export | Kind | Score | Missing |");
1956
- lines.push("|--------|------|-------|---------|");
1957
- for (const e of lowExports) {
1958
- lines.push(`| \`${e.name}\` | ${e.kind} | ${e.score}% | ${e.missing.join(", ") || "-"} |`);
1959
- }
1960
- const totalLow = stats.exports.filter((e) => e.score < 100).length;
1961
- if (totalLow > limit) {
1962
- lines.push(`| ... | | | ${totalLow - limit} more |`);
1963
- }
1964
- }
1965
- if (stats.driftIssues.length > 0) {
1966
- lines.push("");
1967
- lines.push("## Drift Issues");
1968
- lines.push("");
1969
- lines.push("| Export | Type | Issue |");
1970
- lines.push("|--------|------|-------|");
1971
- for (const d of stats.driftIssues.slice(0, limit)) {
1972
- const hint = d.suggestion ? ` → ${d.suggestion}` : "";
1973
- lines.push(`| \`${d.exportName}\` | ${d.type} | ${d.issue}${hint} |`);
1974
- }
1975
- }
1976
- lines.push("");
1977
- lines.push("---");
1978
- lines.push("*Generated by [DocCov](https://doccov.com)*");
1979
- return lines.join(`
1980
- `);
1981
- }
1982
-
1983
- // src/reports/html.ts
1984
- function escapeHtml(s) {
1985
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1986
- }
1987
- function renderHtml(stats, options = {}) {
1988
- const md = renderMarkdown(stats, options);
1989
- return `<!DOCTYPE html>
1990
- <html lang="en">
1991
- <head>
1992
- <meta charset="UTF-8">
1993
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
1994
- <title>DocCov Report: ${escapeHtml(stats.packageName)}</title>
1995
- <style>
1996
- :root { --bg: #0d1117; --fg: #c9d1d9; --border: #30363d; --accent: #58a6ff; }
1997
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg); color: var(--fg); max-width: 900px; margin: 0 auto; padding: 2rem; line-height: 1.6; }
1998
- h1, h2 { border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; }
1999
- table { border-collapse: collapse; width: 100%; margin: 1rem 0; }
2000
- th, td { border: 1px solid var(--border); padding: 0.5rem 1rem; text-align: left; }
2001
- th { background: #161b22; }
2002
- code { background: #161b22; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; }
2003
- a { color: var(--accent); }
2004
- </style>
2005
- </head>
2006
- <body>
2007
- <pre style="white-space: pre-wrap; font-family: inherit;">${escapeHtml(md)}</pre>
2008
- </body>
2009
- </html>`;
2010
- }
2011
- // src/reports/stats.ts
2012
- function computeStats(spec) {
2013
- const exports = spec.exports ?? [];
2014
- const signals = {
2015
- description: { covered: 0, total: 0 },
2016
- params: { covered: 0, total: 0 },
2017
- returns: { covered: 0, total: 0 },
2018
- examples: { covered: 0, total: 0 }
2019
- };
2020
- const kindMap = new Map;
2021
- const driftIssues = [];
2022
- let fullyDocumented = 0;
2023
- let partiallyDocumented = 0;
2024
- let undocumented = 0;
2025
- for (const exp of exports) {
2026
- const score = exp.docs?.coverageScore ?? 0;
2027
- const missing = exp.docs?.missing ?? [];
2028
- for (const sig of ["description", "params", "returns", "examples"]) {
2029
- signals[sig].total++;
2030
- if (!missing.includes(sig))
2031
- signals[sig].covered++;
2032
- }
2033
- const kindEntry = kindMap.get(exp.kind) ?? { count: 0, totalScore: 0 };
2034
- kindEntry.count++;
2035
- kindEntry.totalScore += score;
2036
- kindMap.set(exp.kind, kindEntry);
2037
- if (score === 100)
2038
- fullyDocumented++;
2039
- else if (score > 0)
2040
- partiallyDocumented++;
2041
- else
2042
- undocumented++;
2043
- for (const d of exp.docs?.drift ?? []) {
2044
- driftIssues.push({
2045
- exportName: exp.name,
2046
- type: d.type,
2047
- issue: d.issue,
2048
- suggestion: d.suggestion
2049
- });
2050
- }
2051
- }
2052
- const signalCoverage = Object.fromEntries(Object.entries(signals).map(([k, v]) => [
2053
- k,
2054
- { ...v, pct: v.total ? Math.round(v.covered / v.total * 100) : 0 }
2055
- ]));
2056
- const byKind = Array.from(kindMap.entries()).map(([kind, { count, totalScore }]) => ({
2057
- kind,
2058
- count,
2059
- avgScore: Math.round(totalScore / count)
2060
- })).sort((a, b) => b.count - a.count);
2061
- const sortedExports = exports.map((e) => ({
2062
- name: e.name,
2063
- kind: e.kind,
2064
- score: e.docs?.coverageScore ?? 0,
2065
- missing: e.docs?.missing ?? []
2066
- })).sort((a, b) => a.score - b.score);
2067
- return {
2068
- packageName: spec.meta.name ?? "unknown",
2069
- version: spec.meta.version ?? "0.0.0",
2070
- coverageScore: spec.docs?.coverageScore ?? 0,
2071
- totalExports: exports.length,
2072
- fullyDocumented,
2073
- partiallyDocumented,
2074
- undocumented,
2075
- driftCount: driftIssues.length,
2076
- signalCoverage,
2077
- byKind,
2078
- exports: sortedExports,
2079
- driftIssues
2080
- };
2081
- }
2082
- // src/commands/report.ts
2083
- function registerReportCommand(program) {
2084
- program.command("report [entry]").description("Generate a documentation coverage report").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--spec <file>", "Use existing openpkg.json instead of analyzing").option("--output <format>", "Output format: markdown, html, json", "markdown").option("--out <file>", "Write to file instead of stdout").option("--limit <n>", "Max exports to show in tables", "20").option("--skip-resolve", "Skip external type resolution from node_modules").action(async (entry, options) => {
2085
- try {
2086
- let spec;
2087
- if (options.spec) {
2088
- const specPath = path7.resolve(options.cwd, options.spec);
2089
- spec = JSON.parse(fs6.readFileSync(specPath, "utf-8"));
2090
- } else {
2091
- let targetDir = options.cwd;
2092
- let entryFile = entry;
2093
- const fileSystem = new NodeFileSystem4(options.cwd);
2094
- if (options.package) {
2095
- const mono = await detectMonorepo4(fileSystem);
2096
- if (!mono.isMonorepo) {
2097
- throw new Error(`Not a monorepo. Remove --package flag for single-package repos.`);
2098
- }
2099
- const pkg = findPackageByName4(mono.packages, options.package);
2100
- if (!pkg) {
2101
- const available = mono.packages.map((p) => p.name).join(", ");
2102
- throw new Error(`Package "${options.package}" not found. Available: ${available}`);
2103
- }
2104
- targetDir = path7.join(options.cwd, pkg.path);
2105
- }
2106
- if (!entryFile) {
2107
- const targetFs = new NodeFileSystem4(targetDir);
2108
- const detected = await detectEntryPoint4(targetFs);
2109
- entryFile = path7.join(targetDir, detected.path);
2110
- } else {
2111
- entryFile = path7.resolve(targetDir, entryFile);
2112
- }
2113
- process.stdout.write(chalk7.cyan(`> Analyzing...
2114
- `));
2115
- try {
2116
- const resolveExternalTypes = !options.skipResolve;
2117
- const doccov = new DocCov4({ resolveExternalTypes });
2118
- const result = await doccov.analyzeFileWithDiagnostics(entryFile);
2119
- process.stdout.write(chalk7.green(`✓ Analysis complete
2120
- `));
2121
- spec = result.spec;
2122
- } catch (analysisError) {
2123
- process.stdout.write(chalk7.red(`✗ Analysis failed
2124
- `));
2125
- throw analysisError;
2126
- }
2127
- }
2128
- const stats = computeStats(spec);
2129
- const format = options.output;
2130
- const limit = parseInt(options.limit, 10) || 20;
2131
- let output;
2132
- if (format === "json") {
2133
- output = JSON.stringify(stats, null, 2);
2134
- } else if (format === "html") {
2135
- output = renderHtml(stats, { limit });
2136
- } else {
2137
- output = renderMarkdown(stats, { limit });
2138
- }
2139
- if (options.out) {
2140
- const outPath = path7.resolve(options.cwd, options.out);
2141
- fs6.writeFileSync(outPath, output);
2142
- console.log(chalk7.green(`Report written to ${outPath}`));
2143
- } else {
2144
- console.log(output);
2145
- }
2146
- } catch (err) {
2147
- console.error(chalk7.red("Error:"), err instanceof Error ? err.message : err);
2148
- process.exitCode = 1;
2149
- }
2150
- });
2151
- }
2152
-
2153
- // src/commands/scan.ts
2154
- import * as fs8 from "node:fs";
2155
- import * as fsPromises from "node:fs/promises";
2156
- import * as os from "node:os";
2157
- import * as path9 from "node:path";
2158
- import {
2159
- DocCov as DocCov5,
2160
- detectBuildInfo,
2161
- detectEntryPoint as detectEntryPoint5,
2162
- detectMonorepo as detectMonorepo5,
2163
- detectPackageManager,
2164
- findPackageByName as findPackageByName5,
2165
- formatPackageList,
2166
- getInstallCommand,
2167
- NodeFileSystem as NodeFileSystem5
2168
- } from "@doccov/sdk";
2169
- import chalk8 from "chalk";
2170
1827
  import { simpleGit } from "simple-git";
2171
1828
 
2172
- // src/utils/github-url.ts
2173
- function parseGitHubUrl(input, defaultRef = "main") {
2174
- const trimmed = input.trim();
2175
- if (!trimmed) {
2176
- throw new Error("GitHub URL cannot be empty");
2177
- }
2178
- let normalized = trimmed.replace(/^https?:\/\//, "").replace(/^git@github\.com:/, "").replace(/\.git$/, "");
2179
- normalized = normalized.replace(/^github\.com\//, "");
2180
- const parts = normalized.split("/").filter(Boolean);
2181
- if (parts.length < 2) {
2182
- throw new Error(`Invalid GitHub URL format: "${input}". Expected owner/repo or https://github.com/owner/repo`);
2183
- }
2184
- const owner = parts[0];
2185
- const repo = parts[1];
2186
- let ref = defaultRef;
2187
- if (parts.length >= 4 && (parts[2] === "tree" || parts[2] === "blob")) {
2188
- ref = parts.slice(3).join("/");
2189
- }
2190
- if (!owner || !repo) {
2191
- throw new Error(`Could not parse owner/repo from: "${input}"`);
2192
- }
2193
- return { owner, repo, ref };
2194
- }
2195
- function buildCloneUrl(parsed) {
2196
- return `https://github.com/${parsed.owner}/${parsed.repo}.git`;
2197
- }
2198
- function buildDisplayUrl(parsed) {
2199
- return `github.com/${parsed.owner}/${parsed.repo}`;
2200
- }
2201
-
2202
1829
  // src/utils/llm-build-plan.ts
2203
- import * as fs7 from "node:fs";
2204
- import * as path8 from "node:path";
1830
+ import * as fs5 from "node:fs";
1831
+ import * as path6 from "node:path";
2205
1832
  import { createAnthropic as createAnthropic3 } from "@ai-sdk/anthropic";
2206
1833
  import { createOpenAI as createOpenAI3 } from "@ai-sdk/openai";
2207
1834
  import { generateObject as generateObject3 } from "ai";
@@ -2237,10 +1864,10 @@ function getModel3() {
2237
1864
  async function gatherContextFiles(repoDir) {
2238
1865
  const sections = [];
2239
1866
  for (const fileName of CONTEXT_FILES) {
2240
- const filePath = path8.join(repoDir, fileName);
2241
- if (fs7.existsSync(filePath)) {
1867
+ const filePath = path6.join(repoDir, fileName);
1868
+ if (fs5.existsSync(filePath)) {
2242
1869
  try {
2243
- let content = fs7.readFileSync(filePath, "utf-8");
1870
+ let content = fs5.readFileSync(filePath, "utf-8");
2244
1871
  if (content.length > MAX_FILE_CHARS) {
2245
1872
  content = `${content.slice(0, MAX_FILE_CHARS)}
2246
1873
  ... (truncated)`;
@@ -2292,14 +1919,14 @@ async function generateBuildPlan(repoDir) {
2292
1919
  }
2293
1920
 
2294
1921
  // src/commands/scan.ts
2295
- var defaultDependencies6 = {
2296
- createDocCov: (options) => new DocCov5(options),
1922
+ var defaultDependencies5 = {
1923
+ createDocCov: (options) => new DocCov3(options),
2297
1924
  log: console.log,
2298
1925
  error: console.error
2299
1926
  };
2300
1927
  function registerScanCommand(program, dependencies = {}) {
2301
1928
  const { createDocCov, log, error } = {
2302
- ...defaultDependencies6,
1929
+ ...defaultDependencies5,
2303
1930
  ...dependencies
2304
1931
  };
2305
1932
  program.command("scan <url>").description("Analyze docs coverage for any public GitHub repository").option("--ref <branch>", "Branch or tag to analyze").option("--package <name>", "Target package in monorepo").option("--output <format>", "Output format: text or json", "text").option("--no-cleanup", "Keep cloned repo (for debugging)").option("--skip-install", "Skip dependency installation (faster, but may limit type resolution)").option("--skip-resolve", "Skip external type resolution from node_modules").option("--save-spec <path>", "Save full OpenPkg spec to file").action(async (url, options) => {
@@ -2309,12 +1936,12 @@ function registerScanCommand(program, dependencies = {}) {
2309
1936
  const cloneUrl = buildCloneUrl(parsed);
2310
1937
  const displayUrl = buildDisplayUrl(parsed);
2311
1938
  log("");
2312
- log(chalk8.bold(`Scanning ${displayUrl}`));
2313
- log(chalk8.gray(`Branch/tag: ${parsed.ref}`));
1939
+ log(chalk6.bold(`Scanning ${displayUrl}`));
1940
+ log(chalk6.gray(`Branch/tag: ${parsed.ref}`));
2314
1941
  log("");
2315
- tempDir = path9.join(os.tmpdir(), `doccov-scan-${Date.now()}-${Math.random().toString(36).slice(2)}`);
2316
- fs8.mkdirSync(tempDir, { recursive: true });
2317
- process.stdout.write(chalk8.cyan(`> Cloning ${parsed.owner}/${parsed.repo}...
1942
+ tempDir = path7.join(os.tmpdir(), `doccov-scan-${Date.now()}-${Math.random().toString(36).slice(2)}`);
1943
+ fs6.mkdirSync(tempDir, { recursive: true });
1944
+ process.stdout.write(chalk6.cyan(`> Cloning ${parsed.owner}/${parsed.repo}...
2318
1945
  `));
2319
1946
  try {
2320
1947
  const git = simpleGit({
@@ -2336,10 +1963,10 @@ function registerScanCommand(program, dependencies = {}) {
2336
1963
  } finally {
2337
1964
  process.env = originalEnv;
2338
1965
  }
2339
- process.stdout.write(chalk8.green(`✓ Cloned ${parsed.owner}/${parsed.repo}
1966
+ process.stdout.write(chalk6.green(`✓ Cloned ${parsed.owner}/${parsed.repo}
2340
1967
  `));
2341
1968
  } catch (cloneError) {
2342
- process.stdout.write(chalk8.red(`✗ Failed to clone repository
1969
+ process.stdout.write(chalk6.red(`✗ Failed to clone repository
2343
1970
  `));
2344
1971
  const message = cloneError instanceof Error ? cloneError.message : String(cloneError);
2345
1972
  if (message.includes("Authentication failed") || message.includes("could not read Username") || message.includes("terminal prompts disabled") || message.includes("Invalid username or password") || message.includes("Permission denied")) {
@@ -2355,11 +1982,11 @@ function registerScanCommand(program, dependencies = {}) {
2355
1982
  }
2356
1983
  throw new Error(`Clone failed: ${message}`);
2357
1984
  }
2358
- const fileSystem = new NodeFileSystem5(tempDir);
1985
+ const fileSystem = new NodeFileSystem3(tempDir);
2359
1986
  if (options.skipInstall) {
2360
- log(chalk8.gray("Skipping dependency installation (--skip-install)"));
1987
+ log(chalk6.gray("Skipping dependency installation (--skip-install)"));
2361
1988
  } else {
2362
- process.stdout.write(chalk8.cyan(`> Installing dependencies...
1989
+ process.stdout.write(chalk6.cyan(`> Installing dependencies...
2363
1990
  `));
2364
1991
  const installErrors = [];
2365
1992
  try {
@@ -2409,56 +2036,56 @@ function registerScanCommand(program, dependencies = {}) {
2409
2036
  }
2410
2037
  }
2411
2038
  if (installed) {
2412
- process.stdout.write(chalk8.green(`✓ Dependencies installed
2039
+ process.stdout.write(chalk6.green(`✓ Dependencies installed
2413
2040
  `));
2414
2041
  } else {
2415
- process.stdout.write(chalk8.yellow(`⚠ Could not install dependencies (analysis may be limited)
2042
+ process.stdout.write(chalk6.yellow(`⚠ Could not install dependencies (analysis may be limited)
2416
2043
  `));
2417
2044
  for (const err of installErrors) {
2418
- log(chalk8.gray(` ${err}`));
2045
+ log(chalk6.gray(` ${err}`));
2419
2046
  }
2420
2047
  }
2421
2048
  } catch (outerError) {
2422
2049
  const msg = outerError instanceof Error ? outerError.message : String(outerError);
2423
- process.stdout.write(chalk8.yellow(`⚠ Could not install dependencies: ${msg.slice(0, 100)}
2050
+ process.stdout.write(chalk6.yellow(`⚠ Could not install dependencies: ${msg.slice(0, 100)}
2424
2051
  `));
2425
2052
  for (const err of installErrors) {
2426
- log(chalk8.gray(` ${err}`));
2053
+ log(chalk6.gray(` ${err}`));
2427
2054
  }
2428
2055
  }
2429
2056
  }
2430
2057
  let targetDir = tempDir;
2431
2058
  let packageName;
2432
- const mono = await detectMonorepo5(fileSystem);
2059
+ const mono = await detectMonorepo(fileSystem);
2433
2060
  if (mono.isMonorepo) {
2434
2061
  if (!options.package) {
2435
2062
  error("");
2436
- error(chalk8.red(`Monorepo detected with ${mono.packages.length} packages. Specify target with --package:`));
2063
+ error(chalk6.red(`Monorepo detected with ${mono.packages.length} packages. Specify target with --package:`));
2437
2064
  error("");
2438
2065
  error(formatPackageList(mono.packages));
2439
2066
  error("");
2440
2067
  throw new Error("Monorepo requires --package flag");
2441
2068
  }
2442
- const pkg = findPackageByName5(mono.packages, options.package);
2069
+ const pkg = findPackageByName(mono.packages, options.package);
2443
2070
  if (!pkg) {
2444
2071
  error("");
2445
- error(chalk8.red(`Package "${options.package}" not found. Available packages:`));
2072
+ error(chalk6.red(`Package "${options.package}" not found. Available packages:`));
2446
2073
  error("");
2447
2074
  error(formatPackageList(mono.packages));
2448
2075
  error("");
2449
2076
  throw new Error(`Package not found: ${options.package}`);
2450
2077
  }
2451
- targetDir = path9.join(tempDir, pkg.path);
2078
+ targetDir = path7.join(tempDir, pkg.path);
2452
2079
  packageName = pkg.name;
2453
- log(chalk8.gray(`Analyzing package: ${packageName}`));
2080
+ log(chalk6.gray(`Analyzing package: ${packageName}`));
2454
2081
  }
2455
- process.stdout.write(chalk8.cyan(`> Detecting entry point...
2082
+ process.stdout.write(chalk6.cyan(`> Detecting entry point...
2456
2083
  `));
2457
2084
  let entryPath;
2458
- const targetFs = mono.isMonorepo ? new NodeFileSystem5(targetDir) : fileSystem;
2085
+ const targetFs = mono.isMonorepo ? new NodeFileSystem3(targetDir) : fileSystem;
2459
2086
  let buildFailed = false;
2460
2087
  const runLlmFallback = async (reason) => {
2461
- process.stdout.write(chalk8.cyan(`> ${reason}, trying LLM fallback...
2088
+ process.stdout.write(chalk6.cyan(`> ${reason}, trying LLM fallback...
2462
2089
  `));
2463
2090
  const plan = await generateBuildPlan(targetDir);
2464
2091
  if (!plan) {
@@ -2467,117 +2094,100 @@ function registerScanCommand(program, dependencies = {}) {
2467
2094
  if (plan.buildCommands.length > 0) {
2468
2095
  const { execSync } = await import("node:child_process");
2469
2096
  for (const cmd of plan.buildCommands) {
2470
- log(chalk8.gray(` Running: ${cmd}`));
2097
+ log(chalk6.gray(` Running: ${cmd}`));
2471
2098
  try {
2472
2099
  execSync(cmd, { cwd: targetDir, stdio: "pipe", timeout: 300000 });
2473
2100
  } catch (buildError) {
2474
2101
  buildFailed = true;
2475
2102
  const msg = buildError instanceof Error ? buildError.message : String(buildError);
2476
2103
  if (msg.includes("rustc") || msg.includes("cargo") || msg.includes("wasm-pack")) {
2477
- log(chalk8.yellow(` ⚠ Build requires Rust toolchain (not available)`));
2104
+ log(chalk6.yellow(` ⚠ Build requires Rust toolchain (not available)`));
2478
2105
  } else if (msg.includes("rimraf") || msg.includes("command not found")) {
2479
- log(chalk8.yellow(` ⚠ Build failed: missing dependencies`));
2106
+ log(chalk6.yellow(` ⚠ Build failed: missing dependencies`));
2480
2107
  } else {
2481
- log(chalk8.yellow(` ⚠ Build failed: ${msg.slice(0, 80)}`));
2108
+ log(chalk6.yellow(` ⚠ Build failed: ${msg.slice(0, 80)}`));
2482
2109
  }
2483
2110
  }
2484
2111
  }
2485
2112
  }
2486
2113
  if (plan.notes) {
2487
- log(chalk8.gray(` Note: ${plan.notes}`));
2114
+ log(chalk6.gray(` Note: ${plan.notes}`));
2488
2115
  }
2489
2116
  return plan.entryPoint;
2490
2117
  };
2491
2118
  try {
2492
- const entry = await detectEntryPoint5(targetFs);
2119
+ const entry = await detectEntryPoint(targetFs);
2493
2120
  const buildInfo = await detectBuildInfo(targetFs);
2494
2121
  const needsBuildStep = entry.isDeclarationOnly && buildInfo.exoticIndicators.wasm;
2495
2122
  if (needsBuildStep) {
2496
- process.stdout.write(chalk8.cyan(`> Detected .d.ts entry with WASM indicators...
2123
+ process.stdout.write(chalk6.cyan(`> Detected .d.ts entry with WASM indicators...
2497
2124
  `));
2498
2125
  const llmEntry = await runLlmFallback("WASM project detected");
2499
2126
  if (llmEntry) {
2500
- entryPath = path9.join(targetDir, llmEntry);
2127
+ entryPath = path7.join(targetDir, llmEntry);
2501
2128
  if (buildFailed) {
2502
- process.stdout.write(chalk8.green(`✓ Entry point: ${llmEntry} (using pre-committed declarations)
2129
+ process.stdout.write(chalk6.green(`✓ Entry point: ${llmEntry} (using pre-committed declarations)
2503
2130
  `));
2504
- log(chalk8.gray(" Coverage may be limited - generated .d.ts files typically lack JSDoc"));
2131
+ log(chalk6.gray(" Coverage may be limited - generated .d.ts files typically lack JSDoc"));
2505
2132
  } else {
2506
- process.stdout.write(chalk8.green(`✓ Entry point: ${llmEntry} (from LLM fallback - WASM project)
2133
+ process.stdout.write(chalk6.green(`✓ Entry point: ${llmEntry} (from LLM fallback - WASM project)
2507
2134
  `));
2508
2135
  }
2509
2136
  } else {
2510
- entryPath = path9.join(targetDir, entry.path);
2511
- process.stdout.write(chalk8.green(`✓ Entry point: ${entry.path} (from ${entry.source})
2137
+ entryPath = path7.join(targetDir, entry.path);
2138
+ process.stdout.write(chalk6.green(`✓ Entry point: ${entry.path} (from ${entry.source})
2512
2139
  `));
2513
- log(chalk8.yellow(" ⚠ WASM project detected but no API key - analysis may be limited"));
2140
+ log(chalk6.yellow(" ⚠ WASM project detected but no API key - analysis may be limited"));
2514
2141
  }
2515
2142
  } else {
2516
- entryPath = path9.join(targetDir, entry.path);
2517
- process.stdout.write(chalk8.green(`✓ Entry point: ${entry.path} (from ${entry.source})
2143
+ entryPath = path7.join(targetDir, entry.path);
2144
+ process.stdout.write(chalk6.green(`✓ Entry point: ${entry.path} (from ${entry.source})
2518
2145
  `));
2519
2146
  }
2520
2147
  } catch (entryError) {
2521
2148
  const llmEntry = await runLlmFallback("Heuristics failed");
2522
2149
  if (llmEntry) {
2523
- entryPath = path9.join(targetDir, llmEntry);
2524
- process.stdout.write(chalk8.green(`✓ Entry point: ${llmEntry} (from LLM fallback)
2150
+ entryPath = path7.join(targetDir, llmEntry);
2151
+ process.stdout.write(chalk6.green(`✓ Entry point: ${llmEntry} (from LLM fallback)
2525
2152
  `));
2526
2153
  } else {
2527
- process.stdout.write(chalk8.red(`✗ Could not detect entry point (set OPENAI_API_KEY for smart fallback)
2154
+ process.stdout.write(chalk6.red(`✗ Could not detect entry point (set OPENAI_API_KEY for smart fallback)
2528
2155
  `));
2529
2156
  throw entryError;
2530
2157
  }
2531
2158
  }
2532
- process.stdout.write(chalk8.cyan(`> Analyzing documentation coverage...
2159
+ process.stdout.write(chalk6.cyan(`> Analyzing documentation coverage...
2533
2160
  `));
2534
2161
  let result;
2535
2162
  try {
2536
2163
  const resolveExternalTypes = !options.skipResolve;
2537
2164
  const doccov = createDocCov({ resolveExternalTypes });
2538
2165
  result = await doccov.analyzeFileWithDiagnostics(entryPath);
2539
- process.stdout.write(chalk8.green(`✓ Analysis complete
2166
+ process.stdout.write(chalk6.green(`✓ Analysis complete
2540
2167
  `));
2541
2168
  } catch (analysisError) {
2542
- process.stdout.write(chalk8.red(`✗ Analysis failed
2169
+ process.stdout.write(chalk6.red(`✗ Analysis failed
2543
2170
  `));
2544
2171
  throw analysisError;
2545
2172
  }
2546
2173
  const spec = result.spec;
2547
- const coverageScore = spec.docs?.coverageScore ?? 0;
2548
2174
  if (options.saveSpec) {
2549
- const specPath = path9.resolve(process.cwd(), options.saveSpec);
2550
- fs8.writeFileSync(specPath, JSON.stringify(spec, null, 2));
2551
- log(chalk8.green(`✓ Saved spec to ${options.saveSpec}`));
2552
- }
2553
- const undocumented = [];
2554
- const driftIssues = [];
2555
- for (const exp of spec.exports ?? []) {
2556
- const expDocs = exp.docs;
2557
- if (!expDocs)
2558
- continue;
2559
- if ((expDocs.missing?.length ?? 0) > 0 || (expDocs.coverageScore ?? 0) < 100) {
2560
- undocumented.push(exp.name);
2561
- }
2562
- for (const d of expDocs.drift ?? []) {
2563
- driftIssues.push({
2564
- export: exp.name,
2565
- type: d.type,
2566
- issue: d.issue
2567
- });
2568
- }
2175
+ const specPath = path7.resolve(process.cwd(), options.saveSpec);
2176
+ fs6.writeFileSync(specPath, JSON.stringify(spec, null, 2));
2177
+ log(chalk6.green(`✓ Saved spec to ${options.saveSpec}`));
2569
2178
  }
2179
+ const summary = extractSpecSummary(spec);
2570
2180
  const scanResult = {
2571
2181
  owner: parsed.owner,
2572
2182
  repo: parsed.repo,
2573
2183
  ref: parsed.ref,
2574
2184
  packageName,
2575
- coverage: coverageScore,
2576
- exportCount: spec.exports?.length ?? 0,
2577
- typeCount: spec.types?.length ?? 0,
2578
- driftCount: driftIssues.length,
2579
- undocumented,
2580
- drift: driftIssues
2185
+ coverage: summary.coverage,
2186
+ exportCount: summary.exportCount,
2187
+ typeCount: summary.typeCount,
2188
+ driftCount: summary.driftCount,
2189
+ undocumented: summary.undocumented,
2190
+ drift: summary.drift
2581
2191
  };
2582
2192
  if (options.output === "json") {
2583
2193
  log(JSON.stringify(scanResult, null, 2));
@@ -2585,186 +2195,68 @@ function registerScanCommand(program, dependencies = {}) {
2585
2195
  printTextResult(scanResult, log);
2586
2196
  }
2587
2197
  } catch (commandError) {
2588
- error(chalk8.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
2198
+ error(chalk6.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
2589
2199
  process.exitCode = 1;
2590
2200
  } finally {
2591
2201
  if (tempDir && options.cleanup !== false) {
2592
2202
  fsPromises.rm(tempDir, { recursive: true, force: true }).catch(() => {});
2593
2203
  } else if (tempDir) {
2594
- log(chalk8.gray(`Repo preserved at: ${tempDir}`));
2204
+ log(chalk6.gray(`Repo preserved at: ${tempDir}`));
2595
2205
  }
2596
2206
  }
2597
2207
  });
2598
2208
  }
2599
2209
  function printTextResult(result, log) {
2600
2210
  log("");
2601
- log(chalk8.bold("DocCov Scan Results"));
2211
+ log(chalk6.bold("DocCov Scan Results"));
2602
2212
  log("─".repeat(40));
2603
2213
  const repoName = result.packageName ? `${result.owner}/${result.repo} (${result.packageName})` : `${result.owner}/${result.repo}`;
2604
- log(`Repository: ${chalk8.cyan(repoName)}`);
2605
- log(`Branch: ${chalk8.gray(result.ref)}`);
2214
+ log(`Repository: ${chalk6.cyan(repoName)}`);
2215
+ log(`Branch: ${chalk6.gray(result.ref)}`);
2606
2216
  log("");
2607
- const coverageColor = result.coverage >= 80 ? chalk8.green : result.coverage >= 50 ? chalk8.yellow : chalk8.red;
2608
- log(chalk8.bold("Coverage"));
2217
+ const coverageColor = result.coverage >= 80 ? chalk6.green : result.coverage >= 50 ? chalk6.yellow : chalk6.red;
2218
+ log(chalk6.bold("Coverage"));
2609
2219
  log(` ${coverageColor(`${result.coverage}%`)}`);
2610
2220
  log("");
2611
- log(chalk8.bold("Stats"));
2221
+ log(chalk6.bold("Stats"));
2612
2222
  log(` ${result.exportCount} exports`);
2613
2223
  log(` ${result.typeCount} types`);
2614
2224
  log(` ${result.undocumented.length} undocumented`);
2615
2225
  log(` ${result.driftCount} drift issues`);
2616
2226
  if (result.undocumented.length > 0) {
2617
2227
  log("");
2618
- log(chalk8.bold("Undocumented Exports"));
2228
+ log(chalk6.bold("Undocumented Exports"));
2619
2229
  for (const name of result.undocumented.slice(0, 10)) {
2620
- log(chalk8.yellow(` ! ${name}`));
2230
+ log(chalk6.yellow(` ! ${name}`));
2621
2231
  }
2622
2232
  if (result.undocumented.length > 10) {
2623
- log(chalk8.gray(` ... and ${result.undocumented.length - 10} more`));
2233
+ log(chalk6.gray(` ... and ${result.undocumented.length - 10} more`));
2624
2234
  }
2625
2235
  }
2626
2236
  if (result.drift.length > 0) {
2627
2237
  log("");
2628
- log(chalk8.bold("Drift Issues"));
2238
+ log(chalk6.bold("Drift Issues"));
2629
2239
  for (const d of result.drift.slice(0, 5)) {
2630
- log(chalk8.red(` • ${d.export}: ${d.issue}`));
2240
+ log(chalk6.red(` • ${d.export}: ${d.issue}`));
2631
2241
  }
2632
2242
  if (result.drift.length > 5) {
2633
- log(chalk8.gray(` ... and ${result.drift.length - 5} more`));
2243
+ log(chalk6.gray(` ... and ${result.drift.length - 5} more`));
2634
2244
  }
2635
2245
  }
2636
2246
  log("");
2637
2247
  }
2638
2248
 
2639
- // src/commands/typecheck.ts
2640
- import * as fs9 from "node:fs";
2641
- import * as path10 from "node:path";
2642
- import {
2643
- DocCov as DocCov6,
2644
- detectEntryPoint as detectEntryPoint6,
2645
- detectMonorepo as detectMonorepo6,
2646
- findPackageByName as findPackageByName6,
2647
- NodeFileSystem as NodeFileSystem6,
2648
- typecheckExamples as typecheckExamples2
2649
- } from "@doccov/sdk";
2650
- import chalk9 from "chalk";
2651
- var defaultDependencies7 = {
2652
- createDocCov: (options) => new DocCov6(options),
2653
- log: console.log,
2654
- error: console.error
2655
- };
2656
- function registerTypecheckCommand(program, dependencies = {}) {
2657
- const { createDocCov, log, error } = {
2658
- ...defaultDependencies7,
2659
- ...dependencies
2660
- };
2661
- program.command("typecheck [entry]").description("Type-check @example blocks without executing them").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--skip-resolve", "Skip external type resolution").action(async (entry, options) => {
2662
- try {
2663
- let targetDir = options.cwd;
2664
- let entryFile = entry;
2665
- const fileSystem = new NodeFileSystem6(options.cwd);
2666
- if (options.package) {
2667
- const mono = await detectMonorepo6(fileSystem);
2668
- if (!mono.isMonorepo) {
2669
- throw new Error("Not a monorepo. Remove --package flag.");
2670
- }
2671
- const pkg = findPackageByName6(mono.packages, options.package);
2672
- if (!pkg) {
2673
- const available = mono.packages.map((p) => p.name).join(", ");
2674
- throw new Error(`Package "${options.package}" not found. Available: ${available}`);
2675
- }
2676
- targetDir = path10.join(options.cwd, pkg.path);
2677
- log(chalk9.gray(`Found package at ${pkg.path}`));
2678
- }
2679
- if (!entryFile) {
2680
- const targetFs = new NodeFileSystem6(targetDir);
2681
- const detected = await detectEntryPoint6(targetFs);
2682
- entryFile = path10.join(targetDir, detected.path);
2683
- log(chalk9.gray(`Auto-detected entry point: ${detected.path}`));
2684
- } else {
2685
- entryFile = path10.resolve(targetDir, entryFile);
2686
- if (fs9.existsSync(entryFile) && fs9.statSync(entryFile).isDirectory()) {
2687
- targetDir = entryFile;
2688
- const dirFs = new NodeFileSystem6(entryFile);
2689
- const detected = await detectEntryPoint6(dirFs);
2690
- entryFile = path10.join(entryFile, detected.path);
2691
- log(chalk9.gray(`Auto-detected entry point: ${detected.path}`));
2692
- }
2693
- }
2694
- const resolveExternalTypes = !options.skipResolve;
2695
- process.stdout.write(chalk9.cyan(`> Analyzing documentation...
2696
- `));
2697
- const doccov = createDocCov({ resolveExternalTypes });
2698
- const specResult = await doccov.analyzeFileWithDiagnostics(entryFile);
2699
- if (!specResult) {
2700
- throw new Error("Failed to analyze documentation.");
2701
- }
2702
- const allExamples = [];
2703
- for (const exp of specResult.spec.exports ?? []) {
2704
- if (exp.examples && exp.examples.length > 0) {
2705
- allExamples.push({ exportName: exp.name, examples: exp.examples });
2706
- }
2707
- }
2708
- if (allExamples.length === 0) {
2709
- log(chalk9.gray("No @example blocks found"));
2710
- return;
2711
- }
2712
- const totalExamples = allExamples.reduce((sum, e) => sum + e.examples.length, 0);
2713
- process.stdout.write(chalk9.cyan(`> Type-checking ${totalExamples} example(s)...
2714
- `));
2715
- const allErrors = [];
2716
- let passed = 0;
2717
- let failed = 0;
2718
- for (const { exportName, examples } of allExamples) {
2719
- const result = typecheckExamples2(examples, targetDir);
2720
- for (const err of result.errors) {
2721
- allErrors.push({ exportName, error: err });
2722
- }
2723
- passed += result.passed;
2724
- failed += result.failed;
2725
- }
2726
- if (allErrors.length === 0) {
2727
- log(chalk9.green(`✓ All ${totalExamples} example(s) passed type checking`));
2728
- return;
2729
- }
2730
- log("");
2731
- const byExport = new Map;
2732
- for (const { exportName, error: err } of allErrors) {
2733
- const existing = byExport.get(exportName) ?? [];
2734
- existing.push(err);
2735
- byExport.set(exportName, existing);
2736
- }
2737
- for (const [exportName, errors] of byExport) {
2738
- log(chalk9.red(`✗ ${exportName}`));
2739
- for (const err of errors) {
2740
- log(chalk9.gray(` @example block ${err.exampleIndex + 1}, line ${err.line}:`));
2741
- log(chalk9.red(` ${err.message}`));
2742
- }
2743
- log("");
2744
- }
2745
- log(chalk9.red(`${failed} example(s) failed`) + chalk9.gray(`, ${passed} passed`));
2746
- process.exit(1);
2747
- } catch (commandError) {
2748
- error(chalk9.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
2749
- process.exit(1);
2750
- }
2751
- });
2752
- }
2753
-
2754
2249
  // src/cli.ts
2755
2250
  var __filename2 = fileURLToPath(import.meta.url);
2756
- var __dirname2 = path11.dirname(__filename2);
2757
- var packageJson = JSON.parse(readFileSync5(path11.join(__dirname2, "../package.json"), "utf-8"));
2251
+ var __dirname2 = path8.dirname(__filename2);
2252
+ var packageJson = JSON.parse(readFileSync4(path8.join(__dirname2, "../package.json"), "utf-8"));
2758
2253
  var program = new Command;
2759
2254
  program.name("doccov").description("DocCov - Documentation coverage and drift detection for TypeScript").version(packageJson.version);
2760
2255
  registerGenerateCommand(program);
2761
2256
  registerCheckCommand(program);
2762
2257
  registerDiffCommand(program);
2763
2258
  registerInitCommand(program);
2764
- registerLintCommand(program);
2765
- registerReportCommand(program);
2766
2259
  registerScanCommand(program);
2767
- registerTypecheckCommand(program);
2768
2260
  program.command("*", { hidden: true }).action(() => {
2769
2261
  program.outputHelp();
2770
2262
  });