@doccov/cli 0.30.6 → 0.31.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/README.md +81 -10
- package/dist/cli.js +505 -155
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -37,12 +37,42 @@ doccov info src/index.ts
|
|
|
37
37
|
Analyze documentation coverage against thresholds.
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
doccov check src/index.ts --min-
|
|
40
|
+
doccov check src/index.ts --min-health 80
|
|
41
41
|
doccov check --format json -o report.json
|
|
42
42
|
doccov check --examples typecheck # Validate @example blocks
|
|
43
43
|
doccov check --fix # Auto-fix drift issues
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
#### Monorepo / Batch Mode
|
|
47
|
+
|
|
48
|
+
Analyze multiple packages at once using glob patterns or multiple targets:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Glob pattern - analyze all packages
|
|
52
|
+
doccov check "packages/*/src/index.ts"
|
|
53
|
+
|
|
54
|
+
# Multiple explicit targets
|
|
55
|
+
doccov check packages/server/src/index.ts packages/client/src/index.ts
|
|
56
|
+
|
|
57
|
+
# With options
|
|
58
|
+
doccov check "packages/*/src/index.ts" --format markdown --min-health 60
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Output shows per-package breakdown with aggregated totals:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
Documentation Coverage Report (3 packages)
|
|
65
|
+
|
|
66
|
+
| Package | Health | Exports | Drift |
|
|
67
|
+
|---------|--------|---------|-------|
|
|
68
|
+
| @pkg/server | 75% | 78 | 4 |
|
|
69
|
+
| @pkg/client | 82% | 45 | 2 |
|
|
70
|
+
| @pkg/core | 90% | 32 | 1 |
|
|
71
|
+
| Total | 81% | 155 | 7 |
|
|
72
|
+
|
|
73
|
+
✓ Check passed (health 81% >= 80%)
|
|
74
|
+
```
|
|
75
|
+
|
|
46
76
|
### spec
|
|
47
77
|
|
|
48
78
|
Generate specification files.
|
|
@@ -87,17 +117,58 @@ doccov trends --extended # Show velocity/projections
|
|
|
87
117
|
|
|
88
118
|
## Configuration
|
|
89
119
|
|
|
90
|
-
Create `doccov.config.
|
|
120
|
+
Create `doccov.config.ts` or use `doccov init`:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
// doccov.config.ts
|
|
124
|
+
import { defineConfig } from '@doccov/cli';
|
|
125
|
+
|
|
126
|
+
export default defineConfig({
|
|
127
|
+
check: {
|
|
128
|
+
minHealth: 80,
|
|
129
|
+
examples: ['presence', 'typecheck'],
|
|
130
|
+
|
|
131
|
+
// Documentation style presets
|
|
132
|
+
style: 'minimal', // 'minimal' | 'verbose' | 'types-only'
|
|
133
|
+
|
|
134
|
+
// Fine-grained requirements (override preset)
|
|
135
|
+
require: {
|
|
136
|
+
description: true,
|
|
137
|
+
params: false,
|
|
138
|
+
returns: false,
|
|
139
|
+
examples: false,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
docs: {
|
|
143
|
+
include: ['docs/**/*.md'],
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Style Presets
|
|
149
|
+
|
|
150
|
+
Different projects have different documentation standards. Use `style` to choose a preset:
|
|
151
|
+
|
|
152
|
+
| Preset | description | params | returns | examples |
|
|
153
|
+
|--------|-------------|--------|---------|----------|
|
|
154
|
+
| `minimal` | required | optional | optional | optional |
|
|
155
|
+
| `verbose` | required | required | required | optional |
|
|
156
|
+
| `types-only` | optional | optional | optional | optional |
|
|
157
|
+
|
|
158
|
+
- **minimal** (default): Only requires description. Good for projects relying on TypeScript types.
|
|
159
|
+
- **verbose**: Requires description, @param, and @returns. For comprehensive API documentation.
|
|
160
|
+
- **types-only**: No requirements. Score is 100% if exports exist. For TypeScript-first projects.
|
|
91
161
|
|
|
92
|
-
|
|
93
|
-
check:
|
|
94
|
-
minCoverage: 80
|
|
95
|
-
maxDrift: 10
|
|
96
|
-
examples: [presence, typecheck]
|
|
162
|
+
Use `require` to override individual rules from the preset:
|
|
97
163
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
164
|
+
```ts
|
|
165
|
+
// Start with minimal, but also require examples
|
|
166
|
+
{
|
|
167
|
+
style: 'minimal',
|
|
168
|
+
require: {
|
|
169
|
+
examples: true,
|
|
170
|
+
}
|
|
171
|
+
}
|
|
101
172
|
```
|
|
102
173
|
|
|
103
174
|
## Output Formats
|
package/dist/cli.js
CHANGED
|
@@ -75,19 +75,25 @@ ${formatIssues(issues)}`);
|
|
|
75
75
|
var defineConfig = (config) => config;
|
|
76
76
|
// src/cli.ts
|
|
77
77
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
78
|
-
import * as
|
|
78
|
+
import * as path13 from "node:path";
|
|
79
79
|
import { fileURLToPath } from "node:url";
|
|
80
80
|
import { Command } from "commander";
|
|
81
81
|
|
|
82
82
|
// src/commands/check/index.ts
|
|
83
|
+
import * as path8 from "node:path";
|
|
83
84
|
import {
|
|
85
|
+
aggregateResults,
|
|
84
86
|
buildDocCovSpec,
|
|
87
|
+
createPackageResult,
|
|
85
88
|
DocCov,
|
|
89
|
+
findEntryPointForFile,
|
|
90
|
+
IncrementalAnalyzer,
|
|
86
91
|
NodeFileSystem,
|
|
87
92
|
parseExamplesFlag,
|
|
88
93
|
resolveTarget
|
|
89
94
|
} from "@doccov/sdk";
|
|
90
|
-
import
|
|
95
|
+
import chalk7 from "chalk";
|
|
96
|
+
import { glob as glob2 } from "glob";
|
|
91
97
|
|
|
92
98
|
// src/utils/filter-options.ts
|
|
93
99
|
import { mergeFilters, parseListFlag } from "@doccov/sdk";
|
|
@@ -1060,6 +1066,37 @@ function renderDiffHtml(data, options = {}) {
|
|
|
1060
1066
|
</body>
|
|
1061
1067
|
</html>`;
|
|
1062
1068
|
}
|
|
1069
|
+
// src/reports/json.ts
|
|
1070
|
+
function formatCIJson(report, options = {}) {
|
|
1071
|
+
const minHealth = options.minHealth ?? 0;
|
|
1072
|
+
const health = report.health?.score ?? report.coverage.score;
|
|
1073
|
+
const healthPassed = health >= minHealth;
|
|
1074
|
+
const hasTypecheckErrors = options.hasTypecheckErrors ?? false;
|
|
1075
|
+
const hasRuntimeErrors = options.hasRuntimeErrors ?? false;
|
|
1076
|
+
const hasStaleRefs = options.hasStaleRefs ?? false;
|
|
1077
|
+
const success = healthPassed && !hasTypecheckErrors && !hasRuntimeErrors && !hasStaleRefs;
|
|
1078
|
+
return {
|
|
1079
|
+
success,
|
|
1080
|
+
health,
|
|
1081
|
+
thresholds: {
|
|
1082
|
+
health: {
|
|
1083
|
+
min: minHealth,
|
|
1084
|
+
actual: health,
|
|
1085
|
+
passed: healthPassed
|
|
1086
|
+
}
|
|
1087
|
+
},
|
|
1088
|
+
drift: {
|
|
1089
|
+
total: report.coverage.driftCount,
|
|
1090
|
+
fixable: report.coverage.driftSummary?.fixable ?? 0
|
|
1091
|
+
},
|
|
1092
|
+
exports: {
|
|
1093
|
+
total: report.coverage.totalExports,
|
|
1094
|
+
documented: report.coverage.documentedExports
|
|
1095
|
+
},
|
|
1096
|
+
exitCode: success ? 0 : 1,
|
|
1097
|
+
report
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1063
1100
|
// src/reports/markdown.ts
|
|
1064
1101
|
import { DRIFT_CATEGORY_LABELS } from "@doccov/sdk";
|
|
1065
1102
|
function bar2(pct, width = 10) {
|
|
@@ -1178,18 +1215,18 @@ function renderMarkdown(stats, options = {}) {
|
|
|
1178
1215
|
continue;
|
|
1179
1216
|
lines.push(`### ${DRIFT_CATEGORY_LABELS[category]}`);
|
|
1180
1217
|
lines.push("");
|
|
1181
|
-
lines.push("| Export | Issue |");
|
|
1182
|
-
lines.push("
|
|
1218
|
+
lines.push("| Export | Issue | Expected | Actual |");
|
|
1219
|
+
lines.push("|--------|-------|----------|--------|");
|
|
1183
1220
|
for (const d of issues.slice(0, Math.min(limit, 10))) {
|
|
1184
1221
|
const hint = d.suggestion ? ` → ${d.suggestion}` : "";
|
|
1185
|
-
lines.push(`| \`${d.exportName}\` | ${d.issue}${hint} |`);
|
|
1222
|
+
lines.push(`| \`${d.exportName}\` | ${d.issue}${hint} | ${d.expected ?? "-"} | ${d.actual ?? "-"} |`);
|
|
1186
1223
|
}
|
|
1187
1224
|
if (issues.length > 10) {
|
|
1188
1225
|
const moreText = `${issues.length - 10} more ${category} issues`;
|
|
1189
1226
|
if (reportUrl) {
|
|
1190
|
-
lines.push(`| [${moreText} →](${reportUrl}#drift-${category}) | |`);
|
|
1227
|
+
lines.push(`| [${moreText} →](${reportUrl}#drift-${category}) | | | |`);
|
|
1191
1228
|
} else {
|
|
1192
|
-
lines.push(`| ... | ${moreText} |`);
|
|
1229
|
+
lines.push(`| ... | ${moreText} | | |`);
|
|
1193
1230
|
}
|
|
1194
1231
|
}
|
|
1195
1232
|
lines.push("");
|
|
@@ -1244,6 +1281,38 @@ function renderMarkdown(stats, options = {}) {
|
|
|
1244
1281
|
return lines.join(`
|
|
1245
1282
|
`);
|
|
1246
1283
|
}
|
|
1284
|
+
function renderBatchMarkdown(batchResult, options = {}) {
|
|
1285
|
+
const { packages, aggregate } = batchResult;
|
|
1286
|
+
const lines = [];
|
|
1287
|
+
lines.push("# Documentation Coverage Report");
|
|
1288
|
+
lines.push("");
|
|
1289
|
+
lines.push(`## Summary (${packages.length} packages)`);
|
|
1290
|
+
lines.push("");
|
|
1291
|
+
lines.push("| Package | Health | Exports | Drift |");
|
|
1292
|
+
lines.push("|---------|--------|---------|-------|");
|
|
1293
|
+
for (const pkg of packages) {
|
|
1294
|
+
lines.push(`| ${pkg.name} | ${pkg.health}% | ${pkg.totalExports} | ${pkg.driftCount} |`);
|
|
1295
|
+
}
|
|
1296
|
+
lines.push(`| **Total** | **${aggregate.health}%** | **${aggregate.totalExports}** | **${aggregate.driftCount}** |`);
|
|
1297
|
+
lines.push("");
|
|
1298
|
+
for (const pkg of packages) {
|
|
1299
|
+
lines.push(`## ${pkg.name}`);
|
|
1300
|
+
lines.push("");
|
|
1301
|
+
lines.push(`**Health: ${pkg.health}%** \`${bar2(pkg.health)}\``);
|
|
1302
|
+
lines.push("");
|
|
1303
|
+
lines.push("| Metric | Value |");
|
|
1304
|
+
lines.push("|--------|-------|");
|
|
1305
|
+
lines.push(`| Exports | ${pkg.totalExports} |`);
|
|
1306
|
+
lines.push(`| Documented | ${pkg.documented} |`);
|
|
1307
|
+
lines.push(`| Coverage | ${pkg.coverageScore}% |`);
|
|
1308
|
+
lines.push(`| Drift | ${pkg.driftCount} |`);
|
|
1309
|
+
lines.push("");
|
|
1310
|
+
}
|
|
1311
|
+
lines.push("---");
|
|
1312
|
+
lines.push("*Generated by [DocCov](https://doccov.com)*");
|
|
1313
|
+
return lines.join(`
|
|
1314
|
+
`);
|
|
1315
|
+
}
|
|
1247
1316
|
// src/reports/stats.ts
|
|
1248
1317
|
import { getExportAnalysis, getExportDrift as getExportDrift2, isFixableDrift } from "@doccov/sdk";
|
|
1249
1318
|
import {
|
|
@@ -1291,6 +1360,8 @@ function computeStats(openpkg, doccov, options = {}) {
|
|
|
1291
1360
|
exportName: exp.name,
|
|
1292
1361
|
type: d.type,
|
|
1293
1362
|
issue: d.issue,
|
|
1363
|
+
expected: d.expected,
|
|
1364
|
+
actual: d.actual,
|
|
1294
1365
|
suggestion: d.suggestion
|
|
1295
1366
|
};
|
|
1296
1367
|
driftIssues.push(item);
|
|
@@ -1632,8 +1703,16 @@ function handleNonTextOutput(options, deps) {
|
|
|
1632
1703
|
const stats = computeStats(openpkg, doccov, { staleRefs });
|
|
1633
1704
|
const report = generateReportFromDocCov(openpkg, doccov);
|
|
1634
1705
|
const extendedReport = staleRefs.length > 0 ? { ...report, staleRefs } : report;
|
|
1635
|
-
const
|
|
1636
|
-
const
|
|
1706
|
+
const hasTypecheckErrors = typecheckErrors.length > 0;
|
|
1707
|
+
const hasRuntimeErrors = runtimeErrors > 0;
|
|
1708
|
+
const hasStaleRefs = staleRefs.length > 0;
|
|
1709
|
+
const ciReport = formatCIJson(extendedReport, {
|
|
1710
|
+
minHealth,
|
|
1711
|
+
hasTypecheckErrors,
|
|
1712
|
+
hasRuntimeErrors,
|
|
1713
|
+
hasStaleRefs
|
|
1714
|
+
});
|
|
1715
|
+
const jsonContent = JSON.stringify(ciReport, null, 2);
|
|
1637
1716
|
let formatContent;
|
|
1638
1717
|
switch (format) {
|
|
1639
1718
|
case "json":
|
|
@@ -1656,13 +1735,9 @@ function handleNonTextOutput(options, deps) {
|
|
|
1656
1735
|
cwd
|
|
1657
1736
|
});
|
|
1658
1737
|
}
|
|
1659
|
-
const healthFailed = healthScore < minHealth;
|
|
1660
1738
|
const apiSurfaceScore = doccov.apiSurface?.completeness ?? 100;
|
|
1661
1739
|
const apiSurfaceFailed = minApiSurface !== undefined && apiSurfaceScore < minApiSurface;
|
|
1662
|
-
|
|
1663
|
-
const hasRuntimeErrors = runtimeErrors > 0;
|
|
1664
|
-
const hasStaleRefs = staleRefs.length > 0;
|
|
1665
|
-
return !(healthFailed || apiSurfaceFailed || hasTypecheckErrors || hasRuntimeErrors || hasStaleRefs);
|
|
1740
|
+
return ciReport.success && !apiSurfaceFailed;
|
|
1666
1741
|
}
|
|
1667
1742
|
function displayApiSurfaceOutput(doccov, deps) {
|
|
1668
1743
|
const { log } = deps;
|
|
@@ -1710,6 +1785,100 @@ function displayApiSurfaceOutput(doccov, deps) {
|
|
|
1710
1785
|
log(colors.success(`${sym.success} All referenced types are exported`));
|
|
1711
1786
|
}
|
|
1712
1787
|
}
|
|
1788
|
+
function displayBatchTextOutput(options, deps) {
|
|
1789
|
+
const { batchResult, minHealth, verbose } = options;
|
|
1790
|
+
const { log } = deps;
|
|
1791
|
+
const { packages, aggregate } = batchResult;
|
|
1792
|
+
const sym = getSymbols(supportsUnicode());
|
|
1793
|
+
log("");
|
|
1794
|
+
log(colors.bold(`Documentation Coverage Report (${packages.length} packages)`));
|
|
1795
|
+
log("");
|
|
1796
|
+
log("| Package | Health | Exports | Drift |");
|
|
1797
|
+
log("|---------|--------|---------|-------|");
|
|
1798
|
+
for (const pkg of packages) {
|
|
1799
|
+
const healthColor = pkg.health >= 80 ? colors.success : pkg.health >= 60 ? colors.warning : colors.error;
|
|
1800
|
+
log(`| ${pkg.name} | ${healthColor(`${pkg.health}%`)} | ${pkg.totalExports} | ${pkg.driftCount} |`);
|
|
1801
|
+
}
|
|
1802
|
+
const totalHealthColor = aggregate.health >= 80 ? colors.success : aggregate.health >= 60 ? colors.warning : colors.error;
|
|
1803
|
+
log(`| ${colors.bold("Total")} | ${totalHealthColor(colors.bold(`${aggregate.health}%`))} | ${colors.bold(String(aggregate.totalExports))} | ${colors.bold(String(aggregate.driftCount))} |`);
|
|
1804
|
+
log("");
|
|
1805
|
+
const passed = aggregate.health >= minHealth;
|
|
1806
|
+
if (passed) {
|
|
1807
|
+
log(colors.success(`${sym.success} Check passed (health ${aggregate.health}% >= ${minHealth}%)`));
|
|
1808
|
+
} else {
|
|
1809
|
+
log(colors.error(`${sym.error} Health ${aggregate.health}% below minimum ${minHealth}%`));
|
|
1810
|
+
}
|
|
1811
|
+
if (verbose) {
|
|
1812
|
+
log("");
|
|
1813
|
+
for (const pkg of packages) {
|
|
1814
|
+
const health = pkg.doccov.summary.health;
|
|
1815
|
+
if (health) {
|
|
1816
|
+
log(colors.bold(`${pkg.name}:`));
|
|
1817
|
+
displayHealthVerbose(health, log);
|
|
1818
|
+
log("");
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
return passed;
|
|
1823
|
+
}
|
|
1824
|
+
function handleBatchNonTextOutput(options, deps) {
|
|
1825
|
+
const { format, batchResult, minHealth, limit, stdout, outputPath, cwd } = options;
|
|
1826
|
+
const { log } = deps;
|
|
1827
|
+
const healthPassed = batchResult.aggregate.health >= minHealth;
|
|
1828
|
+
const ciBatchReport = {
|
|
1829
|
+
success: healthPassed,
|
|
1830
|
+
health: batchResult.aggregate.health,
|
|
1831
|
+
thresholds: {
|
|
1832
|
+
health: {
|
|
1833
|
+
min: minHealth,
|
|
1834
|
+
actual: batchResult.aggregate.health,
|
|
1835
|
+
passed: healthPassed
|
|
1836
|
+
}
|
|
1837
|
+
},
|
|
1838
|
+
drift: {
|
|
1839
|
+
total: batchResult.aggregate.driftCount
|
|
1840
|
+
},
|
|
1841
|
+
exports: {
|
|
1842
|
+
total: batchResult.aggregate.totalExports,
|
|
1843
|
+
documented: batchResult.aggregate.documented
|
|
1844
|
+
},
|
|
1845
|
+
exitCode: healthPassed ? 0 : 1,
|
|
1846
|
+
packages: batchResult.packages.map((p) => ({
|
|
1847
|
+
name: p.name,
|
|
1848
|
+
version: p.version,
|
|
1849
|
+
entryPath: p.entryPath,
|
|
1850
|
+
totalExports: p.totalExports,
|
|
1851
|
+
documented: p.documented,
|
|
1852
|
+
health: p.health,
|
|
1853
|
+
driftCount: p.driftCount,
|
|
1854
|
+
coverageScore: p.coverageScore
|
|
1855
|
+
})),
|
|
1856
|
+
aggregate: batchResult.aggregate
|
|
1857
|
+
};
|
|
1858
|
+
let formatContent;
|
|
1859
|
+
switch (format) {
|
|
1860
|
+
case "json":
|
|
1861
|
+
formatContent = JSON.stringify(ciBatchReport, null, 2);
|
|
1862
|
+
break;
|
|
1863
|
+
case "markdown":
|
|
1864
|
+
formatContent = renderBatchMarkdown(batchResult, { limit });
|
|
1865
|
+
break;
|
|
1866
|
+
default:
|
|
1867
|
+
throw new Error(`Unknown format: ${format}`);
|
|
1868
|
+
}
|
|
1869
|
+
if (stdout) {
|
|
1870
|
+
log(formatContent);
|
|
1871
|
+
} else {
|
|
1872
|
+
writeReports({
|
|
1873
|
+
format,
|
|
1874
|
+
formatContent,
|
|
1875
|
+
jsonContent: JSON.stringify(ciBatchReport, null, 2),
|
|
1876
|
+
outputPath,
|
|
1877
|
+
cwd
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
return healthPassed;
|
|
1881
|
+
}
|
|
1713
1882
|
|
|
1714
1883
|
// src/commands/check/validation.ts
|
|
1715
1884
|
import { validateExamples } from "@doccov/sdk";
|
|
@@ -3718,6 +3887,48 @@ async function validateMarkdownDocs(options) {
|
|
|
3718
3887
|
return staleRefs;
|
|
3719
3888
|
}
|
|
3720
3889
|
|
|
3890
|
+
// src/utils/signal-handler.ts
|
|
3891
|
+
import chalk6 from "chalk";
|
|
3892
|
+
var state = {
|
|
3893
|
+
handlers: [],
|
|
3894
|
+
registered: false
|
|
3895
|
+
};
|
|
3896
|
+
function registerSignalHandlers() {
|
|
3897
|
+
if (state.registered)
|
|
3898
|
+
return;
|
|
3899
|
+
const handler = (signal) => {
|
|
3900
|
+
console.error(chalk6.yellow(`
|
|
3901
|
+
⚠️ Received ${signal}, shutting down...`));
|
|
3902
|
+
if (state.incrementalAnalyzer && state.onPartialResults) {
|
|
3903
|
+
try {
|
|
3904
|
+
const partial = state.incrementalAnalyzer.getPartialResultsSync();
|
|
3905
|
+
if (partial.results.length > 0) {
|
|
3906
|
+
console.error(chalk6.yellow(`
|
|
3907
|
+
Partial results available: ${partial.results.length} exports analyzed`));
|
|
3908
|
+
state.onPartialResults(partial);
|
|
3909
|
+
}
|
|
3910
|
+
} catch {}
|
|
3911
|
+
}
|
|
3912
|
+
for (const h of state.handlers) {
|
|
3913
|
+
try {
|
|
3914
|
+
h();
|
|
3915
|
+
} catch {}
|
|
3916
|
+
}
|
|
3917
|
+
process.exit(1);
|
|
3918
|
+
};
|
|
3919
|
+
process.on("SIGINT", () => handler("SIGINT"));
|
|
3920
|
+
process.on("SIGTERM", () => handler("SIGTERM"));
|
|
3921
|
+
state.registered = true;
|
|
3922
|
+
}
|
|
3923
|
+
function setIncrementalAnalyzer(analyzer, onPartialResults) {
|
|
3924
|
+
state.incrementalAnalyzer = analyzer;
|
|
3925
|
+
state.onPartialResults = onPartialResults;
|
|
3926
|
+
}
|
|
3927
|
+
function clearIncrementalAnalyzer() {
|
|
3928
|
+
state.incrementalAnalyzer = undefined;
|
|
3929
|
+
state.onPartialResults = undefined;
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3721
3932
|
// src/commands/check/index.ts
|
|
3722
3933
|
var defaultDependencies = {
|
|
3723
3934
|
createDocCov: (options) => new DocCov(options),
|
|
@@ -3729,16 +3940,24 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
3729
3940
|
...defaultDependencies,
|
|
3730
3941
|
...dependencies
|
|
3731
3942
|
};
|
|
3732
|
-
program.command("check [
|
|
3943
|
+
program.command("check [targets...]").description("Check documentation coverage and output reports").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--min-health <percentage>", "Minimum health score (0-100)", (value) => Number(value)).option("--examples [mode]", "Example validation: presence, typecheck, run (comma-separated). Bare flag runs all.").option("--skip-resolve", "Skip external type resolution from node_modules").option("--docs <glob>", "Glob pattern for markdown docs to check for stale refs", collect, []).option("--fix", "Auto-fix drift issues").option("--preview", "Preview fixes with diff output (implies --fix)").option("--format <format>", "Output format: text, json, markdown", "text").option("-o, --output <file>", "Custom output path (overrides default .doccov/ path)").option("--stdout", "Output to stdout instead of writing to .doccov/").option("--limit <n>", "Max exports to show in report tables", "20").option("--max-type-depth <number>", "Maximum depth for type conversion (default: 20)", (value) => {
|
|
3733
3944
|
const n = parseInt(value, 10);
|
|
3734
3945
|
if (Number.isNaN(n) || n < 1)
|
|
3735
3946
|
throw new Error("--max-type-depth must be a positive integer");
|
|
3736
3947
|
return n;
|
|
3737
|
-
}).option("--no-cache", "Bypass spec cache and force regeneration").option("--visibility <tags>", "Filter by release stage: public,beta,alpha,internal (comma-separated)").option("--api-surface", "Show only API surface / forgotten exports info").option("-v, --verbose", "Show detailed output including forgotten exports").action(async (
|
|
3948
|
+
}).option("--no-cache", "Bypass spec cache and force regeneration").option("--visibility <tags>", "Filter by release stage: public,beta,alpha,internal (comma-separated)").option("--api-surface", "Show only API surface / forgotten exports info").option("-v, --verbose", "Show detailed output including forgotten exports").action(async (targets, options) => {
|
|
3949
|
+
registerSignalHandlers();
|
|
3738
3950
|
try {
|
|
3739
3951
|
const spin = spinner("Analyzing...");
|
|
3952
|
+
const resolvedTargets = await expandGlobTargets(targets, options.cwd);
|
|
3953
|
+
const isBatchMode = resolvedTargets.length > 1;
|
|
3740
3954
|
let validations = parseExamplesFlag(options.examples);
|
|
3741
3955
|
let hasExamples = validations.length > 0;
|
|
3956
|
+
if (isBatchMode) {
|
|
3957
|
+
await runBatchAnalysis(resolvedTargets, options, { createDocCov, log, error, spin });
|
|
3958
|
+
return;
|
|
3959
|
+
}
|
|
3960
|
+
const entry = resolvedTargets[0];
|
|
3742
3961
|
const fileSystem = new NodeFileSystem(options.cwd);
|
|
3743
3962
|
const resolved = await resolveTarget(fileSystem, {
|
|
3744
3963
|
cwd: options.cwd,
|
|
@@ -3771,7 +3990,7 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
3771
3990
|
};
|
|
3772
3991
|
const resolvedFilters = mergeFilterOptions(config, cliFilters);
|
|
3773
3992
|
if (resolvedFilters.visibility) {
|
|
3774
|
-
log(
|
|
3993
|
+
log(chalk7.dim(`Filtering by visibility: ${resolvedFilters.visibility.join(", ")}`));
|
|
3775
3994
|
}
|
|
3776
3995
|
const resolveExternalTypes = !options.skipResolve;
|
|
3777
3996
|
const analyzer = createDocCov({
|
|
@@ -3787,13 +4006,51 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
3787
4006
|
throw new Error("Failed to analyze documentation coverage.");
|
|
3788
4007
|
}
|
|
3789
4008
|
const openpkg = specResult.spec;
|
|
3790
|
-
|
|
4009
|
+
spin.update("Building coverage spec...");
|
|
4010
|
+
let entryExportNames;
|
|
4011
|
+
const entryResult = await findEntryPointForFile(fileSystem, entryFile);
|
|
4012
|
+
if (entryResult) {
|
|
4013
|
+
const pkgEntryPath = path8.resolve(entryResult.packagePath, entryResult.entryPoint.path);
|
|
4014
|
+
const isSubFile = path8.resolve(entryFile) !== pkgEntryPath;
|
|
4015
|
+
if (isSubFile) {
|
|
4016
|
+
const entryAnalyzer = createDocCov({
|
|
4017
|
+
resolveExternalTypes: false,
|
|
4018
|
+
useCache: options.cache !== false,
|
|
4019
|
+
cwd: entryResult.packagePath
|
|
4020
|
+
});
|
|
4021
|
+
const entrySpec = await entryAnalyzer.analyzeFileWithDiagnostics(pkgEntryPath);
|
|
4022
|
+
if (entrySpec?.spec?.exports) {
|
|
4023
|
+
entryExportNames = entrySpec.spec.exports.map((e) => e.name);
|
|
4024
|
+
}
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
const incrementalAnalyzer = new IncrementalAnalyzer;
|
|
4028
|
+
setIncrementalAnalyzer(incrementalAnalyzer, (partial) => {
|
|
4029
|
+
log(chalk7.yellow(`
|
|
4030
|
+
⚠️ Partial results (${partial.results.length} exports):`));
|
|
4031
|
+
for (const r of partial.results.slice(-5)) {
|
|
4032
|
+
log(chalk7.dim(` - ${r.name}: ${r.coverageScore}%`));
|
|
4033
|
+
}
|
|
4034
|
+
if (partial.results.length > 5) {
|
|
4035
|
+
log(chalk7.dim(` ... and ${partial.results.length - 5} more`));
|
|
4036
|
+
}
|
|
4037
|
+
});
|
|
4038
|
+
const doccov = await buildDocCovSpec({
|
|
3791
4039
|
openpkg,
|
|
3792
4040
|
openpkgPath: entryFile,
|
|
3793
4041
|
packagePath: targetDir,
|
|
3794
4042
|
forgottenExports: specResult.forgottenExports,
|
|
3795
|
-
apiSurfaceIgnore
|
|
4043
|
+
apiSurfaceIgnore,
|
|
4044
|
+
entryExportNames,
|
|
4045
|
+
onProgress: (current, total, item) => {
|
|
4046
|
+
spin.setDetail(`${current}/${total}: ${item}`);
|
|
4047
|
+
},
|
|
4048
|
+
onExportAnalyzed: async (id, name, analysis) => {
|
|
4049
|
+
await incrementalAnalyzer.writeExportAnalysis(id, name, analysis);
|
|
4050
|
+
}
|
|
3796
4051
|
});
|
|
4052
|
+
clearIncrementalAnalyzer();
|
|
4053
|
+
await incrementalAnalyzer.cleanup();
|
|
3797
4054
|
const format = options.format ?? "text";
|
|
3798
4055
|
const specWarnings = specResult.diagnostics.filter((d) => d.severity === "warning");
|
|
3799
4056
|
const specInfos = specResult.diagnostics.filter((d) => d.severity === "info" && !d.code?.startsWith("EXTERNAL_TYPE"));
|
|
@@ -3879,14 +4136,103 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
3879
4136
|
process.exit(1);
|
|
3880
4137
|
}
|
|
3881
4138
|
} catch (commandError) {
|
|
3882
|
-
error(
|
|
4139
|
+
error(chalk7.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
|
|
3883
4140
|
process.exit(1);
|
|
3884
4141
|
}
|
|
3885
4142
|
});
|
|
3886
4143
|
}
|
|
4144
|
+
async function expandGlobTargets(targets, cwd) {
|
|
4145
|
+
if (targets.length === 0) {
|
|
4146
|
+
return [];
|
|
4147
|
+
}
|
|
4148
|
+
const expanded = [];
|
|
4149
|
+
for (const target of targets) {
|
|
4150
|
+
if (target.includes("*")) {
|
|
4151
|
+
const matches = await glob2(target, { cwd, nodir: true });
|
|
4152
|
+
expanded.push(...matches.map((m) => path8.resolve(cwd, m)));
|
|
4153
|
+
} else {
|
|
4154
|
+
expanded.push(path8.resolve(cwd, target));
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
return expanded;
|
|
4158
|
+
}
|
|
4159
|
+
async function runBatchAnalysis(targets, options, deps) {
|
|
4160
|
+
const { createDocCov, log, error, spin } = deps;
|
|
4161
|
+
const fileSystem = new NodeFileSystem(options.cwd);
|
|
4162
|
+
const packageResults = [];
|
|
4163
|
+
for (let i = 0;i < targets.length; i++) {
|
|
4164
|
+
const target = targets[i];
|
|
4165
|
+
spin.update(`Analyzing ${i + 1}/${targets.length}: ${path8.basename(target)}...`);
|
|
4166
|
+
try {
|
|
4167
|
+
const resolved = await resolveTarget(fileSystem, {
|
|
4168
|
+
cwd: options.cwd,
|
|
4169
|
+
entry: target
|
|
4170
|
+
});
|
|
4171
|
+
const { targetDir, entryFile } = resolved;
|
|
4172
|
+
const config2 = await loadDocCovConfig(targetDir);
|
|
4173
|
+
const resolveExternalTypes = !options.skipResolve;
|
|
4174
|
+
const analyzer = createDocCov({
|
|
4175
|
+
resolveExternalTypes,
|
|
4176
|
+
maxDepth: options.maxTypeDepth,
|
|
4177
|
+
useCache: options.cache !== false,
|
|
4178
|
+
cwd: targetDir
|
|
4179
|
+
});
|
|
4180
|
+
const specResult = await analyzer.analyzeFileWithDiagnostics(entryFile);
|
|
4181
|
+
if (!specResult) {
|
|
4182
|
+
log(chalk7.yellow(` Skipping ${target}: analysis failed`));
|
|
4183
|
+
continue;
|
|
4184
|
+
}
|
|
4185
|
+
const openpkg = specResult.spec;
|
|
4186
|
+
const doccov = await buildDocCovSpec({
|
|
4187
|
+
openpkg,
|
|
4188
|
+
openpkgPath: entryFile,
|
|
4189
|
+
packagePath: targetDir,
|
|
4190
|
+
forgottenExports: specResult.forgottenExports
|
|
4191
|
+
});
|
|
4192
|
+
packageResults.push(createPackageResult(openpkg, doccov, entryFile));
|
|
4193
|
+
} catch (err) {
|
|
4194
|
+
log(chalk7.yellow(` Skipping ${target}: ${err instanceof Error ? err.message : err}`));
|
|
4195
|
+
}
|
|
4196
|
+
}
|
|
4197
|
+
if (packageResults.length === 0) {
|
|
4198
|
+
spin.fail("No packages analyzed successfully");
|
|
4199
|
+
process.exit(1);
|
|
4200
|
+
}
|
|
4201
|
+
spin.success(`Analyzed ${packageResults.length} package(s)`);
|
|
4202
|
+
const batchResult = aggregateResults(packageResults);
|
|
4203
|
+
const firstTargetDir = path8.dirname(targets[0]);
|
|
4204
|
+
const config = await loadDocCovConfig(firstTargetDir);
|
|
4205
|
+
const DEFAULT_MIN_HEALTH = 80;
|
|
4206
|
+
const minHealthRaw = options.minHealth ?? config?.check?.minHealth ?? DEFAULT_MIN_HEALTH;
|
|
4207
|
+
const minHealth = clampPercentage(minHealthRaw);
|
|
4208
|
+
const format = options.format ?? "text";
|
|
4209
|
+
if (format !== "text") {
|
|
4210
|
+
const passed2 = handleBatchNonTextOutput({
|
|
4211
|
+
format,
|
|
4212
|
+
batchResult,
|
|
4213
|
+
minHealth,
|
|
4214
|
+
limit: parseInt(options.limit, 10) || 20,
|
|
4215
|
+
stdout: options.stdout,
|
|
4216
|
+
outputPath: options.output,
|
|
4217
|
+
cwd: options.cwd
|
|
4218
|
+
}, { log });
|
|
4219
|
+
if (!passed2) {
|
|
4220
|
+
process.exit(1);
|
|
4221
|
+
}
|
|
4222
|
+
return;
|
|
4223
|
+
}
|
|
4224
|
+
const passed = displayBatchTextOutput({
|
|
4225
|
+
batchResult,
|
|
4226
|
+
minHealth,
|
|
4227
|
+
verbose: options.verbose ?? false
|
|
4228
|
+
}, { log });
|
|
4229
|
+
if (!passed) {
|
|
4230
|
+
process.exit(1);
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
3887
4233
|
// src/commands/diff.ts
|
|
3888
4234
|
import * as fs5 from "node:fs";
|
|
3889
|
-
import * as
|
|
4235
|
+
import * as path9 from "node:path";
|
|
3890
4236
|
import {
|
|
3891
4237
|
diffSpecWithDocs,
|
|
3892
4238
|
ensureSpecCoverage,
|
|
@@ -3897,8 +4243,8 @@ import {
|
|
|
3897
4243
|
parseMarkdownFiles as parseMarkdownFiles2
|
|
3898
4244
|
} from "@doccov/sdk";
|
|
3899
4245
|
import { calculateNextVersion, recommendSemverBump } from "@openpkg-ts/spec";
|
|
3900
|
-
import
|
|
3901
|
-
import { glob as
|
|
4246
|
+
import chalk8 from "chalk";
|
|
4247
|
+
import { glob as glob3 } from "glob";
|
|
3902
4248
|
var defaultDependencies2 = {
|
|
3903
4249
|
readFileSync: fs5.readFileSync,
|
|
3904
4250
|
log: console.log,
|
|
@@ -3939,7 +4285,7 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
3939
4285
|
const baseHash = hashString(JSON.stringify(baseSpec));
|
|
3940
4286
|
const headHash = hashString(JSON.stringify(headSpec));
|
|
3941
4287
|
const cacheEnabled = options.cache !== false;
|
|
3942
|
-
const cachedReportPath =
|
|
4288
|
+
const cachedReportPath = path9.resolve(options.cwd, getDiffReportPath(baseHash, headHash, "json"));
|
|
3943
4289
|
let diff;
|
|
3944
4290
|
let fromCache = false;
|
|
3945
4291
|
if (cacheEnabled && fs5.existsSync(cachedReportPath)) {
|
|
@@ -3972,9 +4318,9 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
3972
4318
|
}, null, 2));
|
|
3973
4319
|
} else {
|
|
3974
4320
|
log("");
|
|
3975
|
-
log(
|
|
4321
|
+
log(chalk8.bold("Semver Recommendation"));
|
|
3976
4322
|
log(` Current version: ${currentVersion}`);
|
|
3977
|
-
log(` Recommended: ${
|
|
4323
|
+
log(` Recommended: ${chalk8.cyan(nextVersion)} (${chalk8.yellow(recommendation.bump.toUpperCase())})`);
|
|
3978
4324
|
log(` Reason: ${recommendation.reason}`);
|
|
3979
4325
|
}
|
|
3980
4326
|
return;
|
|
@@ -3982,8 +4328,8 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
3982
4328
|
const format = options.format ?? "text";
|
|
3983
4329
|
const limit = parseInt(options.limit, 10) || 10;
|
|
3984
4330
|
const checks = getStrictChecks(options.strict);
|
|
3985
|
-
const baseName =
|
|
3986
|
-
const headName =
|
|
4331
|
+
const baseName = path9.basename(baseFile);
|
|
4332
|
+
const headName = path9.basename(headFile);
|
|
3987
4333
|
const reportData = {
|
|
3988
4334
|
baseName,
|
|
3989
4335
|
headName,
|
|
@@ -4004,8 +4350,8 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
4004
4350
|
silent: true
|
|
4005
4351
|
});
|
|
4006
4352
|
}
|
|
4007
|
-
const cacheNote = fromCache ?
|
|
4008
|
-
log(
|
|
4353
|
+
const cacheNote = fromCache ? chalk8.cyan(" (cached)") : "";
|
|
4354
|
+
log(chalk8.dim(`Report: ${jsonPath}`) + cacheNote);
|
|
4009
4355
|
}
|
|
4010
4356
|
break;
|
|
4011
4357
|
case "json": {
|
|
@@ -4063,18 +4409,18 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
4063
4409
|
checks
|
|
4064
4410
|
});
|
|
4065
4411
|
if (failures.length > 0) {
|
|
4066
|
-
log(
|
|
4412
|
+
log(chalk8.red(`
|
|
4067
4413
|
✗ Check failed`));
|
|
4068
4414
|
for (const f of failures) {
|
|
4069
|
-
log(
|
|
4415
|
+
log(chalk8.red(` - ${f}`));
|
|
4070
4416
|
}
|
|
4071
4417
|
process.exitCode = 1;
|
|
4072
4418
|
} else if (options.strict || minCoverage !== undefined || maxDrift !== undefined) {
|
|
4073
|
-
log(
|
|
4419
|
+
log(chalk8.green(`
|
|
4074
4420
|
✓ All checks passed`));
|
|
4075
4421
|
}
|
|
4076
4422
|
} catch (commandError) {
|
|
4077
|
-
error(
|
|
4423
|
+
error(chalk8.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
|
|
4078
4424
|
process.exitCode = 1;
|
|
4079
4425
|
}
|
|
4080
4426
|
});
|
|
@@ -4085,7 +4431,7 @@ function collect2(value, previous) {
|
|
|
4085
4431
|
async function loadMarkdownFiles2(patterns) {
|
|
4086
4432
|
const files = [];
|
|
4087
4433
|
for (const pattern of patterns) {
|
|
4088
|
-
const matches = await
|
|
4434
|
+
const matches = await glob3(pattern, { nodir: true });
|
|
4089
4435
|
for (const filePath of matches) {
|
|
4090
4436
|
try {
|
|
4091
4437
|
const content = fs5.readFileSync(filePath, "utf-8");
|
|
@@ -4101,7 +4447,7 @@ async function generateDiff(baseSpec, headSpec, options, config, log) {
|
|
|
4101
4447
|
if (!docsPatterns || docsPatterns.length === 0) {
|
|
4102
4448
|
if (config?.docs?.include) {
|
|
4103
4449
|
docsPatterns = config.docs.include;
|
|
4104
|
-
log(
|
|
4450
|
+
log(chalk8.gray(`Using docs patterns from config: ${docsPatterns.join(", ")}`));
|
|
4105
4451
|
}
|
|
4106
4452
|
}
|
|
4107
4453
|
if (docsPatterns && docsPatterns.length > 0) {
|
|
@@ -4110,7 +4456,7 @@ async function generateDiff(baseSpec, headSpec, options, config, log) {
|
|
|
4110
4456
|
return diffSpecWithDocs(baseSpec, headSpec, { markdownFiles });
|
|
4111
4457
|
}
|
|
4112
4458
|
function loadSpec(filePath, readFileSync4) {
|
|
4113
|
-
const resolvedPath =
|
|
4459
|
+
const resolvedPath = path9.resolve(filePath);
|
|
4114
4460
|
if (!fs5.existsSync(resolvedPath)) {
|
|
4115
4461
|
throw new Error(`File not found: ${filePath}`);
|
|
4116
4462
|
}
|
|
@@ -4124,37 +4470,37 @@ function loadSpec(filePath, readFileSync4) {
|
|
|
4124
4470
|
}
|
|
4125
4471
|
function printSummary(diff, baseName, headName, fromCache, log) {
|
|
4126
4472
|
log("");
|
|
4127
|
-
const cacheIndicator = fromCache ?
|
|
4128
|
-
log(
|
|
4473
|
+
const cacheIndicator = fromCache ? chalk8.cyan(" (cached)") : "";
|
|
4474
|
+
log(chalk8.bold(`Comparing: ${baseName} → ${headName}`) + cacheIndicator);
|
|
4129
4475
|
log("─".repeat(40));
|
|
4130
4476
|
log("");
|
|
4131
|
-
const coverageColor = diff.coverageDelta > 0 ?
|
|
4477
|
+
const coverageColor = diff.coverageDelta > 0 ? chalk8.green : diff.coverageDelta < 0 ? chalk8.red : chalk8.gray;
|
|
4132
4478
|
const coverageSign = diff.coverageDelta > 0 ? "+" : "";
|
|
4133
4479
|
log(` Coverage: ${diff.oldCoverage}% → ${diff.newCoverage}% ${coverageColor(`(${coverageSign}${diff.coverageDelta}%)`)}`);
|
|
4134
4480
|
const breakingCount = diff.breaking.length;
|
|
4135
4481
|
const highSeverity = diff.categorizedBreaking?.filter((c) => c.severity === "high").length ?? 0;
|
|
4136
4482
|
if (breakingCount > 0) {
|
|
4137
|
-
const severityNote = highSeverity > 0 ?
|
|
4138
|
-
log(` Breaking: ${
|
|
4483
|
+
const severityNote = highSeverity > 0 ? chalk8.red(` (${highSeverity} high severity)`) : "";
|
|
4484
|
+
log(` Breaking: ${chalk8.red(breakingCount)} changes${severityNote}`);
|
|
4139
4485
|
} else {
|
|
4140
|
-
log(` Breaking: ${
|
|
4486
|
+
log(` Breaking: ${chalk8.green("0")} changes`);
|
|
4141
4487
|
}
|
|
4142
4488
|
const newCount = diff.nonBreaking.length;
|
|
4143
4489
|
const undocCount = diff.newUndocumented.length;
|
|
4144
4490
|
if (newCount > 0) {
|
|
4145
|
-
const undocNote = undocCount > 0 ?
|
|
4146
|
-
log(` New: ${
|
|
4491
|
+
const undocNote = undocCount > 0 ? chalk8.yellow(` (${undocCount} undocumented)`) : "";
|
|
4492
|
+
log(` New: ${chalk8.green(newCount)} exports${undocNote}`);
|
|
4147
4493
|
}
|
|
4148
4494
|
if (diff.driftIntroduced > 0 || diff.driftResolved > 0) {
|
|
4149
4495
|
const parts = [];
|
|
4150
4496
|
if (diff.driftIntroduced > 0)
|
|
4151
|
-
parts.push(
|
|
4497
|
+
parts.push(chalk8.red(`+${diff.driftIntroduced}`));
|
|
4152
4498
|
if (diff.driftResolved > 0)
|
|
4153
|
-
parts.push(
|
|
4499
|
+
parts.push(chalk8.green(`-${diff.driftResolved}`));
|
|
4154
4500
|
log(` Drift: ${parts.join(", ")}`);
|
|
4155
4501
|
}
|
|
4156
4502
|
const recommendation = recommendSemverBump(diff);
|
|
4157
|
-
const bumpColor = recommendation.bump === "major" ?
|
|
4503
|
+
const bumpColor = recommendation.bump === "major" ? chalk8.red : recommendation.bump === "minor" ? chalk8.yellow : chalk8.green;
|
|
4158
4504
|
log(` Semver: ${bumpColor(recommendation.bump.toUpperCase())} (${recommendation.reason})`);
|
|
4159
4505
|
log("");
|
|
4160
4506
|
}
|
|
@@ -4236,8 +4582,8 @@ function printGitHubAnnotations(diff, log) {
|
|
|
4236
4582
|
|
|
4237
4583
|
// src/commands/init.ts
|
|
4238
4584
|
import * as fs6 from "node:fs";
|
|
4239
|
-
import * as
|
|
4240
|
-
import
|
|
4585
|
+
import * as path10 from "node:path";
|
|
4586
|
+
import chalk9 from "chalk";
|
|
4241
4587
|
var defaultDependencies3 = {
|
|
4242
4588
|
fileExists: fs6.existsSync,
|
|
4243
4589
|
writeFileSync: fs6.writeFileSync,
|
|
@@ -4252,19 +4598,19 @@ function registerInitCommand(program, dependencies = {}) {
|
|
|
4252
4598
|
...dependencies
|
|
4253
4599
|
};
|
|
4254
4600
|
program.command("init").description("Initialize DocCov: config, GitHub Action, and badge").option("--cwd <dir>", "Working directory", process.cwd()).option("--skip-action", "Skip GitHub Action workflow creation").action((options) => {
|
|
4255
|
-
const cwd =
|
|
4601
|
+
const cwd = path10.resolve(options.cwd);
|
|
4256
4602
|
const existing = findExistingConfig(cwd, fileExists2);
|
|
4257
4603
|
if (existing) {
|
|
4258
|
-
error(
|
|
4604
|
+
error(chalk9.red(`A DocCov config already exists at ${path10.relative(cwd, existing) || "./doccov.config.*"}.`));
|
|
4259
4605
|
process.exitCode = 1;
|
|
4260
4606
|
return;
|
|
4261
4607
|
}
|
|
4262
4608
|
const packageType = detectPackageType(cwd, fileExists2, readFileSync5);
|
|
4263
4609
|
const targetFormat = packageType === "module" ? "ts" : "mts";
|
|
4264
4610
|
const fileName = `doccov.config.${targetFormat}`;
|
|
4265
|
-
const outputPath =
|
|
4611
|
+
const outputPath = path10.join(cwd, fileName);
|
|
4266
4612
|
if (fileExists2(outputPath)) {
|
|
4267
|
-
error(
|
|
4613
|
+
error(chalk9.red(`Cannot create ${fileName}; file already exists.`));
|
|
4268
4614
|
process.exitCode = 1;
|
|
4269
4615
|
return;
|
|
4270
4616
|
}
|
|
@@ -4273,8 +4619,8 @@ function registerInitCommand(program, dependencies = {}) {
|
|
|
4273
4619
|
writeFileSync4(outputPath, template, { encoding: "utf8" });
|
|
4274
4620
|
log(colors.success(`${sym.success} Created ${fileName}`));
|
|
4275
4621
|
if (!options.skipAction) {
|
|
4276
|
-
const workflowDir =
|
|
4277
|
-
const workflowPath =
|
|
4622
|
+
const workflowDir = path10.join(cwd, ".github", "workflows");
|
|
4623
|
+
const workflowPath = path10.join(workflowDir, "doccov.yml");
|
|
4278
4624
|
if (!fileExists2(workflowPath)) {
|
|
4279
4625
|
mkdirSync4(workflowDir, { recursive: true });
|
|
4280
4626
|
writeFileSync4(workflowPath, buildWorkflowTemplate(), { encoding: "utf8" });
|
|
@@ -4285,24 +4631,24 @@ function registerInitCommand(program, dependencies = {}) {
|
|
|
4285
4631
|
}
|
|
4286
4632
|
const repoInfo = detectRepoInfo(cwd, fileExists2, readFileSync5);
|
|
4287
4633
|
log("");
|
|
4288
|
-
log(
|
|
4634
|
+
log(chalk9.bold("Add this badge to your README:"));
|
|
4289
4635
|
log("");
|
|
4290
4636
|
if (repoInfo) {
|
|
4291
|
-
log(
|
|
4637
|
+
log(chalk9.cyan(`[](https://doccov.dev/${repoInfo.owner}/${repoInfo.repo})`));
|
|
4292
4638
|
} else {
|
|
4293
|
-
log(
|
|
4294
|
-
log(
|
|
4639
|
+
log(chalk9.cyan(`[](https://doccov.dev/OWNER/REPO)`));
|
|
4640
|
+
log(chalk9.dim(" Replace OWNER/REPO with your GitHub repo"));
|
|
4295
4641
|
}
|
|
4296
4642
|
log("");
|
|
4297
|
-
log(
|
|
4643
|
+
log(chalk9.dim("Run `doccov check` to verify your documentation coverage"));
|
|
4298
4644
|
});
|
|
4299
4645
|
}
|
|
4300
4646
|
var findExistingConfig = (cwd, fileExists2) => {
|
|
4301
|
-
let current =
|
|
4302
|
-
const { root } =
|
|
4647
|
+
let current = path10.resolve(cwd);
|
|
4648
|
+
const { root } = path10.parse(current);
|
|
4303
4649
|
while (true) {
|
|
4304
4650
|
for (const candidate of DOCCOV_CONFIG_FILENAMES) {
|
|
4305
|
-
const candidatePath =
|
|
4651
|
+
const candidatePath = path10.join(current, candidate);
|
|
4306
4652
|
if (fileExists2(candidatePath)) {
|
|
4307
4653
|
return candidatePath;
|
|
4308
4654
|
}
|
|
@@ -4310,7 +4656,7 @@ var findExistingConfig = (cwd, fileExists2) => {
|
|
|
4310
4656
|
if (current === root) {
|
|
4311
4657
|
break;
|
|
4312
4658
|
}
|
|
4313
|
-
current =
|
|
4659
|
+
current = path10.dirname(current);
|
|
4314
4660
|
}
|
|
4315
4661
|
return null;
|
|
4316
4662
|
};
|
|
@@ -4332,17 +4678,17 @@ var detectPackageType = (cwd, fileExists2, readFileSync5) => {
|
|
|
4332
4678
|
return;
|
|
4333
4679
|
};
|
|
4334
4680
|
var findNearestPackageJson = (cwd, fileExists2) => {
|
|
4335
|
-
let current =
|
|
4336
|
-
const { root } =
|
|
4681
|
+
let current = path10.resolve(cwd);
|
|
4682
|
+
const { root } = path10.parse(current);
|
|
4337
4683
|
while (true) {
|
|
4338
|
-
const candidate =
|
|
4684
|
+
const candidate = path10.join(current, "package.json");
|
|
4339
4685
|
if (fileExists2(candidate)) {
|
|
4340
4686
|
return candidate;
|
|
4341
4687
|
}
|
|
4342
4688
|
if (current === root) {
|
|
4343
4689
|
break;
|
|
4344
4690
|
}
|
|
4345
|
-
current =
|
|
4691
|
+
current = path10.dirname(current);
|
|
4346
4692
|
}
|
|
4347
4693
|
return null;
|
|
4348
4694
|
};
|
|
@@ -4404,7 +4750,7 @@ var detectRepoInfo = (cwd, fileExists2, readFileSync5) => {
|
|
|
4404
4750
|
}
|
|
4405
4751
|
} catch {}
|
|
4406
4752
|
}
|
|
4407
|
-
const gitConfigPath =
|
|
4753
|
+
const gitConfigPath = path10.join(cwd, ".git", "config");
|
|
4408
4754
|
if (fileExists2(gitConfigPath)) {
|
|
4409
4755
|
try {
|
|
4410
4756
|
const config = readFileSync5(gitConfigPath, "utf8");
|
|
@@ -4419,7 +4765,7 @@ var detectRepoInfo = (cwd, fileExists2, readFileSync5) => {
|
|
|
4419
4765
|
|
|
4420
4766
|
// src/commands/spec.ts
|
|
4421
4767
|
import * as fs7 from "node:fs";
|
|
4422
|
-
import * as
|
|
4768
|
+
import * as path11 from "node:path";
|
|
4423
4769
|
import {
|
|
4424
4770
|
buildDocCovSpec as buildDocCovSpec2,
|
|
4425
4771
|
DocCov as DocCov2,
|
|
@@ -4434,7 +4780,7 @@ import {
|
|
|
4434
4780
|
normalize,
|
|
4435
4781
|
validateSpec
|
|
4436
4782
|
} from "@openpkg-ts/spec";
|
|
4437
|
-
import
|
|
4783
|
+
import chalk10 from "chalk";
|
|
4438
4784
|
var defaultDependencies4 = {
|
|
4439
4785
|
createDocCov: (options) => new DocCov2(options),
|
|
4440
4786
|
writeFileSync: fs7.writeFileSync,
|
|
@@ -4446,8 +4792,8 @@ function getArrayLength(value) {
|
|
|
4446
4792
|
}
|
|
4447
4793
|
function formatDiagnosticOutput(prefix2, diagnostic, baseDir) {
|
|
4448
4794
|
const location = diagnostic.location;
|
|
4449
|
-
const relativePath = location?.file ?
|
|
4450
|
-
const locationText = location && relativePath ?
|
|
4795
|
+
const relativePath = location?.file ? path11.relative(baseDir, location.file) || location.file : undefined;
|
|
4796
|
+
const locationText = location && relativePath ? chalk10.gray(`${relativePath}:${location.line ?? 1}:${location.column ?? 1}`) : null;
|
|
4451
4797
|
const locationPrefix = locationText ? `${locationText} ` : "";
|
|
4452
4798
|
return `${prefix2} ${locationPrefix}${diagnostic.message}`;
|
|
4453
4799
|
}
|
|
@@ -4471,7 +4817,7 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
4471
4817
|
config = await loadDocCovConfig(targetDir);
|
|
4472
4818
|
} catch (configError) {
|
|
4473
4819
|
spin.fail("Failed to load config");
|
|
4474
|
-
error(
|
|
4820
|
+
error(chalk10.red("Failed to load DocCov config:"), configError instanceof Error ? configError.message : configError);
|
|
4475
4821
|
process.exit(1);
|
|
4476
4822
|
}
|
|
4477
4823
|
const cliFilters = {
|
|
@@ -4504,26 +4850,30 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
4504
4850
|
const validation = validateSpec(normalized);
|
|
4505
4851
|
if (!validation.ok) {
|
|
4506
4852
|
spin.fail("Validation failed");
|
|
4507
|
-
error(
|
|
4853
|
+
error(chalk10.red("Spec failed schema validation"));
|
|
4508
4854
|
for (const err of validation.errors) {
|
|
4509
|
-
error(
|
|
4855
|
+
error(chalk10.red(`schema: ${err.instancePath || "/"} ${err.message}`));
|
|
4510
4856
|
}
|
|
4511
4857
|
process.exit(1);
|
|
4512
4858
|
}
|
|
4513
4859
|
let doccovSpec = null;
|
|
4514
4860
|
if (!options.openpkgOnly) {
|
|
4515
|
-
|
|
4861
|
+
spin.update("Building coverage spec...");
|
|
4862
|
+
doccovSpec = await buildDocCovSpec2({
|
|
4516
4863
|
openpkgPath: "openpkg.json",
|
|
4517
4864
|
openpkg: normalized,
|
|
4518
4865
|
packagePath: targetDir,
|
|
4519
|
-
forgottenExports: result.forgottenExports
|
|
4866
|
+
forgottenExports: result.forgottenExports,
|
|
4867
|
+
onProgress: (current, total, item) => {
|
|
4868
|
+
spin.setDetail(`${current}/${total}: ${item}`);
|
|
4869
|
+
}
|
|
4520
4870
|
});
|
|
4521
4871
|
const doccovValidation = validateDocCovSpec(doccovSpec);
|
|
4522
4872
|
if (!doccovValidation.ok) {
|
|
4523
4873
|
spin.fail("DocCov validation failed");
|
|
4524
|
-
error(
|
|
4874
|
+
error(chalk10.red("DocCov spec failed schema validation"));
|
|
4525
4875
|
for (const err of doccovValidation.errors) {
|
|
4526
|
-
error(
|
|
4876
|
+
error(chalk10.red(`doccov: ${err.instancePath || "/"} ${err.message}`));
|
|
4527
4877
|
}
|
|
4528
4878
|
process.exit(1);
|
|
4529
4879
|
}
|
|
@@ -4533,7 +4883,7 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
4533
4883
|
if (resolved.packageInfo) {
|
|
4534
4884
|
packageName = resolved.packageInfo.name;
|
|
4535
4885
|
} else {
|
|
4536
|
-
const pkgJsonPath =
|
|
4886
|
+
const pkgJsonPath = path11.join(targetDir, "package.json");
|
|
4537
4887
|
try {
|
|
4538
4888
|
const pkgJson = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
|
|
4539
4889
|
packageName = pkgJson.name ?? "unknown";
|
|
@@ -4541,28 +4891,28 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
4541
4891
|
packageName = "unknown";
|
|
4542
4892
|
}
|
|
4543
4893
|
}
|
|
4544
|
-
const baseOutputDir = options.output === ".doccov" ? getDoccovDir3(options.cwd) :
|
|
4545
|
-
const outputDir =
|
|
4894
|
+
const baseOutputDir = options.output === ".doccov" ? getDoccovDir3(options.cwd) : path11.resolve(options.cwd, options.output);
|
|
4895
|
+
const outputDir = path11.join(baseOutputDir, packageName);
|
|
4546
4896
|
fs7.mkdirSync(outputDir, { recursive: true });
|
|
4547
4897
|
const displayPath = `${options.output}/${packageName}`;
|
|
4548
4898
|
if (format === "api-surface") {
|
|
4549
4899
|
const apiSurface = renderApiSurface(normalized);
|
|
4550
|
-
const apiSurfacePath =
|
|
4900
|
+
const apiSurfacePath = path11.join(outputDir, "api-surface.txt");
|
|
4551
4901
|
writeFileSync5(apiSurfacePath, apiSurface);
|
|
4552
4902
|
spin.success(`Generated ${displayPath}/ (API surface)`);
|
|
4553
4903
|
} else {
|
|
4554
|
-
const openpkgPath =
|
|
4904
|
+
const openpkgPath = path11.join(outputDir, "openpkg.json");
|
|
4555
4905
|
writeFileSync5(openpkgPath, JSON.stringify(normalized, null, 2));
|
|
4556
4906
|
if (doccovSpec) {
|
|
4557
|
-
const doccovPath =
|
|
4907
|
+
const doccovPath = path11.join(outputDir, "doccov.json");
|
|
4558
4908
|
writeFileSync5(doccovPath, JSON.stringify(doccovSpec, null, 2));
|
|
4559
4909
|
spin.success(`Generated ${displayPath}/`);
|
|
4560
|
-
log(
|
|
4561
|
-
log(
|
|
4910
|
+
log(chalk10.gray(` openpkg.json: ${getArrayLength(normalized.exports)} exports`));
|
|
4911
|
+
log(chalk10.gray(` doccov.json: ${doccovSpec.summary.score}% coverage, ${doccovSpec.summary.drift.total} drift issues`));
|
|
4562
4912
|
} else {
|
|
4563
4913
|
spin.success(`Generated ${displayPath}/openpkg.json`);
|
|
4564
|
-
log(
|
|
4565
|
-
log(
|
|
4914
|
+
log(chalk10.gray(` ${getArrayLength(normalized.exports)} exports`));
|
|
4915
|
+
log(chalk10.gray(` ${getArrayLength(normalized.types)} types`));
|
|
4566
4916
|
}
|
|
4567
4917
|
}
|
|
4568
4918
|
const isFullGenerationInfo = (gen) => {
|
|
@@ -4574,62 +4924,62 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
4574
4924
|
const pm = await detectPackageManager(fileSystem);
|
|
4575
4925
|
const buildCmd = pm.name === "npm" ? "npm run build" : `${pm.name} run build`;
|
|
4576
4926
|
log("");
|
|
4577
|
-
log(
|
|
4578
|
-
log(
|
|
4927
|
+
log(chalk10.yellow("⚠ Runtime extraction requested but no schemas extracted."));
|
|
4928
|
+
log(chalk10.yellow(` Ensure project is built (${buildCmd}) and dist/ exists.`));
|
|
4579
4929
|
}
|
|
4580
4930
|
if (options.verbose && fullGen) {
|
|
4581
4931
|
log("");
|
|
4582
|
-
log(
|
|
4583
|
-
log(
|
|
4584
|
-
log(
|
|
4585
|
-
log(
|
|
4586
|
-
log(
|
|
4587
|
-
log(
|
|
4588
|
-
log(
|
|
4932
|
+
log(chalk10.bold("Generation Info"));
|
|
4933
|
+
log(chalk10.gray(` Timestamp: ${fullGen.timestamp}`));
|
|
4934
|
+
log(chalk10.gray(` Generator: ${fullGen.generator.name}@${fullGen.generator.version}`));
|
|
4935
|
+
log(chalk10.gray(` Entry point: ${fullGen.analysis.entryPoint}`));
|
|
4936
|
+
log(chalk10.gray(` Detected via: ${fullGen.analysis.entryPointSource}`));
|
|
4937
|
+
log(chalk10.gray(` Declaration only: ${fullGen.analysis.isDeclarationOnly ? "yes" : "no"}`));
|
|
4938
|
+
log(chalk10.gray(` External types: ${fullGen.analysis.resolvedExternalTypes ? "resolved" : "skipped"}`));
|
|
4589
4939
|
if (fullGen.analysis.maxTypeDepth) {
|
|
4590
|
-
log(
|
|
4940
|
+
log(chalk10.gray(` Max type depth: ${fullGen.analysis.maxTypeDepth}`));
|
|
4591
4941
|
}
|
|
4592
4942
|
if (fullGen.analysis.schemaExtraction) {
|
|
4593
4943
|
const se = fullGen.analysis.schemaExtraction;
|
|
4594
|
-
log(
|
|
4944
|
+
log(chalk10.gray(` Schema extraction: ${se.method}`));
|
|
4595
4945
|
if (se.runtimeCount) {
|
|
4596
|
-
log(
|
|
4946
|
+
log(chalk10.gray(` Runtime schemas: ${se.runtimeCount} (${se.vendors?.join(", ")})`));
|
|
4597
4947
|
}
|
|
4598
4948
|
}
|
|
4599
4949
|
log("");
|
|
4600
|
-
log(
|
|
4601
|
-
log(
|
|
4950
|
+
log(chalk10.bold("Environment"));
|
|
4951
|
+
log(chalk10.gray(` node_modules: ${fullGen.environment.hasNodeModules ? "found" : "not found"}`));
|
|
4602
4952
|
if (fullGen.environment.packageManager) {
|
|
4603
|
-
log(
|
|
4953
|
+
log(chalk10.gray(` Package manager: ${fullGen.environment.packageManager}`));
|
|
4604
4954
|
}
|
|
4605
4955
|
if (fullGen.environment.isMonorepo) {
|
|
4606
|
-
log(
|
|
4956
|
+
log(chalk10.gray(` Monorepo: yes`));
|
|
4607
4957
|
}
|
|
4608
4958
|
if (fullGen.environment.targetPackage) {
|
|
4609
|
-
log(
|
|
4959
|
+
log(chalk10.gray(` Target package: ${fullGen.environment.targetPackage}`));
|
|
4610
4960
|
}
|
|
4611
4961
|
if (fullGen.issues.length > 0) {
|
|
4612
4962
|
log("");
|
|
4613
|
-
log(
|
|
4963
|
+
log(chalk10.bold("Issues"));
|
|
4614
4964
|
for (const issue of fullGen.issues) {
|
|
4615
|
-
const prefix2 = issue.severity === "error" ?
|
|
4965
|
+
const prefix2 = issue.severity === "error" ? chalk10.red(">") : issue.severity === "warning" ? chalk10.yellow(">") : chalk10.cyan(">");
|
|
4616
4966
|
log(`${prefix2} [${issue.code}] ${issue.message}`);
|
|
4617
4967
|
if (issue.suggestion) {
|
|
4618
|
-
log(
|
|
4968
|
+
log(chalk10.gray(` ${issue.suggestion}`));
|
|
4619
4969
|
}
|
|
4620
4970
|
}
|
|
4621
4971
|
}
|
|
4622
4972
|
}
|
|
4623
4973
|
if (options.showDiagnostics && result.diagnostics.length > 0) {
|
|
4624
4974
|
log("");
|
|
4625
|
-
log(
|
|
4975
|
+
log(chalk10.bold("Diagnostics"));
|
|
4626
4976
|
for (const diagnostic of result.diagnostics) {
|
|
4627
|
-
const prefix2 = diagnostic.severity === "error" ?
|
|
4977
|
+
const prefix2 = diagnostic.severity === "error" ? chalk10.red(">") : diagnostic.severity === "warning" ? chalk10.yellow(">") : chalk10.cyan(">");
|
|
4628
4978
|
log(formatDiagnosticOutput(prefix2, diagnostic, targetDir));
|
|
4629
4979
|
}
|
|
4630
4980
|
}
|
|
4631
4981
|
} catch (commandError) {
|
|
4632
|
-
error(
|
|
4982
|
+
error(chalk10.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
|
|
4633
4983
|
process.exit(1);
|
|
4634
4984
|
}
|
|
4635
4985
|
});
|
|
@@ -4637,7 +4987,7 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
4637
4987
|
|
|
4638
4988
|
// src/commands/trends.ts
|
|
4639
4989
|
import * as fs8 from "node:fs";
|
|
4640
|
-
import * as
|
|
4990
|
+
import * as path12 from "node:path";
|
|
4641
4991
|
import {
|
|
4642
4992
|
formatDelta as formatDelta2,
|
|
4643
4993
|
getExtendedTrend,
|
|
@@ -4647,7 +4997,7 @@ import {
|
|
|
4647
4997
|
renderSparkline,
|
|
4648
4998
|
saveSnapshot
|
|
4649
4999
|
} from "@doccov/sdk";
|
|
4650
|
-
import
|
|
5000
|
+
import chalk11 from "chalk";
|
|
4651
5001
|
function formatDate(timestamp) {
|
|
4652
5002
|
const date = new Date(timestamp);
|
|
4653
5003
|
return date.toLocaleDateString("en-US", {
|
|
@@ -4660,19 +5010,19 @@ function formatDate(timestamp) {
|
|
|
4660
5010
|
}
|
|
4661
5011
|
function getColorForScore(score) {
|
|
4662
5012
|
if (score >= 90)
|
|
4663
|
-
return
|
|
5013
|
+
return chalk11.green;
|
|
4664
5014
|
if (score >= 70)
|
|
4665
|
-
return
|
|
5015
|
+
return chalk11.yellow;
|
|
4666
5016
|
if (score >= 50)
|
|
4667
|
-
return
|
|
4668
|
-
return
|
|
5017
|
+
return chalk11.hex("#FFA500");
|
|
5018
|
+
return chalk11.red;
|
|
4669
5019
|
}
|
|
4670
5020
|
function formatSnapshot(snapshot) {
|
|
4671
5021
|
const color = getColorForScore(snapshot.coverageScore);
|
|
4672
5022
|
const date = formatDate(snapshot.timestamp);
|
|
4673
5023
|
const version = snapshot.version ? ` v${snapshot.version}` : "";
|
|
4674
|
-
const commit = snapshot.commit ?
|
|
4675
|
-
return `${
|
|
5024
|
+
const commit = snapshot.commit ? chalk11.gray(` (${snapshot.commit.slice(0, 7)})`) : "";
|
|
5025
|
+
return `${chalk11.gray(date)} ${color(`${snapshot.coverageScore}%`)} ${snapshot.documentedExports}/${snapshot.totalExports} exports${version}${commit}`;
|
|
4676
5026
|
}
|
|
4677
5027
|
function formatWeekDate(timestamp) {
|
|
4678
5028
|
const date = new Date(timestamp);
|
|
@@ -4680,26 +5030,26 @@ function formatWeekDate(timestamp) {
|
|
|
4680
5030
|
}
|
|
4681
5031
|
function formatVelocity(velocity) {
|
|
4682
5032
|
if (velocity > 0)
|
|
4683
|
-
return
|
|
5033
|
+
return chalk11.green(`+${velocity}%/day`);
|
|
4684
5034
|
if (velocity < 0)
|
|
4685
|
-
return
|
|
4686
|
-
return
|
|
5035
|
+
return chalk11.red(`${velocity}%/day`);
|
|
5036
|
+
return chalk11.gray("0%/day");
|
|
4687
5037
|
}
|
|
4688
5038
|
function registerTrendsCommand(program) {
|
|
4689
5039
|
program.command("trends").description("Show coverage trends over time").option("--cwd <dir>", "Working directory", process.cwd()).option("-n, --limit <count>", "Number of snapshots to show", "10").option("--prune <count>", "Prune history to keep only N snapshots").option("--record", "Record current coverage to history").option("--json", "Output as JSON").option("--extended", "Show extended trend analysis (velocity, projections)").option("--weekly", "Show weekly summary breakdown").action(async (options) => {
|
|
4690
|
-
const cwd =
|
|
5040
|
+
const cwd = path12.resolve(options.cwd);
|
|
4691
5041
|
if (options.prune) {
|
|
4692
5042
|
const keepCount = parseInt(options.prune, 10);
|
|
4693
5043
|
if (!Number.isNaN(keepCount)) {
|
|
4694
5044
|
const deleted = pruneHistory(cwd, keepCount);
|
|
4695
|
-
console.log(
|
|
5045
|
+
console.log(chalk11.green(`Pruned ${deleted} old snapshots, kept ${keepCount} most recent`));
|
|
4696
5046
|
}
|
|
4697
5047
|
return;
|
|
4698
5048
|
}
|
|
4699
5049
|
if (options.record) {
|
|
4700
|
-
const specPath =
|
|
5050
|
+
const specPath = path12.resolve(cwd, "openpkg.json");
|
|
4701
5051
|
if (!fs8.existsSync(specPath)) {
|
|
4702
|
-
console.error(
|
|
5052
|
+
console.error(chalk11.red("No openpkg.json found. Run `doccov spec` first to generate a spec."));
|
|
4703
5053
|
process.exit(1);
|
|
4704
5054
|
}
|
|
4705
5055
|
const spin = spinner("Recording coverage snapshot");
|
|
@@ -4712,21 +5062,21 @@ function registerTrendsCommand(program) {
|
|
|
4712
5062
|
console.log(formatSnapshot(trend.current));
|
|
4713
5063
|
if (trend.delta !== undefined) {
|
|
4714
5064
|
const deltaStr = formatDelta2(trend.delta);
|
|
4715
|
-
const deltaColor = trend.delta > 0 ?
|
|
4716
|
-
console.log(
|
|
5065
|
+
const deltaColor = trend.delta > 0 ? chalk11.green : trend.delta < 0 ? chalk11.red : chalk11.gray;
|
|
5066
|
+
console.log(chalk11.gray("Change from previous:"), deltaColor(deltaStr));
|
|
4717
5067
|
}
|
|
4718
5068
|
return;
|
|
4719
5069
|
} catch (error) {
|
|
4720
5070
|
spin.fail("Failed to record snapshot");
|
|
4721
|
-
console.error(
|
|
5071
|
+
console.error(chalk11.red("Failed to read openpkg.json:"), error instanceof Error ? error.message : error);
|
|
4722
5072
|
process.exit(1);
|
|
4723
5073
|
}
|
|
4724
5074
|
}
|
|
4725
5075
|
const snapshots = loadSnapshots(cwd);
|
|
4726
5076
|
const limit = parseInt(options.limit ?? "10", 10);
|
|
4727
5077
|
if (snapshots.length === 0) {
|
|
4728
|
-
console.log(
|
|
4729
|
-
console.log(
|
|
5078
|
+
console.log(chalk11.yellow("No coverage history found."));
|
|
5079
|
+
console.log(chalk11.gray("Run `doccov trends --record` to save the current coverage."));
|
|
4730
5080
|
return;
|
|
4731
5081
|
}
|
|
4732
5082
|
if (options.json) {
|
|
@@ -4741,83 +5091,83 @@ function registerTrendsCommand(program) {
|
|
|
4741
5091
|
}
|
|
4742
5092
|
const sparklineData = snapshots.slice(0, 10).map((s) => s.coverageScore).reverse();
|
|
4743
5093
|
const sparkline = renderSparkline(sparklineData);
|
|
4744
|
-
console.log(
|
|
4745
|
-
console.log(
|
|
4746
|
-
console.log(
|
|
5094
|
+
console.log(chalk11.bold("Coverage Trends"));
|
|
5095
|
+
console.log(chalk11.gray(`Package: ${snapshots[0].package}`));
|
|
5096
|
+
console.log(chalk11.gray(`Sparkline: ${sparkline}`));
|
|
4747
5097
|
console.log("");
|
|
4748
5098
|
if (snapshots.length >= 2) {
|
|
4749
5099
|
const oldest = snapshots[snapshots.length - 1];
|
|
4750
5100
|
const newest = snapshots[0];
|
|
4751
5101
|
const overallDelta = newest.coverageScore - oldest.coverageScore;
|
|
4752
5102
|
const deltaStr = formatDelta2(overallDelta);
|
|
4753
|
-
const deltaColor = overallDelta > 0 ?
|
|
4754
|
-
console.log(
|
|
5103
|
+
const deltaColor = overallDelta > 0 ? chalk11.green : overallDelta < 0 ? chalk11.red : chalk11.gray;
|
|
5104
|
+
console.log(chalk11.gray("Overall trend:"), deltaColor(deltaStr), chalk11.gray(`(${snapshots.length} snapshots)`));
|
|
4755
5105
|
console.log("");
|
|
4756
5106
|
}
|
|
4757
5107
|
if (options.extended) {
|
|
4758
|
-
const specPath =
|
|
5108
|
+
const specPath = path12.resolve(cwd, "openpkg.json");
|
|
4759
5109
|
if (fs8.existsSync(specPath)) {
|
|
4760
5110
|
try {
|
|
4761
5111
|
const specContent = fs8.readFileSync(specPath, "utf-8");
|
|
4762
5112
|
const spec = JSON.parse(specContent);
|
|
4763
5113
|
const extended = getExtendedTrend(spec, cwd);
|
|
4764
|
-
console.log(
|
|
4765
|
-
console.log(
|
|
5114
|
+
console.log(chalk11.bold("Extended Analysis"));
|
|
5115
|
+
console.log(chalk11.gray("90-day retention"));
|
|
4766
5116
|
console.log("");
|
|
4767
5117
|
console.log(" Velocity:");
|
|
4768
5118
|
console.log(` 7-day: ${formatVelocity(extended.velocity7d)}`);
|
|
4769
5119
|
console.log(` 30-day: ${formatVelocity(extended.velocity30d)}`);
|
|
4770
5120
|
console.log(` 90-day: ${formatVelocity(extended.velocity90d)}`);
|
|
4771
5121
|
console.log("");
|
|
4772
|
-
const projColor = extended.projected30d >= extended.trend.current.coverageScore ?
|
|
5122
|
+
const projColor = extended.projected30d >= extended.trend.current.coverageScore ? chalk11.green : chalk11.red;
|
|
4773
5123
|
console.log(` Projected (30d): ${projColor(`${extended.projected30d}%`)}`);
|
|
4774
|
-
console.log(` All-time high: ${
|
|
4775
|
-
console.log(` All-time low: ${
|
|
5124
|
+
console.log(` All-time high: ${chalk11.green(`${extended.allTimeHigh}%`)}`);
|
|
5125
|
+
console.log(` All-time low: ${chalk11.red(`${extended.allTimeLow}%`)}`);
|
|
4776
5126
|
if (extended.dataRange) {
|
|
4777
5127
|
const startDate = formatWeekDate(extended.dataRange.start);
|
|
4778
5128
|
const endDate = formatWeekDate(extended.dataRange.end);
|
|
4779
|
-
console.log(
|
|
5129
|
+
console.log(chalk11.gray(` Data range: ${startDate} - ${endDate}`));
|
|
4780
5130
|
}
|
|
4781
5131
|
console.log("");
|
|
4782
5132
|
if (options.weekly && extended.weeklySummaries.length > 0) {
|
|
4783
|
-
console.log(
|
|
5133
|
+
console.log(chalk11.bold("Weekly Summary"));
|
|
4784
5134
|
const weekLimit = Math.min(extended.weeklySummaries.length, 8);
|
|
4785
5135
|
for (let i = 0;i < weekLimit; i++) {
|
|
4786
5136
|
const week = extended.weeklySummaries[i];
|
|
4787
5137
|
const weekStart = formatWeekDate(week.weekStart);
|
|
4788
5138
|
const weekEnd = formatWeekDate(week.weekEnd);
|
|
4789
|
-
const deltaColor = week.delta > 0 ?
|
|
5139
|
+
const deltaColor = week.delta > 0 ? chalk11.green : week.delta < 0 ? chalk11.red : chalk11.gray;
|
|
4790
5140
|
const deltaStr = week.delta > 0 ? `+${week.delta}%` : `${week.delta}%`;
|
|
4791
5141
|
console.log(` ${weekStart} - ${weekEnd}: ${week.avgCoverage}% avg ${deltaColor(deltaStr)} (${week.snapshotCount} snapshots)`);
|
|
4792
5142
|
}
|
|
4793
5143
|
if (extended.weeklySummaries.length > weekLimit) {
|
|
4794
|
-
console.log(
|
|
5144
|
+
console.log(chalk11.gray(` ... and ${extended.weeklySummaries.length - weekLimit} more weeks`));
|
|
4795
5145
|
}
|
|
4796
5146
|
console.log("");
|
|
4797
5147
|
}
|
|
4798
5148
|
} catch {
|
|
4799
|
-
console.log(
|
|
5149
|
+
console.log(chalk11.yellow("Could not load openpkg.json for extended analysis"));
|
|
4800
5150
|
console.log("");
|
|
4801
5151
|
}
|
|
4802
5152
|
}
|
|
4803
5153
|
}
|
|
4804
|
-
console.log(
|
|
5154
|
+
console.log(chalk11.bold("History"));
|
|
4805
5155
|
const displaySnapshots = snapshots.slice(0, limit);
|
|
4806
5156
|
for (let i = 0;i < displaySnapshots.length; i++) {
|
|
4807
5157
|
const snapshot = displaySnapshots[i];
|
|
4808
|
-
const prefix2 = i === 0 ?
|
|
5158
|
+
const prefix2 = i === 0 ? chalk11.cyan("→") : " ";
|
|
4809
5159
|
console.log(`${prefix2} ${formatSnapshot(snapshot)}`);
|
|
4810
5160
|
}
|
|
4811
5161
|
if (snapshots.length > limit) {
|
|
4812
|
-
console.log(
|
|
5162
|
+
console.log(chalk11.gray(` ... and ${snapshots.length - limit} more`));
|
|
4813
5163
|
}
|
|
4814
5164
|
});
|
|
4815
5165
|
}
|
|
4816
5166
|
|
|
4817
5167
|
// src/cli.ts
|
|
4818
5168
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
4819
|
-
var __dirname2 =
|
|
4820
|
-
var packageJson = JSON.parse(readFileSync7(
|
|
5169
|
+
var __dirname2 = path13.dirname(__filename2);
|
|
5170
|
+
var packageJson = JSON.parse(readFileSync7(path13.join(__dirname2, "../package.json"), "utf-8"));
|
|
4821
5171
|
var program = new Command;
|
|
4822
5172
|
program.name("doccov").description("DocCov - Documentation coverage and drift detection for TypeScript").version(packageJson.version);
|
|
4823
5173
|
registerCheckCommand(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doccov/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.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.31.0",
|
|
52
|
+
"@doccov/spec": "^0.31.0",
|
|
53
53
|
"@inquirer/prompts": "^7.8.0",
|
|
54
54
|
"@openpkg-ts/spec": "^0.23.0",
|
|
55
55
|
"ai": "^4.0.0",
|