@doccov/cli 0.27.1 → 0.28.1
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 +249 -1478
- package/dist/config/index.d.ts +4 -44
- package/dist/config/index.js +1 -84
- package/package.json +2 -3
package/dist/cli.js
CHANGED
|
@@ -4,90 +4,7 @@
|
|
|
4
4
|
import { access } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
|
-
|
|
8
|
-
// src/config/schema.ts
|
|
9
|
-
import { z } from "zod";
|
|
10
|
-
var stringList = z.union([
|
|
11
|
-
z.string(),
|
|
12
|
-
z.array(z.string())
|
|
13
|
-
]);
|
|
14
|
-
var docsConfigSchema = z.object({
|
|
15
|
-
include: stringList.optional(),
|
|
16
|
-
exclude: stringList.optional()
|
|
17
|
-
});
|
|
18
|
-
var exampleModeSchema = z.enum([
|
|
19
|
-
"presence",
|
|
20
|
-
"typecheck",
|
|
21
|
-
"run"
|
|
22
|
-
]);
|
|
23
|
-
var exampleModesSchema = z.union([
|
|
24
|
-
exampleModeSchema,
|
|
25
|
-
z.array(exampleModeSchema),
|
|
26
|
-
z.string()
|
|
27
|
-
]);
|
|
28
|
-
var apiSurfaceConfigSchema = z.object({
|
|
29
|
-
minCompleteness: z.number().min(0).max(100).optional(),
|
|
30
|
-
warnBelow: z.number().min(0).max(100).optional(),
|
|
31
|
-
ignore: z.array(z.string()).optional()
|
|
32
|
-
});
|
|
33
|
-
var checkConfigSchema = z.object({
|
|
34
|
-
examples: exampleModesSchema.optional(),
|
|
35
|
-
minHealth: z.number().min(0).max(100).optional(),
|
|
36
|
-
minCoverage: z.number().min(0).max(100).optional(),
|
|
37
|
-
maxDrift: z.number().min(0).max(100).optional(),
|
|
38
|
-
minApiSurface: z.number().min(0).max(100).optional(),
|
|
39
|
-
apiSurface: apiSurfaceConfigSchema.optional()
|
|
40
|
-
});
|
|
41
|
-
var docCovConfigSchema = z.object({
|
|
42
|
-
include: stringList.optional(),
|
|
43
|
-
exclude: stringList.optional(),
|
|
44
|
-
plugins: z.array(z.unknown()).optional(),
|
|
45
|
-
docs: docsConfigSchema.optional(),
|
|
46
|
-
check: checkConfigSchema.optional()
|
|
47
|
-
});
|
|
48
|
-
var normalizeList = (value) => {
|
|
49
|
-
if (!value) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const list = Array.isArray(value) ? value : [value];
|
|
53
|
-
const normalized = list.map((item) => item.trim()).filter(Boolean);
|
|
54
|
-
return normalized.length > 0 ? normalized : undefined;
|
|
55
|
-
};
|
|
56
|
-
var normalizeConfig = (input) => {
|
|
57
|
-
const include = normalizeList(input.include);
|
|
58
|
-
const exclude = normalizeList(input.exclude);
|
|
59
|
-
let docs;
|
|
60
|
-
if (input.docs) {
|
|
61
|
-
const docsInclude = normalizeList(input.docs.include);
|
|
62
|
-
const docsExclude = normalizeList(input.docs.exclude);
|
|
63
|
-
if (docsInclude || docsExclude) {
|
|
64
|
-
docs = {
|
|
65
|
-
include: docsInclude,
|
|
66
|
-
exclude: docsExclude
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
let check;
|
|
71
|
-
if (input.check) {
|
|
72
|
-
check = {
|
|
73
|
-
examples: input.check.examples,
|
|
74
|
-
minHealth: input.check.minHealth,
|
|
75
|
-
minCoverage: input.check.minCoverage,
|
|
76
|
-
maxDrift: input.check.maxDrift,
|
|
77
|
-
minApiSurface: input.check.minApiSurface,
|
|
78
|
-
apiSurface: input.check.apiSurface
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
include,
|
|
83
|
-
exclude,
|
|
84
|
-
plugins: input.plugins,
|
|
85
|
-
docs,
|
|
86
|
-
check
|
|
87
|
-
};
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
// src/config/doccov-config.ts
|
|
7
|
+
import { docCovConfigSchema, normalizeConfig } from "@doccov/sdk";
|
|
91
8
|
var DOCCOV_CONFIG_FILENAMES = [
|
|
92
9
|
"doccov.config.ts",
|
|
93
10
|
"doccov.config.mts",
|
|
@@ -170,26 +87,75 @@ import {
|
|
|
170
87
|
parseExamplesFlag,
|
|
171
88
|
resolveTarget
|
|
172
89
|
} from "@doccov/sdk";
|
|
173
|
-
import
|
|
90
|
+
import chalk6 from "chalk";
|
|
174
91
|
|
|
175
|
-
//
|
|
92
|
+
// src/utils/filter-options.ts
|
|
93
|
+
import { mergeFilters, parseListFlag } from "@doccov/sdk";
|
|
176
94
|
import chalk from "chalk";
|
|
95
|
+
var parseVisibilityFlag = (value) => {
|
|
96
|
+
if (!value)
|
|
97
|
+
return;
|
|
98
|
+
const validTags = ["public", "beta", "alpha", "internal"];
|
|
99
|
+
const parsed = parseListFlag(value);
|
|
100
|
+
if (!parsed)
|
|
101
|
+
return;
|
|
102
|
+
const result = [];
|
|
103
|
+
for (const tag of parsed) {
|
|
104
|
+
const lower = tag.toLowerCase();
|
|
105
|
+
if (validTags.includes(lower)) {
|
|
106
|
+
result.push(lower);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return result.length > 0 ? result : undefined;
|
|
110
|
+
};
|
|
111
|
+
var formatList = (label, values) => `${label}: ${values.map((value) => chalk.cyan(value)).join(", ")}`;
|
|
112
|
+
var mergeFilterOptions = (config, cliOptions) => {
|
|
113
|
+
const messages = [];
|
|
114
|
+
if (config?.include) {
|
|
115
|
+
messages.push(formatList("include filters from config", config.include));
|
|
116
|
+
}
|
|
117
|
+
if (config?.exclude) {
|
|
118
|
+
messages.push(formatList("exclude filters from config", config.exclude));
|
|
119
|
+
}
|
|
120
|
+
if (cliOptions.include) {
|
|
121
|
+
messages.push(formatList("apply include filters from CLI", cliOptions.include));
|
|
122
|
+
}
|
|
123
|
+
if (cliOptions.exclude) {
|
|
124
|
+
messages.push(formatList("apply exclude filters from CLI", cliOptions.exclude));
|
|
125
|
+
}
|
|
126
|
+
if (cliOptions.visibility) {
|
|
127
|
+
messages.push(formatList("apply visibility filter from CLI", cliOptions.visibility));
|
|
128
|
+
}
|
|
129
|
+
const resolved = mergeFilters(config, cliOptions);
|
|
130
|
+
if (!resolved.include && !resolved.exclude && !cliOptions.visibility) {
|
|
131
|
+
return { messages };
|
|
132
|
+
}
|
|
133
|
+
const source = resolved.source === "override" ? "cli" : resolved.source;
|
|
134
|
+
return {
|
|
135
|
+
include: resolved.include,
|
|
136
|
+
exclude: resolved.exclude,
|
|
137
|
+
visibility: cliOptions.visibility,
|
|
138
|
+
source,
|
|
139
|
+
messages
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// src/utils/progress/colors.ts
|
|
177
144
|
import chalk2 from "chalk";
|
|
178
|
-
import { Worker } from "node:worker_threads";
|
|
179
145
|
var colors = {
|
|
180
|
-
success:
|
|
181
|
-
error:
|
|
182
|
-
warning:
|
|
183
|
-
info:
|
|
184
|
-
muted:
|
|
185
|
-
bold:
|
|
186
|
-
dim:
|
|
187
|
-
underline:
|
|
188
|
-
primary:
|
|
189
|
-
secondary:
|
|
190
|
-
path:
|
|
191
|
-
number:
|
|
192
|
-
code:
|
|
146
|
+
success: chalk2.green,
|
|
147
|
+
error: chalk2.red,
|
|
148
|
+
warning: chalk2.yellow,
|
|
149
|
+
info: chalk2.cyan,
|
|
150
|
+
muted: chalk2.gray,
|
|
151
|
+
bold: chalk2.bold,
|
|
152
|
+
dim: chalk2.dim,
|
|
153
|
+
underline: chalk2.underline,
|
|
154
|
+
primary: chalk2.cyan,
|
|
155
|
+
secondary: chalk2.magenta,
|
|
156
|
+
path: chalk2.cyan,
|
|
157
|
+
number: chalk2.yellow,
|
|
158
|
+
code: chalk2.gray
|
|
193
159
|
};
|
|
194
160
|
var symbols = {
|
|
195
161
|
success: "✓",
|
|
@@ -228,6 +194,10 @@ var prefix = {
|
|
|
228
194
|
warning: colors.warning(symbols.warning),
|
|
229
195
|
info: colors.info(symbols.info)
|
|
230
196
|
};
|
|
197
|
+
// src/utils/progress/spinner.ts
|
|
198
|
+
import chalk3 from "chalk";
|
|
199
|
+
|
|
200
|
+
// src/utils/progress/utils.ts
|
|
231
201
|
function isTTY() {
|
|
232
202
|
return Boolean(process.stdout.isTTY);
|
|
233
203
|
}
|
|
@@ -249,18 +219,6 @@ function getTerminalWidth() {
|
|
|
249
219
|
const width = process.stdout.columns || DEFAULT_TERMINAL_WIDTH;
|
|
250
220
|
return Math.max(width, MIN_TERMINAL_WIDTH);
|
|
251
221
|
}
|
|
252
|
-
function formatDuration(ms) {
|
|
253
|
-
if (ms < 1000) {
|
|
254
|
-
return `${ms}ms`;
|
|
255
|
-
}
|
|
256
|
-
const seconds = ms / 1000;
|
|
257
|
-
if (seconds < 60) {
|
|
258
|
-
return `${seconds.toFixed(1)}s`;
|
|
259
|
-
}
|
|
260
|
-
const minutes = Math.floor(seconds / 60);
|
|
261
|
-
const remainingSeconds = Math.floor(seconds % 60);
|
|
262
|
-
return `${minutes}m ${remainingSeconds}s`;
|
|
263
|
-
}
|
|
264
222
|
var cursor = {
|
|
265
223
|
hide: "\x1B[?25l",
|
|
266
224
|
show: "\x1B[?25h",
|
|
@@ -279,11 +237,6 @@ function clearLine() {
|
|
|
279
237
|
process.stdout.write(cursor.clearLine + cursor.left);
|
|
280
238
|
}
|
|
281
239
|
}
|
|
282
|
-
function moveCursorUp(lines = 1) {
|
|
283
|
-
if (isTTY()) {
|
|
284
|
-
process.stdout.write(cursor.up(lines));
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
240
|
function hideCursor() {
|
|
288
241
|
if (isTTY()) {
|
|
289
242
|
process.stdout.write(cursor.hide);
|
|
@@ -304,391 +257,15 @@ function stripAnsi(text) {
|
|
|
304
257
|
return text.replace(ANSI_REGEX, "");
|
|
305
258
|
}
|
|
306
259
|
|
|
307
|
-
|
|
308
|
-
bars = new Map;
|
|
309
|
-
barOrder = [];
|
|
310
|
-
options;
|
|
311
|
-
spinnerFrames;
|
|
312
|
-
spinnerIndex = 0;
|
|
313
|
-
timer = null;
|
|
314
|
-
lastRenderedLines = 0;
|
|
315
|
-
symbols = getSymbols(supportsUnicode());
|
|
316
|
-
sigintHandler = null;
|
|
317
|
-
filledChar;
|
|
318
|
-
emptyChar;
|
|
319
|
-
constructor(options = {}) {
|
|
320
|
-
this.options = {
|
|
321
|
-
barWidth: options.barWidth,
|
|
322
|
-
showPercent: options.showPercent ?? true,
|
|
323
|
-
showCount: options.showCount ?? true,
|
|
324
|
-
spinnerInterval: options.spinnerInterval ?? 80
|
|
325
|
-
};
|
|
326
|
-
this.spinnerFrames = supportsUnicode() ? ["◐", "◓", "◑", "◒"] : ["-", "\\", "|", "/"];
|
|
327
|
-
const unicode = supportsUnicode();
|
|
328
|
-
this.filledChar = unicode ? "█" : "#";
|
|
329
|
-
this.emptyChar = unicode ? "░" : "-";
|
|
330
|
-
}
|
|
331
|
-
start() {
|
|
332
|
-
if (!isInteractive())
|
|
333
|
-
return this;
|
|
334
|
-
hideCursor();
|
|
335
|
-
this.setupSignalHandler();
|
|
336
|
-
this.timer = setInterval(() => {
|
|
337
|
-
if (this.bars.size > 0 && [...this.bars.values()].some((b) => b.status === "active")) {
|
|
338
|
-
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;
|
|
339
|
-
this.render();
|
|
340
|
-
}
|
|
341
|
-
}, this.options.spinnerInterval);
|
|
342
|
-
return this;
|
|
343
|
-
}
|
|
344
|
-
add(config) {
|
|
345
|
-
const state = {
|
|
346
|
-
id: config.id,
|
|
347
|
-
label: config.label,
|
|
348
|
-
total: config.total ?? 100,
|
|
349
|
-
current: config.current ?? 0,
|
|
350
|
-
status: "active",
|
|
351
|
-
startTime: Date.now()
|
|
352
|
-
};
|
|
353
|
-
this.bars.set(config.id, state);
|
|
354
|
-
if (!this.barOrder.includes(config.id)) {
|
|
355
|
-
this.barOrder.push(config.id);
|
|
356
|
-
}
|
|
357
|
-
if (!isInteractive()) {
|
|
358
|
-
console.log(`${this.symbols.bullet} ${config.label}`);
|
|
359
|
-
} else {
|
|
360
|
-
this.render();
|
|
361
|
-
}
|
|
362
|
-
return this;
|
|
363
|
-
}
|
|
364
|
-
update(id, current, label) {
|
|
365
|
-
const bar = this.bars.get(id);
|
|
366
|
-
if (!bar)
|
|
367
|
-
return this;
|
|
368
|
-
bar.current = Math.min(current, bar.total);
|
|
369
|
-
if (label !== undefined)
|
|
370
|
-
bar.label = label;
|
|
371
|
-
if (isInteractive()) {
|
|
372
|
-
this.render();
|
|
373
|
-
}
|
|
374
|
-
return this;
|
|
375
|
-
}
|
|
376
|
-
increment(id, amount = 1) {
|
|
377
|
-
const bar = this.bars.get(id);
|
|
378
|
-
if (!bar)
|
|
379
|
-
return this;
|
|
380
|
-
return this.update(id, bar.current + amount);
|
|
381
|
-
}
|
|
382
|
-
complete(id, label) {
|
|
383
|
-
const bar = this.bars.get(id);
|
|
384
|
-
if (!bar)
|
|
385
|
-
return this;
|
|
386
|
-
bar.status = "completed";
|
|
387
|
-
bar.current = bar.total;
|
|
388
|
-
if (label !== undefined)
|
|
389
|
-
bar.label = label;
|
|
390
|
-
if (!isInteractive()) {
|
|
391
|
-
console.log(`${colors.success(this.symbols.success)} ${bar.label}`);
|
|
392
|
-
} else {
|
|
393
|
-
this.render();
|
|
394
|
-
}
|
|
395
|
-
return this;
|
|
396
|
-
}
|
|
397
|
-
fail(id, label) {
|
|
398
|
-
const bar = this.bars.get(id);
|
|
399
|
-
if (!bar)
|
|
400
|
-
return this;
|
|
401
|
-
bar.status = "failed";
|
|
402
|
-
if (label !== undefined)
|
|
403
|
-
bar.label = label;
|
|
404
|
-
if (!isInteractive()) {
|
|
405
|
-
console.log(`${colors.error(this.symbols.error)} ${bar.label}`);
|
|
406
|
-
} else {
|
|
407
|
-
this.render();
|
|
408
|
-
}
|
|
409
|
-
return this;
|
|
410
|
-
}
|
|
411
|
-
remove(id) {
|
|
412
|
-
this.bars.delete(id);
|
|
413
|
-
this.barOrder = this.barOrder.filter((i) => i !== id);
|
|
414
|
-
if (isInteractive()) {
|
|
415
|
-
this.render();
|
|
416
|
-
}
|
|
417
|
-
return this;
|
|
418
|
-
}
|
|
419
|
-
get(id) {
|
|
420
|
-
return this.bars.get(id);
|
|
421
|
-
}
|
|
422
|
-
get allDone() {
|
|
423
|
-
if (this.bars.size === 0)
|
|
424
|
-
return true;
|
|
425
|
-
return [...this.bars.values()].every((b) => b.status !== "active");
|
|
426
|
-
}
|
|
427
|
-
stop() {
|
|
428
|
-
if (this.timer) {
|
|
429
|
-
clearInterval(this.timer);
|
|
430
|
-
this.timer = null;
|
|
431
|
-
}
|
|
432
|
-
this.cleanup();
|
|
433
|
-
return this;
|
|
434
|
-
}
|
|
435
|
-
render() {
|
|
436
|
-
if (!isTTY())
|
|
437
|
-
return;
|
|
438
|
-
this.clearOutput();
|
|
439
|
-
const width = getTerminalWidth();
|
|
440
|
-
const lines = [];
|
|
441
|
-
for (const id of this.barOrder) {
|
|
442
|
-
const bar = this.bars.get(id);
|
|
443
|
-
if (!bar)
|
|
444
|
-
continue;
|
|
445
|
-
const line = this.renderBar(bar, width);
|
|
446
|
-
lines.push(line);
|
|
447
|
-
}
|
|
448
|
-
if (lines.length > 0) {
|
|
449
|
-
process.stdout.write(lines.join(`
|
|
450
|
-
`));
|
|
451
|
-
this.lastRenderedLines = lines.length;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
renderBar(bar, termWidth) {
|
|
455
|
-
const parts = [];
|
|
456
|
-
let symbol;
|
|
457
|
-
switch (bar.status) {
|
|
458
|
-
case "completed":
|
|
459
|
-
symbol = colors.success(this.symbols.success);
|
|
460
|
-
break;
|
|
461
|
-
case "failed":
|
|
462
|
-
symbol = colors.error(this.symbols.error);
|
|
463
|
-
break;
|
|
464
|
-
default:
|
|
465
|
-
symbol = colors.primary(this.spinnerFrames[this.spinnerIndex]);
|
|
466
|
-
}
|
|
467
|
-
parts.push(symbol);
|
|
468
|
-
parts.push(bar.label);
|
|
469
|
-
const suffixParts = [];
|
|
470
|
-
if (this.options.showPercent) {
|
|
471
|
-
const pct = bar.total === 0 ? 0 : Math.round(bar.current / bar.total * 100);
|
|
472
|
-
suffixParts.push(`${pct}%`);
|
|
473
|
-
}
|
|
474
|
-
if (this.options.showCount) {
|
|
475
|
-
suffixParts.push(`${bar.current}/${bar.total}`);
|
|
476
|
-
}
|
|
477
|
-
const suffix = suffixParts.length > 0 ? ` ${suffixParts.join(" ")}` : "";
|
|
478
|
-
const labelLen = stripAnsi(parts.join(" ")).length + 1;
|
|
479
|
-
const bracketLen = 2;
|
|
480
|
-
const suffixLen = stripAnsi(suffix).length;
|
|
481
|
-
const minBarWidth = 10;
|
|
482
|
-
const availableWidth = termWidth - labelLen - bracketLen - suffixLen - 1;
|
|
483
|
-
const barWidth = this.options.barWidth ?? Math.max(minBarWidth, Math.min(30, availableWidth));
|
|
484
|
-
const filledWidth = Math.round(bar.current / bar.total * barWidth);
|
|
485
|
-
const emptyWidth = barWidth - filledWidth;
|
|
486
|
-
const barViz = `[${this.filledChar.repeat(filledWidth)}${this.emptyChar.repeat(emptyWidth)}]`;
|
|
487
|
-
parts.push(barViz);
|
|
488
|
-
return truncate(parts.join(" ") + suffix, termWidth);
|
|
489
|
-
}
|
|
490
|
-
clearOutput() {
|
|
491
|
-
if (!isTTY() || this.lastRenderedLines === 0)
|
|
492
|
-
return;
|
|
493
|
-
for (let i = 0;i < this.lastRenderedLines; i++) {
|
|
494
|
-
if (i > 0)
|
|
495
|
-
process.stdout.write(cursor.up(1));
|
|
496
|
-
clearLine();
|
|
497
|
-
}
|
|
498
|
-
this.lastRenderedLines = 0;
|
|
499
|
-
}
|
|
500
|
-
setupSignalHandler() {
|
|
501
|
-
this.sigintHandler = () => {
|
|
502
|
-
this.cleanup();
|
|
503
|
-
process.exit(130);
|
|
504
|
-
};
|
|
505
|
-
process.on("SIGINT", this.sigintHandler);
|
|
506
|
-
}
|
|
507
|
-
cleanup() {
|
|
508
|
-
if (this.sigintHandler) {
|
|
509
|
-
process.removeListener("SIGINT", this.sigintHandler);
|
|
510
|
-
this.sigintHandler = null;
|
|
511
|
-
}
|
|
512
|
-
showCursor();
|
|
513
|
-
if (isTTY() && this.lastRenderedLines > 0) {
|
|
514
|
-
process.stdout.write(`
|
|
515
|
-
`);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
class ProgressBar {
|
|
520
|
-
total;
|
|
521
|
-
current;
|
|
522
|
-
label;
|
|
523
|
-
width;
|
|
524
|
-
showPercent;
|
|
525
|
-
showCount;
|
|
526
|
-
showETA;
|
|
527
|
-
filledChar;
|
|
528
|
-
emptyChar;
|
|
529
|
-
startTime = null;
|
|
530
|
-
lastRender = "";
|
|
531
|
-
symbols = getSymbols(supportsUnicode());
|
|
532
|
-
sigintHandler = null;
|
|
533
|
-
isComplete = false;
|
|
534
|
-
constructor(options = {}) {
|
|
535
|
-
this.total = options.total ?? 100;
|
|
536
|
-
this.current = options.current ?? 0;
|
|
537
|
-
this.label = options.label ?? "";
|
|
538
|
-
this.width = options.width;
|
|
539
|
-
this.showPercent = options.showPercent ?? true;
|
|
540
|
-
this.showCount = options.showCount ?? true;
|
|
541
|
-
this.showETA = options.showETA ?? true;
|
|
542
|
-
const unicode = supportsUnicode();
|
|
543
|
-
this.filledChar = options.chars?.filled ?? (unicode ? "█" : "#");
|
|
544
|
-
this.emptyChar = options.chars?.empty ?? (unicode ? "░" : "-");
|
|
545
|
-
}
|
|
546
|
-
start(label) {
|
|
547
|
-
if (label !== undefined)
|
|
548
|
-
this.label = label;
|
|
549
|
-
this.startTime = Date.now();
|
|
550
|
-
this.current = 0;
|
|
551
|
-
this.isComplete = false;
|
|
552
|
-
if (!isInteractive()) {
|
|
553
|
-
console.log(`${this.symbols.bullet} ${this.label}`);
|
|
554
|
-
return this;
|
|
555
|
-
}
|
|
556
|
-
hideCursor();
|
|
557
|
-
this.setupSignalHandler();
|
|
558
|
-
this.render();
|
|
559
|
-
return this;
|
|
560
|
-
}
|
|
561
|
-
update(current) {
|
|
562
|
-
this.current = Math.min(current, this.total);
|
|
563
|
-
if (isInteractive()) {
|
|
564
|
-
this.render();
|
|
565
|
-
}
|
|
566
|
-
return this;
|
|
567
|
-
}
|
|
568
|
-
increment(amount = 1) {
|
|
569
|
-
return this.update(this.current + amount);
|
|
570
|
-
}
|
|
571
|
-
setLabel(label) {
|
|
572
|
-
this.label = label;
|
|
573
|
-
if (isInteractive()) {
|
|
574
|
-
this.render();
|
|
575
|
-
}
|
|
576
|
-
return this;
|
|
577
|
-
}
|
|
578
|
-
setTotal(total) {
|
|
579
|
-
this.total = total;
|
|
580
|
-
if (isInteractive()) {
|
|
581
|
-
this.render();
|
|
582
|
-
}
|
|
583
|
-
return this;
|
|
584
|
-
}
|
|
585
|
-
complete(label) {
|
|
586
|
-
if (label !== undefined)
|
|
587
|
-
this.label = label;
|
|
588
|
-
this.current = this.total;
|
|
589
|
-
this.isComplete = true;
|
|
590
|
-
if (!isInteractive()) {
|
|
591
|
-
console.log(`${colors.success(this.symbols.success)} ${this.label}`);
|
|
592
|
-
} else {
|
|
593
|
-
clearLine();
|
|
594
|
-
process.stdout.write(`${colors.success(this.symbols.success)} ${this.label}
|
|
595
|
-
`);
|
|
596
|
-
}
|
|
597
|
-
this.cleanup();
|
|
598
|
-
return this;
|
|
599
|
-
}
|
|
600
|
-
fail(label) {
|
|
601
|
-
if (label !== undefined)
|
|
602
|
-
this.label = label;
|
|
603
|
-
this.isComplete = true;
|
|
604
|
-
if (!isInteractive()) {
|
|
605
|
-
console.log(`${colors.error(this.symbols.error)} ${this.label}`);
|
|
606
|
-
} else {
|
|
607
|
-
clearLine();
|
|
608
|
-
process.stdout.write(`${colors.error(this.symbols.error)} ${this.label}
|
|
609
|
-
`);
|
|
610
|
-
}
|
|
611
|
-
this.cleanup();
|
|
612
|
-
return this;
|
|
613
|
-
}
|
|
614
|
-
get percentage() {
|
|
615
|
-
return this.total === 0 ? 0 : Math.round(this.current / this.total * 100);
|
|
616
|
-
}
|
|
617
|
-
get isDone() {
|
|
618
|
-
return this.isComplete || this.current >= this.total;
|
|
619
|
-
}
|
|
620
|
-
render() {
|
|
621
|
-
if (!isTTY())
|
|
622
|
-
return;
|
|
623
|
-
const termWidth = getTerminalWidth();
|
|
624
|
-
const parts = [];
|
|
625
|
-
if (this.label) {
|
|
626
|
-
parts.push(this.label);
|
|
627
|
-
}
|
|
628
|
-
const suffixParts = [];
|
|
629
|
-
if (this.showPercent) {
|
|
630
|
-
suffixParts.push(`${this.percentage}%`);
|
|
631
|
-
}
|
|
632
|
-
if (this.showCount) {
|
|
633
|
-
suffixParts.push(`${this.current}/${this.total}`);
|
|
634
|
-
}
|
|
635
|
-
if (this.showETA && this.startTime) {
|
|
636
|
-
const eta = this.calculateETA();
|
|
637
|
-
if (eta)
|
|
638
|
-
suffixParts.push(eta);
|
|
639
|
-
}
|
|
640
|
-
const suffix = suffixParts.length > 0 ? ` ${suffixParts.join(" ")}` : "";
|
|
641
|
-
const labelLen = this.label ? stripAnsi(this.label).length + 1 : 0;
|
|
642
|
-
const bracketLen = 2;
|
|
643
|
-
const suffixLen = stripAnsi(suffix).length;
|
|
644
|
-
const minBarWidth = 10;
|
|
645
|
-
const availableWidth = termWidth - labelLen - bracketLen - suffixLen - 1;
|
|
646
|
-
const barWidth = this.width ?? Math.max(minBarWidth, Math.min(40, availableWidth));
|
|
647
|
-
const filledWidth = Math.round(this.current / this.total * barWidth);
|
|
648
|
-
const emptyWidth = barWidth - filledWidth;
|
|
649
|
-
const bar = `[${this.filledChar.repeat(filledWidth)}${this.emptyChar.repeat(emptyWidth)}]`;
|
|
650
|
-
parts.push(bar);
|
|
651
|
-
const line = truncate(parts.join(" ") + suffix, termWidth);
|
|
652
|
-
if (line !== this.lastRender) {
|
|
653
|
-
clearLine();
|
|
654
|
-
process.stdout.write(line);
|
|
655
|
-
this.lastRender = line;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
calculateETA() {
|
|
659
|
-
if (!this.startTime || this.current === 0)
|
|
660
|
-
return null;
|
|
661
|
-
const elapsed = Date.now() - this.startTime;
|
|
662
|
-
if (elapsed < 1000)
|
|
663
|
-
return null;
|
|
664
|
-
const rate = this.current / elapsed;
|
|
665
|
-
const remaining = this.total - this.current;
|
|
666
|
-
const etaMs = remaining / rate;
|
|
667
|
-
return `ETA ${formatDuration(etaMs)}`;
|
|
668
|
-
}
|
|
669
|
-
setupSignalHandler() {
|
|
670
|
-
this.sigintHandler = () => {
|
|
671
|
-
this.cleanup();
|
|
672
|
-
process.exit(130);
|
|
673
|
-
};
|
|
674
|
-
process.on("SIGINT", this.sigintHandler);
|
|
675
|
-
}
|
|
676
|
-
cleanup() {
|
|
677
|
-
if (this.sigintHandler) {
|
|
678
|
-
process.removeListener("SIGINT", this.sigintHandler);
|
|
679
|
-
this.sigintHandler = null;
|
|
680
|
-
}
|
|
681
|
-
showCursor();
|
|
682
|
-
}
|
|
683
|
-
}
|
|
260
|
+
// src/utils/progress/spinner.ts
|
|
684
261
|
var spinnerColors = {
|
|
685
|
-
cyan:
|
|
686
|
-
yellow:
|
|
687
|
-
green:
|
|
688
|
-
red:
|
|
689
|
-
magenta:
|
|
690
|
-
blue:
|
|
691
|
-
white:
|
|
262
|
+
cyan: chalk3.cyan,
|
|
263
|
+
yellow: chalk3.yellow,
|
|
264
|
+
green: chalk3.green,
|
|
265
|
+
red: chalk3.red,
|
|
266
|
+
magenta: chalk3.magenta,
|
|
267
|
+
blue: chalk3.blue,
|
|
268
|
+
white: chalk3.white
|
|
692
269
|
};
|
|
693
270
|
var FRAME_SETS = {
|
|
694
271
|
dots: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
|
@@ -708,13 +285,11 @@ class Spinner {
|
|
|
708
285
|
symbols = getSymbols(supportsUnicode());
|
|
709
286
|
lastRenderedLines = 0;
|
|
710
287
|
sigintHandler = null;
|
|
711
|
-
animate;
|
|
712
288
|
constructor(options = {}) {
|
|
713
289
|
this.label = options.label ?? "";
|
|
714
290
|
this.detail = options.detail;
|
|
715
291
|
this.interval = options.interval ?? 80;
|
|
716
292
|
this.colorFn = spinnerColors[options.color ?? "cyan"];
|
|
717
|
-
this.animate = options.animate ?? true;
|
|
718
293
|
const style = options.style ?? "circle";
|
|
719
294
|
this.frames = supportsUnicode() ? FRAME_SETS[style] : ASCII_FRAME_SET;
|
|
720
295
|
}
|
|
@@ -726,7 +301,7 @@ class Spinner {
|
|
|
726
301
|
this.state = "spinning";
|
|
727
302
|
this.frameIndex = 0;
|
|
728
303
|
this.lastRenderedLines = 0;
|
|
729
|
-
if (!isInteractive()
|
|
304
|
+
if (!isInteractive()) {
|
|
730
305
|
console.log(`${this.symbols.bullet} ${this.label}`);
|
|
731
306
|
return this;
|
|
732
307
|
}
|
|
@@ -784,12 +359,14 @@ class Spinner {
|
|
|
784
359
|
this.timer = null;
|
|
785
360
|
}
|
|
786
361
|
this.state = state;
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
362
|
+
if (!isInteractive()) {
|
|
363
|
+
const symbol = state === "success" ? this.symbols.success : this.symbols.error;
|
|
364
|
+
const colorFn = state === "success" ? colors.success : colors.error;
|
|
790
365
|
console.log(`${colorFn(symbol)} ${this.label}`);
|
|
791
366
|
} else {
|
|
792
367
|
this.clearOutput();
|
|
368
|
+
const symbol = state === "success" ? this.symbols.success : this.symbols.error;
|
|
369
|
+
const colorFn = state === "success" ? colors.success : colors.error;
|
|
793
370
|
process.stdout.write(`${colorFn(symbol)} ${this.label}
|
|
794
371
|
`);
|
|
795
372
|
}
|
|
@@ -839,206 +416,7 @@ ${detailLine}`);
|
|
|
839
416
|
function spinner(label, options) {
|
|
840
417
|
return new Spinner({ ...options, label }).start();
|
|
841
418
|
}
|
|
842
|
-
|
|
843
|
-
class StepProgress {
|
|
844
|
-
steps = [];
|
|
845
|
-
showNumbers;
|
|
846
|
-
spinnerInterval;
|
|
847
|
-
spinnerFrames;
|
|
848
|
-
spinnerIndex = 0;
|
|
849
|
-
timer = null;
|
|
850
|
-
symbols = getSymbols(supportsUnicode());
|
|
851
|
-
lastRenderedLines = 0;
|
|
852
|
-
sigintHandler = null;
|
|
853
|
-
constructor(options = {}) {
|
|
854
|
-
this.showNumbers = options.showNumbers ?? true;
|
|
855
|
-
this.spinnerInterval = options.spinnerInterval ?? 80;
|
|
856
|
-
this.spinnerFrames = supportsUnicode() ? ["◐", "◓", "◑", "◒"] : ["-", "\\", "|", "/"];
|
|
857
|
-
if (options.steps) {
|
|
858
|
-
this.steps = options.steps.map((label) => ({ label, status: "pending" }));
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
start() {
|
|
862
|
-
if (!isInteractive())
|
|
863
|
-
return this;
|
|
864
|
-
hideCursor();
|
|
865
|
-
this.setupSignalHandler();
|
|
866
|
-
this.render();
|
|
867
|
-
this.timer = setInterval(() => {
|
|
868
|
-
if (this.steps.some((s) => s.status === "active")) {
|
|
869
|
-
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;
|
|
870
|
-
this.render();
|
|
871
|
-
}
|
|
872
|
-
}, this.spinnerInterval);
|
|
873
|
-
return this;
|
|
874
|
-
}
|
|
875
|
-
addStep(label) {
|
|
876
|
-
this.steps.push({ label, status: "pending" });
|
|
877
|
-
if (isInteractive())
|
|
878
|
-
this.render();
|
|
879
|
-
return this;
|
|
880
|
-
}
|
|
881
|
-
startStep(index) {
|
|
882
|
-
if (index >= 0 && index < this.steps.length) {
|
|
883
|
-
this.steps[index].status = "active";
|
|
884
|
-
this.steps[index].startTime = Date.now();
|
|
885
|
-
if (isInteractive()) {
|
|
886
|
-
this.render();
|
|
887
|
-
} else {
|
|
888
|
-
const step = this.steps[index];
|
|
889
|
-
const prefix2 = this.showNumbers ? `[${index + 1}/${this.steps.length}] ` : "";
|
|
890
|
-
console.log(`${this.symbols.bullet} ${prefix2}${step.label}...`);
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
return this;
|
|
894
|
-
}
|
|
895
|
-
completeStep(index) {
|
|
896
|
-
if (index >= 0 && index < this.steps.length) {
|
|
897
|
-
this.steps[index].status = "completed";
|
|
898
|
-
this.steps[index].endTime = Date.now();
|
|
899
|
-
if (isInteractive()) {
|
|
900
|
-
this.render();
|
|
901
|
-
} else {
|
|
902
|
-
const step = this.steps[index];
|
|
903
|
-
const duration = this.getStepDuration(step);
|
|
904
|
-
const prefix2 = this.showNumbers ? `[${index + 1}/${this.steps.length}] ` : "";
|
|
905
|
-
console.log(`${colors.success(this.symbols.success)} ${prefix2}${step.label}${duration}`);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
return this;
|
|
909
|
-
}
|
|
910
|
-
failStep(index) {
|
|
911
|
-
if (index >= 0 && index < this.steps.length) {
|
|
912
|
-
this.steps[index].status = "failed";
|
|
913
|
-
this.steps[index].endTime = Date.now();
|
|
914
|
-
if (isInteractive()) {
|
|
915
|
-
this.render();
|
|
916
|
-
} else {
|
|
917
|
-
const step = this.steps[index];
|
|
918
|
-
const prefix2 = this.showNumbers ? `[${index + 1}/${this.steps.length}] ` : "";
|
|
919
|
-
console.log(`${colors.error(this.symbols.error)} ${prefix2}${step.label}`);
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
return this;
|
|
923
|
-
}
|
|
924
|
-
skipStep(index) {
|
|
925
|
-
if (index >= 0 && index < this.steps.length) {
|
|
926
|
-
this.steps[index].status = "skipped";
|
|
927
|
-
if (isInteractive())
|
|
928
|
-
this.render();
|
|
929
|
-
}
|
|
930
|
-
return this;
|
|
931
|
-
}
|
|
932
|
-
async run(tasks) {
|
|
933
|
-
this.steps = tasks.map((t) => ({ label: t.label, status: "pending" }));
|
|
934
|
-
this.start();
|
|
935
|
-
const results = [];
|
|
936
|
-
let failed = false;
|
|
937
|
-
for (let i = 0;i < tasks.length; i++) {
|
|
938
|
-
if (failed) {
|
|
939
|
-
this.skipStep(i);
|
|
940
|
-
continue;
|
|
941
|
-
}
|
|
942
|
-
this.startStep(i);
|
|
943
|
-
try {
|
|
944
|
-
results.push(await tasks[i].task());
|
|
945
|
-
this.completeStep(i);
|
|
946
|
-
} catch {
|
|
947
|
-
this.failStep(i);
|
|
948
|
-
failed = true;
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
this.stop();
|
|
952
|
-
return { results, failed };
|
|
953
|
-
}
|
|
954
|
-
stop() {
|
|
955
|
-
if (this.timer) {
|
|
956
|
-
clearInterval(this.timer);
|
|
957
|
-
this.timer = null;
|
|
958
|
-
}
|
|
959
|
-
this.cleanup();
|
|
960
|
-
return this;
|
|
961
|
-
}
|
|
962
|
-
get currentStepIndex() {
|
|
963
|
-
const activeIdx = this.steps.findIndex((s) => s.status === "active");
|
|
964
|
-
if (activeIdx >= 0)
|
|
965
|
-
return activeIdx;
|
|
966
|
-
return this.steps.findIndex((s) => s.status === "pending");
|
|
967
|
-
}
|
|
968
|
-
render() {
|
|
969
|
-
if (!isTTY())
|
|
970
|
-
return;
|
|
971
|
-
if (this.lastRenderedLines > 0) {
|
|
972
|
-
moveCursorUp(this.lastRenderedLines - 1);
|
|
973
|
-
for (let i = 0;i < this.lastRenderedLines; i++) {
|
|
974
|
-
clearLine();
|
|
975
|
-
if (i < this.lastRenderedLines - 1) {
|
|
976
|
-
process.stdout.write(cursor.down(1));
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
moveCursorUp(this.lastRenderedLines - 1);
|
|
980
|
-
}
|
|
981
|
-
const width = getTerminalWidth();
|
|
982
|
-
const lines = [];
|
|
983
|
-
for (let i = 0;i < this.steps.length; i++) {
|
|
984
|
-
const step = this.steps[i];
|
|
985
|
-
const prefix2 = this.showNumbers ? `[${i + 1}/${this.steps.length}] ` : "";
|
|
986
|
-
const duration = this.getStepDuration(step);
|
|
987
|
-
let symbol;
|
|
988
|
-
let text;
|
|
989
|
-
switch (step.status) {
|
|
990
|
-
case "completed":
|
|
991
|
-
symbol = colors.success(this.symbols.success);
|
|
992
|
-
text = `${prefix2}${step.label}${duration}`;
|
|
993
|
-
break;
|
|
994
|
-
case "failed":
|
|
995
|
-
symbol = colors.error(this.symbols.error);
|
|
996
|
-
text = `${prefix2}${step.label}`;
|
|
997
|
-
break;
|
|
998
|
-
case "active":
|
|
999
|
-
symbol = colors.primary(this.spinnerFrames[this.spinnerIndex]);
|
|
1000
|
-
text = `${prefix2}${step.label}`;
|
|
1001
|
-
break;
|
|
1002
|
-
case "skipped":
|
|
1003
|
-
symbol = colors.muted(this.symbols.bullet);
|
|
1004
|
-
text = colors.muted(`${prefix2}${step.label} (skipped)`);
|
|
1005
|
-
break;
|
|
1006
|
-
default:
|
|
1007
|
-
symbol = colors.muted("○");
|
|
1008
|
-
text = colors.muted(`${prefix2}${step.label}`);
|
|
1009
|
-
}
|
|
1010
|
-
lines.push(truncate(`${symbol} ${text}`, width));
|
|
1011
|
-
}
|
|
1012
|
-
process.stdout.write(lines.join(`
|
|
1013
|
-
`));
|
|
1014
|
-
this.lastRenderedLines = lines.length;
|
|
1015
|
-
}
|
|
1016
|
-
getStepDuration(step) {
|
|
1017
|
-
if (step.startTime && step.endTime) {
|
|
1018
|
-
const ms = step.endTime - step.startTime;
|
|
1019
|
-
return colors.muted(` (${formatDuration(ms)})`);
|
|
1020
|
-
}
|
|
1021
|
-
return "";
|
|
1022
|
-
}
|
|
1023
|
-
setupSignalHandler() {
|
|
1024
|
-
this.sigintHandler = () => {
|
|
1025
|
-
this.cleanup();
|
|
1026
|
-
process.exit(130);
|
|
1027
|
-
};
|
|
1028
|
-
process.on("SIGINT", this.sigintHandler);
|
|
1029
|
-
}
|
|
1030
|
-
cleanup() {
|
|
1031
|
-
if (this.sigintHandler) {
|
|
1032
|
-
process.removeListener("SIGINT", this.sigintHandler);
|
|
1033
|
-
this.sigintHandler = null;
|
|
1034
|
-
}
|
|
1035
|
-
showCursor();
|
|
1036
|
-
if (isTTY() && this.lastRenderedLines > 0) {
|
|
1037
|
-
process.stdout.write(`
|
|
1038
|
-
`);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
419
|
+
// src/utils/progress/summary.ts
|
|
1042
420
|
class Summary {
|
|
1043
421
|
items = [];
|
|
1044
422
|
title;
|
|
@@ -1149,70 +527,18 @@ class Summary {
|
|
|
1149
527
|
switch (operator) {
|
|
1150
528
|
case "<":
|
|
1151
529
|
return value < thresh;
|
|
1152
|
-
case ">":
|
|
1153
|
-
return value > thresh;
|
|
1154
|
-
case "<=":
|
|
1155
|
-
return value <= thresh;
|
|
1156
|
-
case ">=":
|
|
1157
|
-
return value >= thresh;
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
function summary(options) {
|
|
1162
|
-
return new Summary(options);
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
// src/utils/filter-options.ts
|
|
1166
|
-
import { mergeFilters, parseListFlag } from "@doccov/sdk";
|
|
1167
|
-
import chalk3 from "chalk";
|
|
1168
|
-
var parseVisibilityFlag = (value) => {
|
|
1169
|
-
if (!value)
|
|
1170
|
-
return;
|
|
1171
|
-
const validTags = ["public", "beta", "alpha", "internal"];
|
|
1172
|
-
const parsed = parseListFlag(value);
|
|
1173
|
-
if (!parsed)
|
|
1174
|
-
return;
|
|
1175
|
-
const result = [];
|
|
1176
|
-
for (const tag of parsed) {
|
|
1177
|
-
const lower = tag.toLowerCase();
|
|
1178
|
-
if (validTags.includes(lower)) {
|
|
1179
|
-
result.push(lower);
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
return result.length > 0 ? result : undefined;
|
|
1183
|
-
};
|
|
1184
|
-
var formatList = (label, values) => `${label}: ${values.map((value) => chalk3.cyan(value)).join(", ")}`;
|
|
1185
|
-
var mergeFilterOptions = (config, cliOptions) => {
|
|
1186
|
-
const messages = [];
|
|
1187
|
-
if (config?.include) {
|
|
1188
|
-
messages.push(formatList("include filters from config", config.include));
|
|
1189
|
-
}
|
|
1190
|
-
if (config?.exclude) {
|
|
1191
|
-
messages.push(formatList("exclude filters from config", config.exclude));
|
|
1192
|
-
}
|
|
1193
|
-
if (cliOptions.include) {
|
|
1194
|
-
messages.push(formatList("apply include filters from CLI", cliOptions.include));
|
|
1195
|
-
}
|
|
1196
|
-
if (cliOptions.exclude) {
|
|
1197
|
-
messages.push(formatList("apply exclude filters from CLI", cliOptions.exclude));
|
|
1198
|
-
}
|
|
1199
|
-
if (cliOptions.visibility) {
|
|
1200
|
-
messages.push(formatList("apply visibility filter from CLI", cliOptions.visibility));
|
|
1201
|
-
}
|
|
1202
|
-
const resolved = mergeFilters(config, cliOptions);
|
|
1203
|
-
if (!resolved.include && !resolved.exclude && !cliOptions.visibility) {
|
|
1204
|
-
return { messages };
|
|
530
|
+
case ">":
|
|
531
|
+
return value > thresh;
|
|
532
|
+
case "<=":
|
|
533
|
+
return value <= thresh;
|
|
534
|
+
case ">=":
|
|
535
|
+
return value >= thresh;
|
|
536
|
+
}
|
|
1205
537
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
visibility: cliOptions.visibility,
|
|
1211
|
-
source,
|
|
1212
|
-
messages
|
|
1213
|
-
};
|
|
1214
|
-
};
|
|
1215
|
-
|
|
538
|
+
}
|
|
539
|
+
function summary(options) {
|
|
540
|
+
return new Summary(options);
|
|
541
|
+
}
|
|
1216
542
|
// src/utils/validation.ts
|
|
1217
543
|
function clampPercentage(value, fallback = 80) {
|
|
1218
544
|
if (Number.isNaN(value))
|
|
@@ -1240,7 +566,7 @@ import {
|
|
|
1240
566
|
previewForgottenExportFixes,
|
|
1241
567
|
serializeJSDoc
|
|
1242
568
|
} from "@doccov/sdk";
|
|
1243
|
-
import
|
|
569
|
+
import chalk4 from "chalk";
|
|
1244
570
|
|
|
1245
571
|
// src/commands/check/utils.ts
|
|
1246
572
|
import * as fs from "node:fs";
|
|
@@ -1314,13 +640,13 @@ async function handleFixes(openpkg, doccov, options, deps) {
|
|
|
1314
640
|
}
|
|
1315
641
|
const { fixable, nonFixable } = categorizeDrifts(allDrifts.map((d) => d.drift));
|
|
1316
642
|
if (fixable.length === 0) {
|
|
1317
|
-
log(
|
|
643
|
+
log(chalk4.yellow(`Found ${nonFixable.length} drift issue(s), but none are auto-fixable.`));
|
|
1318
644
|
return { fixedDriftKeys, editsApplied: 0, filesModified: 0, forgottenExportsFixed: 0 };
|
|
1319
645
|
}
|
|
1320
646
|
log("");
|
|
1321
|
-
log(
|
|
647
|
+
log(chalk4.bold(`Found ${fixable.length} fixable issue(s)`));
|
|
1322
648
|
if (nonFixable.length > 0) {
|
|
1323
|
-
log(
|
|
649
|
+
log(chalk4.gray(`(${nonFixable.length} non-fixable issue(s) skipped)`));
|
|
1324
650
|
}
|
|
1325
651
|
log("");
|
|
1326
652
|
const groupedDrifts = groupByExport(allDrifts.filter((d) => fixable.includes(d.drift)));
|
|
@@ -1353,20 +679,20 @@ async function handleFixes(openpkg, doccov, options, deps) {
|
|
|
1353
679
|
const applyResult = await applyEdits(edits);
|
|
1354
680
|
if (applyResult.errors.length > 0) {
|
|
1355
681
|
for (const err of applyResult.errors) {
|
|
1356
|
-
error(
|
|
682
|
+
error(chalk4.red(` ${err.file}: ${err.error}`));
|
|
1357
683
|
}
|
|
1358
684
|
}
|
|
1359
685
|
const totalFixes = Array.from(editsByFile.values()).reduce((sum, fileEdits) => sum + fileEdits.reduce((s, e) => s + e.fixes.length, 0), 0);
|
|
1360
686
|
log("");
|
|
1361
|
-
log(
|
|
687
|
+
log(chalk4.green(`✓ Applied ${totalFixes} fix(es) to ${applyResult.filesModified} file(s)`));
|
|
1362
688
|
for (const [filePath, fileEdits] of editsByFile) {
|
|
1363
689
|
const relativePath = path3.relative(targetDir, filePath);
|
|
1364
690
|
const fixCount = fileEdits.reduce((s, e) => s + e.fixes.length, 0);
|
|
1365
|
-
log(
|
|
691
|
+
log(chalk4.dim(` ${relativePath} (${fixCount} fixes)`));
|
|
1366
692
|
}
|
|
1367
693
|
if (options.healthScore !== undefined) {
|
|
1368
694
|
log("");
|
|
1369
|
-
log(
|
|
695
|
+
log(chalk4.cyan("Run doccov check again to see updated health score"));
|
|
1370
696
|
}
|
|
1371
697
|
return {
|
|
1372
698
|
fixedDriftKeys,
|
|
@@ -1393,7 +719,7 @@ async function handleForgottenExportFixes(doccov, options, deps) {
|
|
|
1393
719
|
return { fixesApplied: 0, filesModified: 0 };
|
|
1394
720
|
}
|
|
1395
721
|
log("");
|
|
1396
|
-
log(
|
|
722
|
+
log(chalk4.bold(`Found ${fixes.length} forgotten export(s) to fix`));
|
|
1397
723
|
if (isPreview) {
|
|
1398
724
|
displayForgottenExportPreview(fixes, targetDir, log);
|
|
1399
725
|
return { fixesApplied: 0, filesModified: 0 };
|
|
@@ -1401,12 +727,12 @@ async function handleForgottenExportFixes(doccov, options, deps) {
|
|
|
1401
727
|
const result = await applyForgottenExportFixes(fixes);
|
|
1402
728
|
if (result.errors.length > 0) {
|
|
1403
729
|
for (const err of result.errors) {
|
|
1404
|
-
error(
|
|
730
|
+
error(chalk4.red(` ${err.file}: ${err.error}`));
|
|
1405
731
|
}
|
|
1406
732
|
}
|
|
1407
733
|
if (result.fixesApplied > 0) {
|
|
1408
734
|
log("");
|
|
1409
|
-
log(
|
|
735
|
+
log(chalk4.green(`✓ Added ${result.fixesApplied} export(s) to ${result.filesModified} file(s)`));
|
|
1410
736
|
const grouped = new Map;
|
|
1411
737
|
for (const fix of fixes) {
|
|
1412
738
|
const relativePath = path3.relative(targetDir, fix.targetFile);
|
|
@@ -1415,45 +741,45 @@ async function handleForgottenExportFixes(doccov, options, deps) {
|
|
|
1415
741
|
grouped.set(relativePath, types);
|
|
1416
742
|
}
|
|
1417
743
|
for (const [file, types] of grouped) {
|
|
1418
|
-
log(
|
|
744
|
+
log(chalk4.dim(` ${file}: ${types.join(", ")}`));
|
|
1419
745
|
}
|
|
1420
746
|
}
|
|
1421
747
|
return { fixesApplied: result.fixesApplied, filesModified: result.filesModified };
|
|
1422
748
|
}
|
|
1423
749
|
function displayForgottenExportPreview(fixes, targetDir, log) {
|
|
1424
|
-
log(
|
|
750
|
+
log(chalk4.bold("Preview - forgotten exports that would be added:"));
|
|
1425
751
|
log("");
|
|
1426
752
|
const previews = previewForgottenExportFixes(fixes);
|
|
1427
753
|
for (const [filePath, preview] of previews) {
|
|
1428
754
|
const relativePath = path3.relative(targetDir, filePath);
|
|
1429
|
-
log(
|
|
755
|
+
log(chalk4.cyan(`${relativePath}:${preview.insertLine + 1}`));
|
|
1430
756
|
log("");
|
|
1431
757
|
for (const stmt of preview.statements) {
|
|
1432
|
-
log(
|
|
758
|
+
log(chalk4.green(` + ${stmt}`));
|
|
1433
759
|
}
|
|
1434
760
|
log("");
|
|
1435
761
|
}
|
|
1436
|
-
log(
|
|
1437
|
-
log(
|
|
762
|
+
log(chalk4.yellow(`${fixes.length} export(s) would be added.`));
|
|
763
|
+
log(chalk4.gray("Run with --fix to apply these changes."));
|
|
1438
764
|
}
|
|
1439
765
|
function generateEditForExport(exp, drifts, targetDir, log) {
|
|
1440
766
|
if (!exp.source?.file) {
|
|
1441
|
-
log(
|
|
767
|
+
log(chalk4.gray(` Skipping ${exp.name}: no source location`));
|
|
1442
768
|
return null;
|
|
1443
769
|
}
|
|
1444
770
|
if (exp.source.file.endsWith(".d.ts")) {
|
|
1445
|
-
log(
|
|
771
|
+
log(chalk4.gray(` Skipping ${exp.name}: declaration file`));
|
|
1446
772
|
return null;
|
|
1447
773
|
}
|
|
1448
774
|
const filePath = path3.resolve(targetDir, exp.source.file);
|
|
1449
775
|
if (!fs2.existsSync(filePath)) {
|
|
1450
|
-
log(
|
|
776
|
+
log(chalk4.gray(` Skipping ${exp.name}: file not found`));
|
|
1451
777
|
return null;
|
|
1452
778
|
}
|
|
1453
779
|
const sourceFile = createSourceFile(filePath);
|
|
1454
780
|
const location = findJSDocLocation(sourceFile, exp.name, exp.source.line);
|
|
1455
781
|
if (!location) {
|
|
1456
|
-
log(
|
|
782
|
+
log(chalk4.gray(` Skipping ${exp.name}: could not find declaration`));
|
|
1457
783
|
return null;
|
|
1458
784
|
}
|
|
1459
785
|
let existingPatch = {};
|
|
@@ -1478,13 +804,13 @@ function generateEditForExport(exp, drifts, targetDir, log) {
|
|
|
1478
804
|
return { filePath, edit, fixes, existingPatch };
|
|
1479
805
|
}
|
|
1480
806
|
function displayPreview(editsByFile, targetDir, log) {
|
|
1481
|
-
log(
|
|
807
|
+
log(chalk4.bold("Preview - changes that would be made:"));
|
|
1482
808
|
log("");
|
|
1483
809
|
for (const [filePath, fileEdits] of editsByFile) {
|
|
1484
810
|
const relativePath = path3.relative(targetDir, filePath);
|
|
1485
811
|
for (const { export: exp, edit, fixes } of fileEdits) {
|
|
1486
|
-
log(
|
|
1487
|
-
log(
|
|
812
|
+
log(chalk4.cyan(`${relativePath}:${edit.startLine + 1}`));
|
|
813
|
+
log(chalk4.bold(` ${exp.name}`));
|
|
1488
814
|
log("");
|
|
1489
815
|
if (edit.hasExisting && edit.existingJSDoc) {
|
|
1490
816
|
const oldLines = edit.existingJSDoc.split(`
|
|
@@ -1492,98 +818,31 @@ function displayPreview(editsByFile, targetDir, log) {
|
|
|
1492
818
|
const newLines = edit.newJSDoc.split(`
|
|
1493
819
|
`);
|
|
1494
820
|
for (const line of oldLines) {
|
|
1495
|
-
log(
|
|
821
|
+
log(chalk4.red(` - ${line}`));
|
|
1496
822
|
}
|
|
1497
823
|
for (const line of newLines) {
|
|
1498
|
-
log(
|
|
824
|
+
log(chalk4.green(` + ${line}`));
|
|
1499
825
|
}
|
|
1500
826
|
} else {
|
|
1501
827
|
const newLines = edit.newJSDoc.split(`
|
|
1502
828
|
`);
|
|
1503
829
|
for (const line of newLines) {
|
|
1504
|
-
log(
|
|
830
|
+
log(chalk4.green(` + ${line}`));
|
|
1505
831
|
}
|
|
1506
832
|
}
|
|
1507
833
|
log("");
|
|
1508
|
-
log(
|
|
834
|
+
log(chalk4.dim(` Fixes: ${fixes.map((f) => f.description).join(", ")}`));
|
|
1509
835
|
log("");
|
|
1510
836
|
}
|
|
1511
837
|
}
|
|
1512
838
|
const totalFixes = Array.from(editsByFile.values()).reduce((sum, fileEdits) => sum + fileEdits.reduce((s, e) => s + e.fixes.length, 0), 0);
|
|
1513
|
-
log(
|
|
1514
|
-
log(
|
|
839
|
+
log(chalk4.yellow(`${totalFixes} fix(es) across ${editsByFile.size} file(s) would be applied.`));
|
|
840
|
+
log(chalk4.gray("Run with --fix to apply these changes."));
|
|
1515
841
|
}
|
|
1516
842
|
|
|
1517
843
|
// src/commands/check/output.ts
|
|
1518
844
|
import { generateReportFromDocCov } from "@doccov/sdk";
|
|
1519
845
|
|
|
1520
|
-
// src/reports/changelog-renderer.ts
|
|
1521
|
-
function renderChangelog(data, options = {}) {
|
|
1522
|
-
const { diff, categorizedBreaking } = data;
|
|
1523
|
-
const lines = [];
|
|
1524
|
-
const version = options.version ?? data.version ?? "Unreleased";
|
|
1525
|
-
const date = options.date instanceof Date ? options.date.toISOString().split("T")[0] : options.date ?? new Date().toISOString().split("T")[0];
|
|
1526
|
-
lines.push(`## [${version}] - ${date}`);
|
|
1527
|
-
lines.push("");
|
|
1528
|
-
if (diff.breaking.length > 0) {
|
|
1529
|
-
lines.push("### ⚠️ BREAKING CHANGES");
|
|
1530
|
-
lines.push("");
|
|
1531
|
-
if (categorizedBreaking && categorizedBreaking.length > 0) {
|
|
1532
|
-
for (const breaking of categorizedBreaking) {
|
|
1533
|
-
const severity = breaking.severity === "high" ? "**" : "";
|
|
1534
|
-
lines.push(`- ${severity}${breaking.name}${severity}: ${breaking.reason}`);
|
|
1535
|
-
}
|
|
1536
|
-
} else {
|
|
1537
|
-
for (const id of diff.breaking) {
|
|
1538
|
-
lines.push(`- \`${id}\` removed or changed`);
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
lines.push("");
|
|
1542
|
-
}
|
|
1543
|
-
if (diff.nonBreaking.length > 0) {
|
|
1544
|
-
lines.push("### Added");
|
|
1545
|
-
lines.push("");
|
|
1546
|
-
for (const id of diff.nonBreaking) {
|
|
1547
|
-
lines.push(`- \`${id}\``);
|
|
1548
|
-
}
|
|
1549
|
-
lines.push("");
|
|
1550
|
-
}
|
|
1551
|
-
if (diff.docsOnly.length > 0) {
|
|
1552
|
-
lines.push("### Documentation");
|
|
1553
|
-
lines.push("");
|
|
1554
|
-
for (const id of diff.docsOnly) {
|
|
1555
|
-
lines.push(`- Updated documentation for \`${id}\``);
|
|
1556
|
-
}
|
|
1557
|
-
lines.push("");
|
|
1558
|
-
}
|
|
1559
|
-
if (diff.coverageDelta !== 0) {
|
|
1560
|
-
lines.push("### Coverage");
|
|
1561
|
-
lines.push("");
|
|
1562
|
-
const arrow = diff.coverageDelta > 0 ? "↑" : "↓";
|
|
1563
|
-
const sign = diff.coverageDelta > 0 ? "+" : "";
|
|
1564
|
-
lines.push(`- Documentation coverage: ${diff.oldCoverage}% → ${diff.newCoverage}% (${arrow} ${sign}${diff.coverageDelta}%)`);
|
|
1565
|
-
lines.push("");
|
|
1566
|
-
}
|
|
1567
|
-
if (diff.driftIntroduced > 0 || diff.driftResolved > 0) {
|
|
1568
|
-
if (!lines.some((l) => l.startsWith("### Coverage"))) {
|
|
1569
|
-
lines.push("### Coverage");
|
|
1570
|
-
lines.push("");
|
|
1571
|
-
}
|
|
1572
|
-
if (diff.driftResolved > 0) {
|
|
1573
|
-
lines.push(`- Fixed ${diff.driftResolved} drift issue${diff.driftResolved === 1 ? "" : "s"}`);
|
|
1574
|
-
}
|
|
1575
|
-
if (diff.driftIntroduced > 0) {
|
|
1576
|
-
lines.push(`- ${diff.driftIntroduced} new drift issue${diff.driftIntroduced === 1 ? "" : "s"} detected`);
|
|
1577
|
-
}
|
|
1578
|
-
lines.push("");
|
|
1579
|
-
}
|
|
1580
|
-
if (options.compareUrl) {
|
|
1581
|
-
lines.push(`**Full Changelog**: ${options.compareUrl}`);
|
|
1582
|
-
lines.push("");
|
|
1583
|
-
}
|
|
1584
|
-
return lines.join(`
|
|
1585
|
-
`);
|
|
1586
|
-
}
|
|
1587
846
|
// src/reports/diff-markdown.ts
|
|
1588
847
|
import * as path4 from "node:path";
|
|
1589
848
|
function bar(pct, width = 10) {
|
|
@@ -1801,55 +1060,6 @@ function renderDiffHtml(data, options = {}) {
|
|
|
1801
1060
|
</body>
|
|
1802
1061
|
</html>`;
|
|
1803
1062
|
}
|
|
1804
|
-
// src/reports/github.ts
|
|
1805
|
-
function renderGithubSummary(stats, options = {}) {
|
|
1806
|
-
const coverageScore = options.coverageScore ?? stats.coverageScore;
|
|
1807
|
-
const driftCount = options.driftCount ?? stats.driftCount;
|
|
1808
|
-
const qualityIssues = options.qualityIssues ?? 0;
|
|
1809
|
-
let output = "";
|
|
1810
|
-
if (stats.health) {
|
|
1811
|
-
const h = stats.health;
|
|
1812
|
-
const status = h.score >= 80 ? "✅" : h.score >= 50 ? "⚠️" : "❌";
|
|
1813
|
-
output += `## ${status} Documentation Health: ${h.score}%
|
|
1814
|
-
|
|
1815
|
-
`;
|
|
1816
|
-
output += `| Metric | Score | Details |
|
|
1817
|
-
|--------|-------|---------|
|
|
1818
|
-
`;
|
|
1819
|
-
output += `| Completeness | ${h.completeness.score}% | ${h.completeness.total - h.completeness.documented} missing docs |
|
|
1820
|
-
`;
|
|
1821
|
-
output += `| Accuracy | ${h.accuracy.score}% | ${h.accuracy.issues} drift issues |
|
|
1822
|
-
`;
|
|
1823
|
-
if (h.examples) {
|
|
1824
|
-
output += `| Examples | ${h.examples.score}% | ${h.examples.passed}/${h.examples.total} passed |
|
|
1825
|
-
`;
|
|
1826
|
-
}
|
|
1827
|
-
output += `
|
|
1828
|
-
`;
|
|
1829
|
-
} else {
|
|
1830
|
-
output += `## Documentation Coverage: ${coverageScore}%
|
|
1831
|
-
|
|
1832
|
-
`;
|
|
1833
|
-
}
|
|
1834
|
-
output += `| Metric | Value |
|
|
1835
|
-
|--------|-------|
|
|
1836
|
-
`;
|
|
1837
|
-
output += `| Coverage Score | ${coverageScore}% |
|
|
1838
|
-
`;
|
|
1839
|
-
output += `| Total Exports | ${stats.totalExports} |
|
|
1840
|
-
`;
|
|
1841
|
-
output += `| Drift Issues | ${driftCount} |
|
|
1842
|
-
`;
|
|
1843
|
-
output += `| Quality Issues | ${qualityIssues} |
|
|
1844
|
-
`;
|
|
1845
|
-
if (!stats.health) {
|
|
1846
|
-
const status = coverageScore >= 80 ? "✅" : coverageScore >= 50 ? "⚠️" : "❌";
|
|
1847
|
-
output += `
|
|
1848
|
-
${status} Coverage ${coverageScore >= 80 ? "passing" : coverageScore >= 50 ? "needs improvement" : "failing"}
|
|
1849
|
-
`;
|
|
1850
|
-
}
|
|
1851
|
-
return output;
|
|
1852
|
-
}
|
|
1853
1063
|
// src/reports/markdown.ts
|
|
1854
1064
|
import { DRIFT_CATEGORY_LABELS } from "@doccov/sdk";
|
|
1855
1065
|
function bar2(pct, width = 10) {
|
|
@@ -1977,321 +1187,8 @@ function renderMarkdown(stats, options = {}) {
|
|
|
1977
1187
|
return lines.join(`
|
|
1978
1188
|
`);
|
|
1979
1189
|
}
|
|
1980
|
-
|
|
1981
|
-
// src/reports/html.ts
|
|
1982
|
-
function escapeHtml2(s) {
|
|
1983
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1984
|
-
}
|
|
1985
|
-
function renderHtml(stats, options = {}) {
|
|
1986
|
-
const md = renderMarkdown(stats, options);
|
|
1987
|
-
return `<!DOCTYPE html>
|
|
1988
|
-
<html lang="en">
|
|
1989
|
-
<head>
|
|
1990
|
-
<meta charset="UTF-8">
|
|
1991
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1992
|
-
<title>DocCov Report: ${escapeHtml2(stats.packageName)}</title>
|
|
1993
|
-
<style>
|
|
1994
|
-
:root { --bg: #0d1117; --fg: #c9d1d9; --border: #30363d; --accent: #58a6ff; }
|
|
1995
|
-
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; }
|
|
1996
|
-
h1, h2 { border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; }
|
|
1997
|
-
table { border-collapse: collapse; width: 100%; margin: 1rem 0; }
|
|
1998
|
-
th, td { border: 1px solid var(--border); padding: 0.5rem 1rem; text-align: left; }
|
|
1999
|
-
th { background: #161b22; }
|
|
2000
|
-
code { background: #161b22; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; }
|
|
2001
|
-
a { color: var(--accent); }
|
|
2002
|
-
</style>
|
|
2003
|
-
</head>
|
|
2004
|
-
<body>
|
|
2005
|
-
<pre style="white-space: pre-wrap; font-family: inherit;">${escapeHtml2(md)}</pre>
|
|
2006
|
-
</body>
|
|
2007
|
-
</html>`;
|
|
2008
|
-
}
|
|
2009
|
-
// src/reports/pr-comment.ts
|
|
2010
|
-
import { getExportDrift as getExportDrift2 } from "@doccov/sdk";
|
|
2011
|
-
function extractReturnType(schema) {
|
|
2012
|
-
if (!schema)
|
|
2013
|
-
return "void";
|
|
2014
|
-
if (typeof schema === "string")
|
|
2015
|
-
return schema;
|
|
2016
|
-
if (typeof schema === "object") {
|
|
2017
|
-
const s = schema;
|
|
2018
|
-
if (typeof s.type === "string")
|
|
2019
|
-
return s.type;
|
|
2020
|
-
if (typeof s.$ref === "string") {
|
|
2021
|
-
const ref = s.$ref;
|
|
2022
|
-
return ref.startsWith("#/types/") ? ref.slice("#/types/".length) : ref;
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
return "unknown";
|
|
2026
|
-
}
|
|
2027
|
-
function renderPRComment(data, opts = {}) {
|
|
2028
|
-
const { diff, headSpec, doccov } = data;
|
|
2029
|
-
const limit = opts.limit ?? 10;
|
|
2030
|
-
const lines = [];
|
|
2031
|
-
const hasStaleRefs = (opts.staleDocsRefs?.length ?? 0) > 0;
|
|
2032
|
-
const hasIssues = diff.newUndocumented.length > 0 || diff.driftIntroduced > 0 || diff.breaking.length > 0 || hasStaleRefs || opts.minCoverage !== undefined && diff.newCoverage < opts.minCoverage;
|
|
2033
|
-
const statusIcon = hasIssues ? diff.coverageDelta < 0 ? "❌" : "⚠️" : "✅";
|
|
2034
|
-
lines.push(`## ${statusIcon} DocCov — Documentation Coverage`);
|
|
2035
|
-
lines.push("");
|
|
2036
|
-
const targetStr = opts.minCoverage !== undefined ? ` (target: ${opts.minCoverage}%) ${diff.newCoverage >= opts.minCoverage ? "✅" : "❌"}` : "";
|
|
2037
|
-
lines.push(`**Patch coverage:** ${diff.newCoverage}%${targetStr}`);
|
|
2038
|
-
if (diff.newUndocumented.length > 0) {
|
|
2039
|
-
lines.push(`**New undocumented exports:** ${diff.newUndocumented.length}`);
|
|
2040
|
-
}
|
|
2041
|
-
if (diff.driftIntroduced > 0) {
|
|
2042
|
-
lines.push(`**Doc drift issues:** ${diff.driftIntroduced}`);
|
|
2043
|
-
}
|
|
2044
|
-
if (opts.staleDocsRefs && opts.staleDocsRefs.length > 0) {
|
|
2045
|
-
lines.push(`**Stale doc references:** ${opts.staleDocsRefs.length}`);
|
|
2046
|
-
}
|
|
2047
|
-
if (opts.semverBump) {
|
|
2048
|
-
const emoji = opts.semverBump.bump === "major" ? "\uD83D\uDD34" : opts.semverBump.bump === "minor" ? "\uD83D\uDFE1" : "\uD83D\uDFE2";
|
|
2049
|
-
lines.push(`**Semver:** ${emoji} ${opts.semverBump.bump.toUpperCase()} (${opts.semverBump.reason})`);
|
|
2050
|
-
}
|
|
2051
|
-
if (diff.newUndocumented.length > 0) {
|
|
2052
|
-
lines.push("");
|
|
2053
|
-
lines.push("### Undocumented exports in this PR");
|
|
2054
|
-
lines.push("");
|
|
2055
|
-
renderUndocumentedExports(lines, diff.newUndocumented, headSpec, opts, limit);
|
|
2056
|
-
}
|
|
2057
|
-
if (diff.driftIntroduced > 0 && headSpec) {
|
|
2058
|
-
lines.push("");
|
|
2059
|
-
lines.push("### Doc drift detected");
|
|
2060
|
-
lines.push("");
|
|
2061
|
-
renderDriftIssues(lines, diff.newUndocumented, headSpec, doccov, opts, limit);
|
|
2062
|
-
}
|
|
2063
|
-
if (opts.staleDocsRefs && opts.staleDocsRefs.length > 0) {
|
|
2064
|
-
lines.push("");
|
|
2065
|
-
lines.push("### \uD83D\uDCDD Stale documentation references");
|
|
2066
|
-
lines.push("");
|
|
2067
|
-
lines.push("These markdown files reference exports that no longer exist:");
|
|
2068
|
-
lines.push("");
|
|
2069
|
-
renderStaleDocsRefs(lines, opts.staleDocsRefs, opts, limit);
|
|
2070
|
-
}
|
|
2071
|
-
const fixGuidance = renderFixGuidance(diff, opts);
|
|
2072
|
-
if (fixGuidance) {
|
|
2073
|
-
lines.push("");
|
|
2074
|
-
lines.push("### How to fix");
|
|
2075
|
-
lines.push("");
|
|
2076
|
-
lines.push(fixGuidance);
|
|
2077
|
-
}
|
|
2078
|
-
lines.push("");
|
|
2079
|
-
lines.push("<details>");
|
|
2080
|
-
lines.push("<summary>View full report</summary>");
|
|
2081
|
-
lines.push("");
|
|
2082
|
-
renderDetailsTable(lines, diff);
|
|
2083
|
-
lines.push("");
|
|
2084
|
-
lines.push("</details>");
|
|
2085
|
-
if (opts.includeBadge !== false && opts.repoUrl) {
|
|
2086
|
-
const repoMatch = opts.repoUrl.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
2087
|
-
if (repoMatch) {
|
|
2088
|
-
const [, owner, repo] = repoMatch;
|
|
2089
|
-
lines.push("");
|
|
2090
|
-
lines.push("---");
|
|
2091
|
-
lines.push("");
|
|
2092
|
-
lines.push(`[](https://doccov.dev/${owner}/${repo})`);
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
return lines.join(`
|
|
2096
|
-
`);
|
|
2097
|
-
}
|
|
2098
|
-
function renderUndocumentedExports(lines, undocumented, headSpec, opts, limit) {
|
|
2099
|
-
if (!headSpec) {
|
|
2100
|
-
for (const name of undocumented.slice(0, limit)) {
|
|
2101
|
-
lines.push(`- \`${name}\``);
|
|
2102
|
-
}
|
|
2103
|
-
if (undocumented.length > limit) {
|
|
2104
|
-
lines.push(`- _...and ${undocumented.length - limit} more_`);
|
|
2105
|
-
}
|
|
2106
|
-
return;
|
|
2107
|
-
}
|
|
2108
|
-
const byFile = new Map;
|
|
2109
|
-
const undocSet = new Set(undocumented);
|
|
2110
|
-
for (const exp of headSpec.exports) {
|
|
2111
|
-
if (undocSet.has(exp.name)) {
|
|
2112
|
-
const file = exp.source?.file ?? "unknown";
|
|
2113
|
-
const list = byFile.get(file) ?? [];
|
|
2114
|
-
list.push(exp);
|
|
2115
|
-
byFile.set(file, list);
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
let count = 0;
|
|
2119
|
-
for (const [file, exports] of byFile) {
|
|
2120
|
-
if (count >= limit)
|
|
2121
|
-
break;
|
|
2122
|
-
const fileLink = buildFileLink(file, opts);
|
|
2123
|
-
lines.push(`\uD83D\uDCC1 ${fileLink}`);
|
|
2124
|
-
for (const exp of exports) {
|
|
2125
|
-
if (count >= limit) {
|
|
2126
|
-
lines.push(`- _...and more_`);
|
|
2127
|
-
break;
|
|
2128
|
-
}
|
|
2129
|
-
const sig = formatExportSignature(exp);
|
|
2130
|
-
lines.push(`- \`${sig}\``);
|
|
2131
|
-
const missing = getMissingSignals(exp);
|
|
2132
|
-
if (missing.length > 0) {
|
|
2133
|
-
lines.push(` - Missing: ${missing.join(", ")}`);
|
|
2134
|
-
}
|
|
2135
|
-
count++;
|
|
2136
|
-
}
|
|
2137
|
-
lines.push("");
|
|
2138
|
-
}
|
|
2139
|
-
if (undocumented.length > count) {
|
|
2140
|
-
lines.push(`_...and ${undocumented.length - count} more undocumented exports_`);
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
|
-
function renderDriftIssues(lines, _undocumented, headSpec, doccov, opts, limit) {
|
|
2144
|
-
const driftIssues = [];
|
|
2145
|
-
for (const exp of headSpec.exports) {
|
|
2146
|
-
const drifts = doccov ? getExportDrift2(exp, doccov) : [];
|
|
2147
|
-
for (const d of drifts) {
|
|
2148
|
-
driftIssues.push({
|
|
2149
|
-
exportName: exp.name,
|
|
2150
|
-
file: exp.source?.file,
|
|
2151
|
-
drift: d
|
|
2152
|
-
});
|
|
2153
|
-
}
|
|
2154
|
-
}
|
|
2155
|
-
if (driftIssues.length === 0) {
|
|
2156
|
-
lines.push("_No specific drift details available_");
|
|
2157
|
-
return;
|
|
2158
|
-
}
|
|
2159
|
-
for (const issue of driftIssues.slice(0, limit)) {
|
|
2160
|
-
const fileRef = issue.file ? `\`${issue.file}\`` : "unknown file";
|
|
2161
|
-
const fileLink = issue.file ? buildFileLink(issue.file, opts) : fileRef;
|
|
2162
|
-
lines.push(`⚠️ ${fileLink}: \`${issue.exportName}\``);
|
|
2163
|
-
lines.push(`- ${issue.drift.issue}`);
|
|
2164
|
-
if (issue.drift.suggestion) {
|
|
2165
|
-
lines.push(`- Fix: ${issue.drift.suggestion}`);
|
|
2166
|
-
}
|
|
2167
|
-
lines.push("");
|
|
2168
|
-
}
|
|
2169
|
-
if (driftIssues.length > limit) {
|
|
2170
|
-
lines.push(`_...and ${driftIssues.length - limit} more drift issues_`);
|
|
2171
|
-
}
|
|
2172
|
-
}
|
|
2173
|
-
function buildFileLink(file, opts) {
|
|
2174
|
-
if (opts.repoUrl && opts.sha) {
|
|
2175
|
-
const url = `${opts.repoUrl}/blob/${opts.sha}/${file}`;
|
|
2176
|
-
return `[\`${file}\`](${url})`;
|
|
2177
|
-
}
|
|
2178
|
-
return `\`${file}\``;
|
|
2179
|
-
}
|
|
2180
|
-
function getExportKeyword(kind) {
|
|
2181
|
-
switch (kind) {
|
|
2182
|
-
case "type":
|
|
2183
|
-
return "type";
|
|
2184
|
-
case "interface":
|
|
2185
|
-
return "interface";
|
|
2186
|
-
case "class":
|
|
2187
|
-
return "class";
|
|
2188
|
-
default:
|
|
2189
|
-
return "function";
|
|
2190
|
-
}
|
|
2191
|
-
}
|
|
2192
|
-
function formatExportSignature(exp) {
|
|
2193
|
-
const prefix2 = `export ${getExportKeyword(exp.kind)}`;
|
|
2194
|
-
if (exp.kind === "function" && exp.signatures?.[0]) {
|
|
2195
|
-
const sig = exp.signatures[0];
|
|
2196
|
-
const params = sig.parameters?.map((p) => `${p.name}${p.required === false ? "?" : ""}`).join(", ") ?? "";
|
|
2197
|
-
const ret = extractReturnType(sig.returns?.schema);
|
|
2198
|
-
return `${prefix2} ${exp.name}(${params}): ${ret}`;
|
|
2199
|
-
}
|
|
2200
|
-
if (exp.kind === "type" || exp.kind === "interface") {
|
|
2201
|
-
return `${prefix2} ${exp.name}`;
|
|
2202
|
-
}
|
|
2203
|
-
if (exp.kind === "class") {
|
|
2204
|
-
return `${prefix2} ${exp.name}`;
|
|
2205
|
-
}
|
|
2206
|
-
return `export ${exp.kind} ${exp.name}`;
|
|
2207
|
-
}
|
|
2208
|
-
function getMissingSignals(exp) {
|
|
2209
|
-
const missing = [];
|
|
2210
|
-
if (!exp.description) {
|
|
2211
|
-
missing.push("description");
|
|
2212
|
-
}
|
|
2213
|
-
if (exp.kind === "function" && exp.signatures?.[0]) {
|
|
2214
|
-
const sig = exp.signatures[0];
|
|
2215
|
-
const undocParams = sig.parameters?.filter((p) => !p.description) ?? [];
|
|
2216
|
-
if (undocParams.length > 0) {
|
|
2217
|
-
missing.push(`\`@param ${undocParams.map((p) => p.name).join(", ")}\``);
|
|
2218
|
-
}
|
|
2219
|
-
if (!sig.returns?.description && extractReturnType(sig.returns?.schema) !== "void") {
|
|
2220
|
-
missing.push("`@returns`");
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
return missing;
|
|
2224
|
-
}
|
|
2225
|
-
function renderStaleDocsRefs(lines, refs, opts, limit) {
|
|
2226
|
-
const byFile = new Map;
|
|
2227
|
-
for (const ref of refs) {
|
|
2228
|
-
const list = byFile.get(ref.file) ?? [];
|
|
2229
|
-
list.push({ line: ref.line, exportName: ref.exportName });
|
|
2230
|
-
byFile.set(ref.file, list);
|
|
2231
|
-
}
|
|
2232
|
-
let count = 0;
|
|
2233
|
-
for (const [file, fileRefs] of byFile) {
|
|
2234
|
-
if (count >= limit)
|
|
2235
|
-
break;
|
|
2236
|
-
const fileLink = buildFileLink(file, opts);
|
|
2237
|
-
lines.push(`\uD83D\uDCC1 ${fileLink}`);
|
|
2238
|
-
for (const ref of fileRefs) {
|
|
2239
|
-
if (count >= limit)
|
|
2240
|
-
break;
|
|
2241
|
-
lines.push(`- Line ${ref.line}: \`${ref.exportName}\` does not exist`);
|
|
2242
|
-
count++;
|
|
2243
|
-
}
|
|
2244
|
-
lines.push("");
|
|
2245
|
-
}
|
|
2246
|
-
if (refs.length > count) {
|
|
2247
|
-
lines.push(`_...and ${refs.length - count} more stale references_`);
|
|
2248
|
-
}
|
|
2249
|
-
}
|
|
2250
|
-
function renderFixGuidance(diff, opts) {
|
|
2251
|
-
const sections = [];
|
|
2252
|
-
if (diff.newUndocumented.length > 0) {
|
|
2253
|
-
sections.push(`**For undocumented exports:**
|
|
2254
|
-
` + "Add JSDoc/TSDoc blocks with description, `@param`, and `@returns` tags.");
|
|
2255
|
-
}
|
|
2256
|
-
if (diff.driftIntroduced > 0) {
|
|
2257
|
-
const fixableNote = opts.fixableDriftCount && opts.fixableDriftCount > 0 ? `
|
|
2258
|
-
|
|
2259
|
-
**Quick fix:** Run \`npx doccov check --fix\` to auto-fix ${opts.fixableDriftCount} issue(s).` : "";
|
|
2260
|
-
sections.push(`**For doc drift:**
|
|
2261
|
-
Update JSDoc to match current code signatures.${fixableNote}`);
|
|
2262
|
-
}
|
|
2263
|
-
if (opts.staleDocsRefs && opts.staleDocsRefs.length > 0) {
|
|
2264
|
-
sections.push(`**For stale docs:**
|
|
2265
|
-
` + "Update or remove code examples that reference deleted exports.");
|
|
2266
|
-
}
|
|
2267
|
-
if (diff.breaking.length > 0) {
|
|
2268
|
-
sections.push(`**For breaking changes:**
|
|
2269
|
-
` + "Consider adding a migration guide or updating changelog.");
|
|
2270
|
-
}
|
|
2271
|
-
if (sections.length === 0) {
|
|
2272
|
-
return "";
|
|
2273
|
-
}
|
|
2274
|
-
sections.push(`
|
|
2275
|
-
Push your changes — DocCov re-checks automatically.`);
|
|
2276
|
-
return sections.join(`
|
|
2277
|
-
|
|
2278
|
-
`);
|
|
2279
|
-
}
|
|
2280
|
-
function renderDetailsTable(lines, diff) {
|
|
2281
|
-
const delta = (n) => n > 0 ? `+${n}` : n === 0 ? "0" : String(n);
|
|
2282
|
-
lines.push("| Metric | Before | After | Delta |");
|
|
2283
|
-
lines.push("|--------|--------|-------|-------|");
|
|
2284
|
-
lines.push(`| Coverage | ${diff.oldCoverage}% | ${diff.newCoverage}% | ${delta(diff.coverageDelta)}% |`);
|
|
2285
|
-
lines.push(`| Breaking changes | - | ${diff.breaking.length} | - |`);
|
|
2286
|
-
lines.push(`| New exports | - | ${diff.nonBreaking.length} | - |`);
|
|
2287
|
-
lines.push(`| Undocumented | - | ${diff.newUndocumented.length} | - |`);
|
|
2288
|
-
if (diff.driftIntroduced > 0 || diff.driftResolved > 0) {
|
|
2289
|
-
const driftDelta = diff.driftIntroduced - diff.driftResolved;
|
|
2290
|
-
lines.push(`| Drift | - | - | ${delta(driftDelta)} |`);
|
|
2291
|
-
}
|
|
2292
|
-
}
|
|
2293
1190
|
// src/reports/stats.ts
|
|
2294
|
-
import { getExportAnalysis, getExportDrift as
|
|
1191
|
+
import { getExportAnalysis, getExportDrift as getExportDrift2, isFixableDrift } from "@doccov/sdk";
|
|
2295
1192
|
import {
|
|
2296
1193
|
DRIFT_CATEGORIES as DRIFT_CATEGORIES2
|
|
2297
1194
|
} from "@doccov/spec";
|
|
@@ -2332,7 +1229,7 @@ function computeStats(openpkg, doccov) {
|
|
|
2332
1229
|
partiallyDocumented++;
|
|
2333
1230
|
else
|
|
2334
1231
|
undocumented++;
|
|
2335
|
-
for (const d of
|
|
1232
|
+
for (const d of getExportDrift2(exp, doccov)) {
|
|
2336
1233
|
const item = {
|
|
2337
1234
|
exportName: exp.name,
|
|
2338
1235
|
type: d.type,
|
|
@@ -2397,7 +1294,7 @@ function computeStats(openpkg, doccov) {
|
|
|
2397
1294
|
import * as fs3 from "node:fs";
|
|
2398
1295
|
import * as path5 from "node:path";
|
|
2399
1296
|
import { DEFAULT_REPORT_DIR, getReportPath } from "@doccov/sdk";
|
|
2400
|
-
import
|
|
1297
|
+
import chalk5 from "chalk";
|
|
2401
1298
|
function writeReport(options) {
|
|
2402
1299
|
const { format, content, outputPath, cwd = process.cwd(), silent = false } = options;
|
|
2403
1300
|
const reportPath = outputPath ? path5.resolve(cwd, outputPath) : path5.resolve(cwd, getReportPath(format));
|
|
@@ -2408,7 +1305,7 @@ function writeReport(options) {
|
|
|
2408
1305
|
fs3.writeFileSync(reportPath, content);
|
|
2409
1306
|
const relativePath = path5.relative(cwd, reportPath);
|
|
2410
1307
|
if (!silent) {
|
|
2411
|
-
console.log(
|
|
1308
|
+
console.log(chalk5.green(`✓ Wrote ${format} report to ${relativePath}`));
|
|
2412
1309
|
}
|
|
2413
1310
|
return { path: reportPath, format, relativePath };
|
|
2414
1311
|
}
|
|
@@ -2467,7 +1364,7 @@ function displayHealthTree(health, log) {
|
|
|
2467
1364
|
}
|
|
2468
1365
|
function displayHealthVerbose(health, log) {
|
|
2469
1366
|
const tree = supportsUnicode() ? { branch: "├─", corner: "└─" } : { branch: "|-", corner: "\\-" };
|
|
2470
|
-
log(colors.bold("COMPLETENESS")
|
|
1367
|
+
log(`${colors.bold("COMPLETENESS")} ${health.completeness.score}%`);
|
|
2471
1368
|
const missingRules = Object.entries(health.completeness.missing);
|
|
2472
1369
|
for (let i = 0;i < missingRules.length; i++) {
|
|
2473
1370
|
const [rule, count] = missingRules[i];
|
|
@@ -2489,7 +1386,7 @@ function displayHealthVerbose(health, log) {
|
|
|
2489
1386
|
}
|
|
2490
1387
|
if (health.examples) {
|
|
2491
1388
|
log("");
|
|
2492
|
-
log(colors.bold("EXAMPLES")
|
|
1389
|
+
log(`${colors.bold("EXAMPLES")} ${health.examples.score}%`);
|
|
2493
1390
|
log(`${tree.branch} ${colors.muted("passed".padEnd(12))} ${health.examples.passed}`);
|
|
2494
1391
|
log(`${tree.corner} ${colors.muted("failed".padEnd(12))} ${health.examples.failed}`);
|
|
2495
1392
|
}
|
|
@@ -2505,7 +1402,6 @@ function displayTextOutput(options, deps) {
|
|
|
2505
1402
|
driftExports,
|
|
2506
1403
|
typecheckErrors,
|
|
2507
1404
|
staleRefs,
|
|
2508
|
-
exampleResult,
|
|
2509
1405
|
specWarnings,
|
|
2510
1406
|
specInfos,
|
|
2511
1407
|
verbose
|
|
@@ -2657,10 +1553,8 @@ function handleNonTextOutput(options, deps) {
|
|
|
2657
1553
|
format,
|
|
2658
1554
|
openpkg,
|
|
2659
1555
|
doccov,
|
|
2660
|
-
coverageScore,
|
|
2661
1556
|
minHealth,
|
|
2662
1557
|
minApiSurface,
|
|
2663
|
-
driftExports,
|
|
2664
1558
|
typecheckErrors,
|
|
2665
1559
|
limit,
|
|
2666
1560
|
stdout,
|
|
@@ -2671,7 +1565,7 @@ function handleNonTextOutput(options, deps) {
|
|
|
2671
1565
|
const stats = computeStats(openpkg, doccov);
|
|
2672
1566
|
const report = generateReportFromDocCov(openpkg, doccov);
|
|
2673
1567
|
const jsonContent = JSON.stringify(report, null, 2);
|
|
2674
|
-
const healthScore = doccov.summary.health?.score ?? coverageScore;
|
|
1568
|
+
const healthScore = doccov.summary.health?.score ?? stats.coverageScore;
|
|
2675
1569
|
let formatContent;
|
|
2676
1570
|
switch (format) {
|
|
2677
1571
|
case "json":
|
|
@@ -2680,15 +1574,6 @@ function handleNonTextOutput(options, deps) {
|
|
|
2680
1574
|
case "markdown":
|
|
2681
1575
|
formatContent = renderMarkdown(stats, { limit });
|
|
2682
1576
|
break;
|
|
2683
|
-
case "html":
|
|
2684
|
-
formatContent = renderHtml(stats, { limit });
|
|
2685
|
-
break;
|
|
2686
|
-
case "github":
|
|
2687
|
-
formatContent = renderGithubSummary(stats, {
|
|
2688
|
-
coverageScore,
|
|
2689
|
-
driftCount: driftExports.length
|
|
2690
|
-
});
|
|
2691
|
-
break;
|
|
2692
1577
|
default:
|
|
2693
1578
|
throw new Error(`Unknown format: ${format}`);
|
|
2694
1579
|
}
|
|
@@ -2838,12 +1723,12 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2838
1723
|
...defaultDependencies,
|
|
2839
1724
|
...dependencies
|
|
2840
1725
|
};
|
|
2841
|
-
program.command("check [entry]").description("Check documentation coverage and output reports").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--min-health <percentage>", "Minimum health score (0-100)", (value) => Number(value)).option("--
|
|
1726
|
+
program.command("check [entry]").description("Check documentation coverage and output reports").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--min-health <percentage>", "Minimum health score (0-100)", (value) => Number(value)).option("--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) => {
|
|
2842
1727
|
const n = parseInt(value, 10);
|
|
2843
1728
|
if (Number.isNaN(n) || n < 1)
|
|
2844
1729
|
throw new Error("--max-type-depth must be a positive integer");
|
|
2845
1730
|
return n;
|
|
2846
|
-
}).option("--no-cache", "Bypass spec cache and force regeneration").option("--visibility <tags>", "Filter by release stage: public,beta,alpha,internal (comma-separated)").option("--
|
|
1731
|
+
}).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 (entry, options) => {
|
|
2847
1732
|
try {
|
|
2848
1733
|
const spin = spinner("Analyzing...");
|
|
2849
1734
|
let validations = parseExamplesFlag(options.examples);
|
|
@@ -2865,27 +1750,11 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2865
1750
|
}
|
|
2866
1751
|
hasExamples = validations.length > 0;
|
|
2867
1752
|
}
|
|
2868
|
-
if (options.minCoverage !== undefined) {
|
|
2869
|
-
log(chalk7.yellow("Warning: --min-coverage is deprecated. Use --min-health instead."));
|
|
2870
|
-
}
|
|
2871
|
-
if (options.maxDrift !== undefined) {
|
|
2872
|
-
log(chalk7.yellow("Warning: --max-drift is deprecated. Use --min-health instead."));
|
|
2873
|
-
}
|
|
2874
|
-
if (config?.check?.minCoverage !== undefined) {
|
|
2875
|
-
log(chalk7.yellow("Warning: config.check.minCoverage is deprecated. Use minHealth."));
|
|
2876
|
-
}
|
|
2877
|
-
if (config?.check?.maxDrift !== undefined) {
|
|
2878
|
-
log(chalk7.yellow("Warning: config.check.maxDrift is deprecated. Use minHealth."));
|
|
2879
|
-
}
|
|
2880
1753
|
const DEFAULT_MIN_HEALTH = 80;
|
|
2881
1754
|
const minHealthRaw = options.minHealth ?? config?.check?.minHealth ?? DEFAULT_MIN_HEALTH;
|
|
2882
1755
|
const minHealth = clampPercentage(minHealthRaw);
|
|
2883
|
-
const minCoverageRaw = options.minCoverage ?? config?.check?.minCoverage ?? DEFAULT_MIN_HEALTH;
|
|
2884
|
-
const minCoverage = clampPercentage(minCoverageRaw);
|
|
2885
|
-
const maxDriftRaw = options.maxDrift ?? config?.check?.maxDrift;
|
|
2886
|
-
const maxDrift = maxDriftRaw !== undefined ? clampPercentage(maxDriftRaw) : undefined;
|
|
2887
1756
|
const apiSurfaceConfig = config?.check?.apiSurface;
|
|
2888
|
-
const minApiSurfaceRaw =
|
|
1757
|
+
const minApiSurfaceRaw = apiSurfaceConfig?.minCompleteness;
|
|
2889
1758
|
const minApiSurface = minApiSurfaceRaw !== undefined ? clampPercentage(minApiSurfaceRaw) : undefined;
|
|
2890
1759
|
const warnBelowApiSurface = apiSurfaceConfig?.warnBelow ? clampPercentage(apiSurfaceConfig.warnBelow) : undefined;
|
|
2891
1760
|
const apiSurfaceIgnore = apiSurfaceConfig?.ignore ?? [];
|
|
@@ -2896,7 +1765,7 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2896
1765
|
};
|
|
2897
1766
|
const resolvedFilters = mergeFilterOptions(config, cliFilters);
|
|
2898
1767
|
if (resolvedFilters.visibility) {
|
|
2899
|
-
log(
|
|
1768
|
+
log(chalk6.dim(`Filtering by visibility: ${resolvedFilters.visibility.join(", ")}`));
|
|
2900
1769
|
}
|
|
2901
1770
|
const resolveExternalTypes = !options.skipResolve;
|
|
2902
1771
|
const analyzer = createDocCov({
|
|
@@ -2924,7 +1793,6 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2924
1793
|
const specInfos = specResult.diagnostics.filter((d) => d.severity === "info");
|
|
2925
1794
|
const isPreview = options.preview;
|
|
2926
1795
|
const shouldFix = options.fix || isPreview;
|
|
2927
|
-
let exampleResult;
|
|
2928
1796
|
let typecheckErrors = [];
|
|
2929
1797
|
let runtimeDrifts = [];
|
|
2930
1798
|
if (hasExamples) {
|
|
@@ -2932,7 +1800,6 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2932
1800
|
validations,
|
|
2933
1801
|
targetDir
|
|
2934
1802
|
});
|
|
2935
|
-
exampleResult = validation.result;
|
|
2936
1803
|
typecheckErrors = validation.typecheckErrors;
|
|
2937
1804
|
runtimeDrifts = validation.runtimeDrifts;
|
|
2938
1805
|
}
|
|
@@ -2972,10 +1839,8 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2972
1839
|
format,
|
|
2973
1840
|
openpkg,
|
|
2974
1841
|
doccov,
|
|
2975
|
-
coverageScore,
|
|
2976
1842
|
minHealth,
|
|
2977
1843
|
minApiSurface,
|
|
2978
|
-
driftExports,
|
|
2979
1844
|
typecheckErrors,
|
|
2980
1845
|
limit: parseInt(options.limit, 10) || 20,
|
|
2981
1846
|
stdout: options.stdout,
|
|
@@ -2997,7 +1862,6 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
2997
1862
|
driftExports,
|
|
2998
1863
|
typecheckErrors,
|
|
2999
1864
|
staleRefs,
|
|
3000
|
-
exampleResult,
|
|
3001
1865
|
specWarnings,
|
|
3002
1866
|
specInfos,
|
|
3003
1867
|
verbose: options.verbose ?? false
|
|
@@ -3006,7 +1870,7 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
3006
1870
|
process.exit(1);
|
|
3007
1871
|
}
|
|
3008
1872
|
} catch (commandError) {
|
|
3009
|
-
error(
|
|
1873
|
+
error(chalk6.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
|
|
3010
1874
|
process.exit(1);
|
|
3011
1875
|
}
|
|
3012
1876
|
});
|
|
@@ -3024,7 +1888,7 @@ import {
|
|
|
3024
1888
|
parseMarkdownFiles as parseMarkdownFiles2
|
|
3025
1889
|
} from "@doccov/sdk";
|
|
3026
1890
|
import { calculateNextVersion, recommendSemverBump } from "@openpkg-ts/spec";
|
|
3027
|
-
import
|
|
1891
|
+
import chalk7 from "chalk";
|
|
3028
1892
|
import { glob as glob2 } from "glob";
|
|
3029
1893
|
var defaultDependencies2 = {
|
|
3030
1894
|
readFileSync: fs4.readFileSync,
|
|
@@ -3050,7 +1914,7 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
3050
1914
|
...defaultDependencies2,
|
|
3051
1915
|
...dependencies
|
|
3052
1916
|
};
|
|
3053
|
-
program.command("diff [base] [head]").description("Compare two OpenPkg specs and detect breaking changes").option("--base <file>", 'Base spec file (the "before" state)').option("--head <file>", 'Head spec file (the "after" state)').option("--format <format>", "Output format: text, json, markdown, html, github
|
|
1917
|
+
program.command("diff [base] [head]").description("Compare two OpenPkg specs and detect breaking changes").option("--base <file>", 'Base spec file (the "before" state)').option("--head <file>", 'Head spec file (the "after" state)').option("--format <format>", "Output format: text, json, markdown, html, github", "text").option("--stdout", "Output to stdout instead of writing to .doccov/").option("-o, --output <file>", "Custom output path").option("--cwd <dir>", "Working directory", process.cwd()).option("--limit <n>", "Max items to show in terminal/reports", "10").option("--min-coverage <n>", "Minimum coverage % for HEAD spec (0-100)").option("--max-drift <n>", "Maximum drift % for HEAD spec (0-100)").option("--strict <preset>", "Fail on conditions: ci, release, quality").option("--docs <glob>", "Glob pattern for markdown docs to check for impact", collect2, []).option("--no-cache", "Bypass cache and force regeneration").option("--recommend-version", "Output recommended semver version bump").action(async (baseArg, headArg, options) => {
|
|
3054
1918
|
try {
|
|
3055
1919
|
const baseFile = options.base ?? baseArg;
|
|
3056
1920
|
const headFile = options.head ?? headArg;
|
|
@@ -3099,9 +1963,9 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
3099
1963
|
}, null, 2));
|
|
3100
1964
|
} else {
|
|
3101
1965
|
log("");
|
|
3102
|
-
log(
|
|
1966
|
+
log(chalk7.bold("Semver Recommendation"));
|
|
3103
1967
|
log(` Current version: ${currentVersion}`);
|
|
3104
|
-
log(` Recommended: ${
|
|
1968
|
+
log(` Recommended: ${chalk7.cyan(nextVersion)} (${chalk7.yellow(recommendation.bump.toUpperCase())})`);
|
|
3105
1969
|
log(` Reason: ${recommendation.reason}`);
|
|
3106
1970
|
}
|
|
3107
1971
|
return;
|
|
@@ -3131,8 +1995,8 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
3131
1995
|
silent: true
|
|
3132
1996
|
});
|
|
3133
1997
|
}
|
|
3134
|
-
const cacheNote = fromCache ?
|
|
3135
|
-
log(
|
|
1998
|
+
const cacheNote = fromCache ? chalk7.cyan(" (cached)") : "";
|
|
1999
|
+
log(chalk7.dim(`Report: ${jsonPath}`) + cacheNote);
|
|
3136
2000
|
}
|
|
3137
2001
|
break;
|
|
3138
2002
|
case "json": {
|
|
@@ -3183,43 +2047,6 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
3183
2047
|
case "github":
|
|
3184
2048
|
printGitHubAnnotations(diff, log);
|
|
3185
2049
|
break;
|
|
3186
|
-
case "pr-comment": {
|
|
3187
|
-
const semverRecommendation = recommendSemverBump(diff);
|
|
3188
|
-
const content = renderPRComment({ diff, baseName, headName, headSpec }, {
|
|
3189
|
-
repoUrl: options.repoUrl,
|
|
3190
|
-
sha: options.sha,
|
|
3191
|
-
minCoverage,
|
|
3192
|
-
limit,
|
|
3193
|
-
semverBump: {
|
|
3194
|
-
bump: semverRecommendation.bump,
|
|
3195
|
-
reason: semverRecommendation.reason
|
|
3196
|
-
}
|
|
3197
|
-
});
|
|
3198
|
-
log(content);
|
|
3199
|
-
break;
|
|
3200
|
-
}
|
|
3201
|
-
case "changelog": {
|
|
3202
|
-
const content = renderChangelog({
|
|
3203
|
-
diff,
|
|
3204
|
-
categorizedBreaking: diff.categorizedBreaking,
|
|
3205
|
-
version: headSpec.meta?.version
|
|
3206
|
-
}, {
|
|
3207
|
-
version: headSpec.meta?.version,
|
|
3208
|
-
compareUrl: options.repoUrl ? `${options.repoUrl}/compare/${baseSpec.meta?.version ?? "v0"}...${headSpec.meta?.version ?? "HEAD"}` : undefined
|
|
3209
|
-
});
|
|
3210
|
-
if (options.stdout) {
|
|
3211
|
-
log(content);
|
|
3212
|
-
} else {
|
|
3213
|
-
const outputPath = options.output ?? getDiffReportPath(baseHash, headHash, "md");
|
|
3214
|
-
writeReport({
|
|
3215
|
-
format: "markdown",
|
|
3216
|
-
content,
|
|
3217
|
-
outputPath: outputPath.replace(/\.(json|html)$/, ".changelog.md"),
|
|
3218
|
-
cwd: options.cwd
|
|
3219
|
-
});
|
|
3220
|
-
}
|
|
3221
|
-
break;
|
|
3222
|
-
}
|
|
3223
2050
|
}
|
|
3224
2051
|
const failures = validateDiff(diff, headSpec, {
|
|
3225
2052
|
minCoverage,
|
|
@@ -3227,18 +2054,18 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
3227
2054
|
checks
|
|
3228
2055
|
});
|
|
3229
2056
|
if (failures.length > 0) {
|
|
3230
|
-
log(
|
|
2057
|
+
log(chalk7.red(`
|
|
3231
2058
|
✗ Check failed`));
|
|
3232
2059
|
for (const f of failures) {
|
|
3233
|
-
log(
|
|
2060
|
+
log(chalk7.red(` - ${f}`));
|
|
3234
2061
|
}
|
|
3235
2062
|
process.exitCode = 1;
|
|
3236
2063
|
} else if (options.strict || minCoverage !== undefined || maxDrift !== undefined) {
|
|
3237
|
-
log(
|
|
2064
|
+
log(chalk7.green(`
|
|
3238
2065
|
✓ All checks passed`));
|
|
3239
2066
|
}
|
|
3240
2067
|
} catch (commandError) {
|
|
3241
|
-
error(
|
|
2068
|
+
error(chalk7.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
|
|
3242
2069
|
process.exitCode = 1;
|
|
3243
2070
|
}
|
|
3244
2071
|
});
|
|
@@ -3265,7 +2092,7 @@ async function generateDiff(baseSpec, headSpec, options, config, log) {
|
|
|
3265
2092
|
if (!docsPatterns || docsPatterns.length === 0) {
|
|
3266
2093
|
if (config?.docs?.include) {
|
|
3267
2094
|
docsPatterns = config.docs.include;
|
|
3268
|
-
log(
|
|
2095
|
+
log(chalk7.gray(`Using docs patterns from config: ${docsPatterns.join(", ")}`));
|
|
3269
2096
|
}
|
|
3270
2097
|
}
|
|
3271
2098
|
if (docsPatterns && docsPatterns.length > 0) {
|
|
@@ -3288,37 +2115,37 @@ function loadSpec(filePath, readFileSync3) {
|
|
|
3288
2115
|
}
|
|
3289
2116
|
function printSummary(diff, baseName, headName, fromCache, log) {
|
|
3290
2117
|
log("");
|
|
3291
|
-
const cacheIndicator = fromCache ?
|
|
3292
|
-
log(
|
|
2118
|
+
const cacheIndicator = fromCache ? chalk7.cyan(" (cached)") : "";
|
|
2119
|
+
log(chalk7.bold(`Comparing: ${baseName} → ${headName}`) + cacheIndicator);
|
|
3293
2120
|
log("─".repeat(40));
|
|
3294
2121
|
log("");
|
|
3295
|
-
const coverageColor = diff.coverageDelta > 0 ?
|
|
2122
|
+
const coverageColor = diff.coverageDelta > 0 ? chalk7.green : diff.coverageDelta < 0 ? chalk7.red : chalk7.gray;
|
|
3296
2123
|
const coverageSign = diff.coverageDelta > 0 ? "+" : "";
|
|
3297
2124
|
log(` Coverage: ${diff.oldCoverage}% → ${diff.newCoverage}% ${coverageColor(`(${coverageSign}${diff.coverageDelta}%)`)}`);
|
|
3298
2125
|
const breakingCount = diff.breaking.length;
|
|
3299
2126
|
const highSeverity = diff.categorizedBreaking?.filter((c) => c.severity === "high").length ?? 0;
|
|
3300
2127
|
if (breakingCount > 0) {
|
|
3301
|
-
const severityNote = highSeverity > 0 ?
|
|
3302
|
-
log(` Breaking: ${
|
|
2128
|
+
const severityNote = highSeverity > 0 ? chalk7.red(` (${highSeverity} high severity)`) : "";
|
|
2129
|
+
log(` Breaking: ${chalk7.red(breakingCount)} changes${severityNote}`);
|
|
3303
2130
|
} else {
|
|
3304
|
-
log(` Breaking: ${
|
|
2131
|
+
log(` Breaking: ${chalk7.green("0")} changes`);
|
|
3305
2132
|
}
|
|
3306
2133
|
const newCount = diff.nonBreaking.length;
|
|
3307
2134
|
const undocCount = diff.newUndocumented.length;
|
|
3308
2135
|
if (newCount > 0) {
|
|
3309
|
-
const undocNote = undocCount > 0 ?
|
|
3310
|
-
log(` New: ${
|
|
2136
|
+
const undocNote = undocCount > 0 ? chalk7.yellow(` (${undocCount} undocumented)`) : "";
|
|
2137
|
+
log(` New: ${chalk7.green(newCount)} exports${undocNote}`);
|
|
3311
2138
|
}
|
|
3312
2139
|
if (diff.driftIntroduced > 0 || diff.driftResolved > 0) {
|
|
3313
2140
|
const parts = [];
|
|
3314
2141
|
if (diff.driftIntroduced > 0)
|
|
3315
|
-
parts.push(
|
|
2142
|
+
parts.push(chalk7.red(`+${diff.driftIntroduced}`));
|
|
3316
2143
|
if (diff.driftResolved > 0)
|
|
3317
|
-
parts.push(
|
|
2144
|
+
parts.push(chalk7.green(`-${diff.driftResolved}`));
|
|
3318
2145
|
log(` Drift: ${parts.join(", ")}`);
|
|
3319
2146
|
}
|
|
3320
2147
|
const recommendation = recommendSemverBump(diff);
|
|
3321
|
-
const bumpColor = recommendation.bump === "major" ?
|
|
2148
|
+
const bumpColor = recommendation.bump === "major" ? chalk7.red : recommendation.bump === "minor" ? chalk7.yellow : chalk7.green;
|
|
3322
2149
|
log(` Semver: ${bumpColor(recommendation.bump.toUpperCase())} (${recommendation.reason})`);
|
|
3323
2150
|
log("");
|
|
3324
2151
|
}
|
|
@@ -3398,57 +2225,10 @@ function printGitHubAnnotations(diff, log) {
|
|
|
3398
2225
|
}
|
|
3399
2226
|
}
|
|
3400
2227
|
|
|
3401
|
-
// src/commands/info.ts
|
|
3402
|
-
import { buildDocCovSpec as buildDocCovSpec2, DocCov as DocCov2, NodeFileSystem as NodeFileSystem2, resolveTarget as resolveTarget2 } from "@doccov/sdk";
|
|
3403
|
-
import chalk9 from "chalk";
|
|
3404
|
-
function registerInfoCommand(program) {
|
|
3405
|
-
program.command("info [entry]").description("Show brief documentation coverage summary").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--skip-resolve", "Skip external type resolution from node_modules").action(async (entry, options) => {
|
|
3406
|
-
const spin = spinner("Analyzing documentation coverage");
|
|
3407
|
-
try {
|
|
3408
|
-
const fileSystem = new NodeFileSystem2(options.cwd);
|
|
3409
|
-
const resolved = await resolveTarget2(fileSystem, {
|
|
3410
|
-
cwd: options.cwd,
|
|
3411
|
-
package: options.package,
|
|
3412
|
-
entry
|
|
3413
|
-
});
|
|
3414
|
-
const { entryFile, targetDir } = resolved;
|
|
3415
|
-
const resolveExternalTypes = !options.skipResolve;
|
|
3416
|
-
const analyzer = new DocCov2({
|
|
3417
|
-
resolveExternalTypes
|
|
3418
|
-
});
|
|
3419
|
-
const specResult = await analyzer.analyzeFileWithDiagnostics(entryFile);
|
|
3420
|
-
if (!specResult) {
|
|
3421
|
-
spin.fail("Failed to analyze");
|
|
3422
|
-
throw new Error("Failed to analyze documentation coverage.");
|
|
3423
|
-
}
|
|
3424
|
-
const openpkg = specResult.spec;
|
|
3425
|
-
const doccov = buildDocCovSpec2({
|
|
3426
|
-
openpkg,
|
|
3427
|
-
openpkgPath: entryFile,
|
|
3428
|
-
packagePath: targetDir,
|
|
3429
|
-
forgottenExports: specResult.forgottenExports
|
|
3430
|
-
});
|
|
3431
|
-
const stats = computeStats(openpkg, doccov);
|
|
3432
|
-
spin.success("Analysis complete");
|
|
3433
|
-
console.log("");
|
|
3434
|
-
console.log(chalk9.bold(`${stats.packageName}@${stats.version}`));
|
|
3435
|
-
console.log("");
|
|
3436
|
-
console.log(` Exports: ${chalk9.bold(stats.totalExports.toString())}`);
|
|
3437
|
-
console.log(` Coverage: ${chalk9.bold(`${stats.coverageScore}%`)}`);
|
|
3438
|
-
console.log(` Drift: ${chalk9.bold(`${stats.driftScore}%`)}`);
|
|
3439
|
-
console.log("");
|
|
3440
|
-
} catch (err) {
|
|
3441
|
-
spin.fail("Analysis failed");
|
|
3442
|
-
console.error(chalk9.red("Error:"), err instanceof Error ? err.message : err);
|
|
3443
|
-
process.exit(1);
|
|
3444
|
-
}
|
|
3445
|
-
});
|
|
3446
|
-
}
|
|
3447
|
-
|
|
3448
2228
|
// src/commands/init.ts
|
|
3449
2229
|
import * as fs5 from "node:fs";
|
|
3450
2230
|
import * as path7 from "node:path";
|
|
3451
|
-
import
|
|
2231
|
+
import chalk8 from "chalk";
|
|
3452
2232
|
var defaultDependencies3 = {
|
|
3453
2233
|
fileExists: fs5.existsSync,
|
|
3454
2234
|
writeFileSync: fs5.writeFileSync,
|
|
@@ -3466,7 +2246,7 @@ function registerInitCommand(program, dependencies = {}) {
|
|
|
3466
2246
|
const cwd = path7.resolve(options.cwd);
|
|
3467
2247
|
const existing = findExistingConfig(cwd, fileExists2);
|
|
3468
2248
|
if (existing) {
|
|
3469
|
-
error(
|
|
2249
|
+
error(chalk8.red(`A DocCov config already exists at ${path7.relative(cwd, existing) || "./doccov.config.*"}.`));
|
|
3470
2250
|
process.exitCode = 1;
|
|
3471
2251
|
return;
|
|
3472
2252
|
}
|
|
@@ -3475,7 +2255,7 @@ function registerInitCommand(program, dependencies = {}) {
|
|
|
3475
2255
|
const fileName = `doccov.config.${targetFormat}`;
|
|
3476
2256
|
const outputPath = path7.join(cwd, fileName);
|
|
3477
2257
|
if (fileExists2(outputPath)) {
|
|
3478
|
-
error(
|
|
2258
|
+
error(chalk8.red(`Cannot create ${fileName}; file already exists.`));
|
|
3479
2259
|
process.exitCode = 1;
|
|
3480
2260
|
return;
|
|
3481
2261
|
}
|
|
@@ -3496,16 +2276,16 @@ function registerInitCommand(program, dependencies = {}) {
|
|
|
3496
2276
|
}
|
|
3497
2277
|
const repoInfo = detectRepoInfo(cwd, fileExists2, readFileSync4);
|
|
3498
2278
|
log("");
|
|
3499
|
-
log(
|
|
2279
|
+
log(chalk8.bold("Add this badge to your README:"));
|
|
3500
2280
|
log("");
|
|
3501
2281
|
if (repoInfo) {
|
|
3502
|
-
log(
|
|
2282
|
+
log(chalk8.cyan(`[](https://doccov.dev/${repoInfo.owner}/${repoInfo.repo})`));
|
|
3503
2283
|
} else {
|
|
3504
|
-
log(
|
|
3505
|
-
log(
|
|
2284
|
+
log(chalk8.cyan(`[](https://doccov.dev/OWNER/REPO)`));
|
|
2285
|
+
log(chalk8.dim(" Replace OWNER/REPO with your GitHub repo"));
|
|
3506
2286
|
}
|
|
3507
2287
|
log("");
|
|
3508
|
-
log(
|
|
2288
|
+
log(chalk8.dim("Run `doccov check` to verify your documentation coverage"));
|
|
3509
2289
|
});
|
|
3510
2290
|
}
|
|
3511
2291
|
var findExistingConfig = (cwd, fileExists2) => {
|
|
@@ -3632,21 +2412,21 @@ var detectRepoInfo = (cwd, fileExists2, readFileSync4) => {
|
|
|
3632
2412
|
import * as fs6 from "node:fs";
|
|
3633
2413
|
import * as path8 from "node:path";
|
|
3634
2414
|
import {
|
|
3635
|
-
buildDocCovSpec as
|
|
3636
|
-
DocCov as
|
|
2415
|
+
buildDocCovSpec as buildDocCovSpec2,
|
|
2416
|
+
DocCov as DocCov2,
|
|
3637
2417
|
detectPackageManager,
|
|
3638
|
-
NodeFileSystem as
|
|
2418
|
+
NodeFileSystem as NodeFileSystem2,
|
|
3639
2419
|
renderApiSurface,
|
|
3640
|
-
resolveTarget as
|
|
2420
|
+
resolveTarget as resolveTarget2
|
|
3641
2421
|
} from "@doccov/sdk";
|
|
3642
2422
|
import { validateDocCovSpec } from "@doccov/spec";
|
|
3643
2423
|
import {
|
|
3644
2424
|
normalize,
|
|
3645
2425
|
validateSpec
|
|
3646
2426
|
} from "@openpkg-ts/spec";
|
|
3647
|
-
import
|
|
2427
|
+
import chalk9 from "chalk";
|
|
3648
2428
|
var defaultDependencies4 = {
|
|
3649
|
-
createDocCov: (options) => new
|
|
2429
|
+
createDocCov: (options) => new DocCov2(options),
|
|
3650
2430
|
writeFileSync: fs6.writeFileSync,
|
|
3651
2431
|
log: console.log,
|
|
3652
2432
|
error: console.error
|
|
@@ -3657,7 +2437,7 @@ function getArrayLength(value) {
|
|
|
3657
2437
|
function formatDiagnosticOutput(prefix2, diagnostic, baseDir) {
|
|
3658
2438
|
const location = diagnostic.location;
|
|
3659
2439
|
const relativePath = location?.file ? path8.relative(baseDir, location.file) || location.file : undefined;
|
|
3660
|
-
const locationText = location && relativePath ?
|
|
2440
|
+
const locationText = location && relativePath ? chalk9.gray(`${relativePath}:${location.line ?? 1}:${location.column ?? 1}`) : null;
|
|
3661
2441
|
const locationPrefix = locationText ? `${locationText} ` : "";
|
|
3662
2442
|
return `${prefix2} ${locationPrefix}${diagnostic.message}`;
|
|
3663
2443
|
}
|
|
@@ -3669,19 +2449,19 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
3669
2449
|
program.command("spec [entry]").description("Generate OpenPkg + DocCov specifications").option("--cwd <dir>", "Working directory", process.cwd()).option("-p, --package <name>", "Target package name (for monorepos)").option("-o, --output <dir>", "Output directory", ".doccov").option("-f, --format <format>", "Output format: json (default) or api-surface", "json").option("--openpkg-only", "Only generate openpkg.json (skip coverage analysis)").option("--include <patterns>", "Include exports matching pattern (comma-separated)").option("--exclude <patterns>", "Exclude exports matching pattern (comma-separated)").option("--visibility <tags>", "Filter by release stage: public,beta,alpha,internal (comma-separated)").option("--skip-resolve", "Skip external type resolution from node_modules").option("--max-type-depth <n>", "Maximum depth for type conversion", "20").option("--runtime", "Enable Standard Schema runtime extraction (richer output for Zod, Valibot, etc.)").option("--no-cache", "Bypass spec cache and force regeneration").option("--show-diagnostics", "Show TypeScript compiler diagnostics").option("--verbose", "Show detailed generation metadata").action(async (entry, options) => {
|
|
3670
2450
|
try {
|
|
3671
2451
|
const spin = spinner("Generating spec...");
|
|
3672
|
-
const fileSystem = new
|
|
3673
|
-
const resolved = await
|
|
2452
|
+
const fileSystem = new NodeFileSystem2(options.cwd);
|
|
2453
|
+
const resolved = await resolveTarget2(fileSystem, {
|
|
3674
2454
|
cwd: options.cwd,
|
|
3675
2455
|
package: options.package,
|
|
3676
2456
|
entry
|
|
3677
2457
|
});
|
|
3678
|
-
const { targetDir, entryFile
|
|
2458
|
+
const { targetDir, entryFile } = resolved;
|
|
3679
2459
|
let config = null;
|
|
3680
2460
|
try {
|
|
3681
2461
|
config = await loadDocCovConfig(targetDir);
|
|
3682
2462
|
} catch (configError) {
|
|
3683
2463
|
spin.fail("Failed to load config");
|
|
3684
|
-
error(
|
|
2464
|
+
error(chalk9.red("Failed to load DocCov config:"), configError instanceof Error ? configError.message : configError);
|
|
3685
2465
|
process.exit(1);
|
|
3686
2466
|
}
|
|
3687
2467
|
const cliFilters = {
|
|
@@ -3714,15 +2494,15 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
3714
2494
|
const validation = validateSpec(normalized);
|
|
3715
2495
|
if (!validation.ok) {
|
|
3716
2496
|
spin.fail("Validation failed");
|
|
3717
|
-
error(
|
|
2497
|
+
error(chalk9.red("Spec failed schema validation"));
|
|
3718
2498
|
for (const err of validation.errors) {
|
|
3719
|
-
error(
|
|
2499
|
+
error(chalk9.red(`schema: ${err.instancePath || "/"} ${err.message}`));
|
|
3720
2500
|
}
|
|
3721
2501
|
process.exit(1);
|
|
3722
2502
|
}
|
|
3723
2503
|
let doccovSpec = null;
|
|
3724
2504
|
if (!options.openpkgOnly) {
|
|
3725
|
-
doccovSpec =
|
|
2505
|
+
doccovSpec = buildDocCovSpec2({
|
|
3726
2506
|
openpkgPath: "openpkg.json",
|
|
3727
2507
|
openpkg: normalized,
|
|
3728
2508
|
packagePath: targetDir,
|
|
@@ -3731,9 +2511,9 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
3731
2511
|
const doccovValidation = validateDocCovSpec(doccovSpec);
|
|
3732
2512
|
if (!doccovValidation.ok) {
|
|
3733
2513
|
spin.fail("DocCov validation failed");
|
|
3734
|
-
error(
|
|
2514
|
+
error(chalk9.red("DocCov spec failed schema validation"));
|
|
3735
2515
|
for (const err of doccovValidation.errors) {
|
|
3736
|
-
error(
|
|
2516
|
+
error(chalk9.red(`doccov: ${err.instancePath || "/"} ${err.message}`));
|
|
3737
2517
|
}
|
|
3738
2518
|
process.exit(1);
|
|
3739
2519
|
}
|
|
@@ -3753,12 +2533,12 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
3753
2533
|
const doccovPath = path8.join(outputDir, "doccov.json");
|
|
3754
2534
|
writeFileSync4(doccovPath, JSON.stringify(doccovSpec, null, 2));
|
|
3755
2535
|
spin.success(`Generated ${options.output}/`);
|
|
3756
|
-
log(
|
|
3757
|
-
log(
|
|
2536
|
+
log(chalk9.gray(` openpkg.json: ${getArrayLength(normalized.exports)} exports`));
|
|
2537
|
+
log(chalk9.gray(` doccov.json: ${doccovSpec.summary.score}% coverage, ${doccovSpec.summary.drift.total} drift issues`));
|
|
3758
2538
|
} else {
|
|
3759
2539
|
spin.success(`Generated ${options.output}/openpkg.json`);
|
|
3760
|
-
log(
|
|
3761
|
-
log(
|
|
2540
|
+
log(chalk9.gray(` ${getArrayLength(normalized.exports)} exports`));
|
|
2541
|
+
log(chalk9.gray(` ${getArrayLength(normalized.types)} types`));
|
|
3762
2542
|
}
|
|
3763
2543
|
}
|
|
3764
2544
|
const isFullGenerationInfo = (gen) => {
|
|
@@ -3770,62 +2550,62 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
3770
2550
|
const pm = await detectPackageManager(fileSystem);
|
|
3771
2551
|
const buildCmd = pm.name === "npm" ? "npm run build" : `${pm.name} run build`;
|
|
3772
2552
|
log("");
|
|
3773
|
-
log(
|
|
3774
|
-
log(
|
|
2553
|
+
log(chalk9.yellow("⚠ Runtime extraction requested but no schemas extracted."));
|
|
2554
|
+
log(chalk9.yellow(` Ensure project is built (${buildCmd}) and dist/ exists.`));
|
|
3775
2555
|
}
|
|
3776
2556
|
if (options.verbose && fullGen) {
|
|
3777
2557
|
log("");
|
|
3778
|
-
log(
|
|
3779
|
-
log(
|
|
3780
|
-
log(
|
|
3781
|
-
log(
|
|
3782
|
-
log(
|
|
3783
|
-
log(
|
|
3784
|
-
log(
|
|
2558
|
+
log(chalk9.bold("Generation Info"));
|
|
2559
|
+
log(chalk9.gray(` Timestamp: ${fullGen.timestamp}`));
|
|
2560
|
+
log(chalk9.gray(` Generator: ${fullGen.generator.name}@${fullGen.generator.version}`));
|
|
2561
|
+
log(chalk9.gray(` Entry point: ${fullGen.analysis.entryPoint}`));
|
|
2562
|
+
log(chalk9.gray(` Detected via: ${fullGen.analysis.entryPointSource}`));
|
|
2563
|
+
log(chalk9.gray(` Declaration only: ${fullGen.analysis.isDeclarationOnly ? "yes" : "no"}`));
|
|
2564
|
+
log(chalk9.gray(` External types: ${fullGen.analysis.resolvedExternalTypes ? "resolved" : "skipped"}`));
|
|
3785
2565
|
if (fullGen.analysis.maxTypeDepth) {
|
|
3786
|
-
log(
|
|
2566
|
+
log(chalk9.gray(` Max type depth: ${fullGen.analysis.maxTypeDepth}`));
|
|
3787
2567
|
}
|
|
3788
2568
|
if (fullGen.analysis.schemaExtraction) {
|
|
3789
2569
|
const se = fullGen.analysis.schemaExtraction;
|
|
3790
|
-
log(
|
|
2570
|
+
log(chalk9.gray(` Schema extraction: ${se.method}`));
|
|
3791
2571
|
if (se.runtimeCount) {
|
|
3792
|
-
log(
|
|
2572
|
+
log(chalk9.gray(` Runtime schemas: ${se.runtimeCount} (${se.vendors?.join(", ")})`));
|
|
3793
2573
|
}
|
|
3794
2574
|
}
|
|
3795
2575
|
log("");
|
|
3796
|
-
log(
|
|
3797
|
-
log(
|
|
2576
|
+
log(chalk9.bold("Environment"));
|
|
2577
|
+
log(chalk9.gray(` node_modules: ${fullGen.environment.hasNodeModules ? "found" : "not found"}`));
|
|
3798
2578
|
if (fullGen.environment.packageManager) {
|
|
3799
|
-
log(
|
|
2579
|
+
log(chalk9.gray(` Package manager: ${fullGen.environment.packageManager}`));
|
|
3800
2580
|
}
|
|
3801
2581
|
if (fullGen.environment.isMonorepo) {
|
|
3802
|
-
log(
|
|
2582
|
+
log(chalk9.gray(` Monorepo: yes`));
|
|
3803
2583
|
}
|
|
3804
2584
|
if (fullGen.environment.targetPackage) {
|
|
3805
|
-
log(
|
|
2585
|
+
log(chalk9.gray(` Target package: ${fullGen.environment.targetPackage}`));
|
|
3806
2586
|
}
|
|
3807
2587
|
if (fullGen.issues.length > 0) {
|
|
3808
2588
|
log("");
|
|
3809
|
-
log(
|
|
2589
|
+
log(chalk9.bold("Issues"));
|
|
3810
2590
|
for (const issue of fullGen.issues) {
|
|
3811
|
-
const prefix2 = issue.severity === "error" ?
|
|
2591
|
+
const prefix2 = issue.severity === "error" ? chalk9.red(">") : issue.severity === "warning" ? chalk9.yellow(">") : chalk9.cyan(">");
|
|
3812
2592
|
log(`${prefix2} [${issue.code}] ${issue.message}`);
|
|
3813
2593
|
if (issue.suggestion) {
|
|
3814
|
-
log(
|
|
2594
|
+
log(chalk9.gray(` ${issue.suggestion}`));
|
|
3815
2595
|
}
|
|
3816
2596
|
}
|
|
3817
2597
|
}
|
|
3818
2598
|
}
|
|
3819
2599
|
if (options.showDiagnostics && result.diagnostics.length > 0) {
|
|
3820
2600
|
log("");
|
|
3821
|
-
log(
|
|
2601
|
+
log(chalk9.bold("Diagnostics"));
|
|
3822
2602
|
for (const diagnostic of result.diagnostics) {
|
|
3823
|
-
const prefix2 = diagnostic.severity === "error" ?
|
|
2603
|
+
const prefix2 = diagnostic.severity === "error" ? chalk9.red(">") : diagnostic.severity === "warning" ? chalk9.yellow(">") : chalk9.cyan(">");
|
|
3824
2604
|
log(formatDiagnosticOutput(prefix2, diagnostic, targetDir));
|
|
3825
2605
|
}
|
|
3826
2606
|
}
|
|
3827
2607
|
} catch (commandError) {
|
|
3828
|
-
error(
|
|
2608
|
+
error(chalk9.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
|
|
3829
2609
|
process.exit(1);
|
|
3830
2610
|
}
|
|
3831
2611
|
});
|
|
@@ -3839,13 +2619,11 @@ import {
|
|
|
3839
2619
|
getExtendedTrend,
|
|
3840
2620
|
getTrend,
|
|
3841
2621
|
loadSnapshots,
|
|
3842
|
-
pruneByTier,
|
|
3843
2622
|
pruneHistory,
|
|
3844
|
-
RETENTION_DAYS,
|
|
3845
2623
|
renderSparkline,
|
|
3846
2624
|
saveSnapshot
|
|
3847
2625
|
} from "@doccov/sdk";
|
|
3848
|
-
import
|
|
2626
|
+
import chalk10 from "chalk";
|
|
3849
2627
|
function formatDate(timestamp) {
|
|
3850
2628
|
const date = new Date(timestamp);
|
|
3851
2629
|
return date.toLocaleDateString("en-US", {
|
|
@@ -3858,19 +2636,19 @@ function formatDate(timestamp) {
|
|
|
3858
2636
|
}
|
|
3859
2637
|
function getColorForScore(score) {
|
|
3860
2638
|
if (score >= 90)
|
|
3861
|
-
return
|
|
2639
|
+
return chalk10.green;
|
|
3862
2640
|
if (score >= 70)
|
|
3863
|
-
return
|
|
2641
|
+
return chalk10.yellow;
|
|
3864
2642
|
if (score >= 50)
|
|
3865
|
-
return
|
|
3866
|
-
return
|
|
2643
|
+
return chalk10.hex("#FFA500");
|
|
2644
|
+
return chalk10.red;
|
|
3867
2645
|
}
|
|
3868
2646
|
function formatSnapshot(snapshot) {
|
|
3869
2647
|
const color = getColorForScore(snapshot.coverageScore);
|
|
3870
2648
|
const date = formatDate(snapshot.timestamp);
|
|
3871
2649
|
const version = snapshot.version ? ` v${snapshot.version}` : "";
|
|
3872
|
-
const commit = snapshot.commit ?
|
|
3873
|
-
return `${
|
|
2650
|
+
const commit = snapshot.commit ? chalk10.gray(` (${snapshot.commit.slice(0, 7)})`) : "";
|
|
2651
|
+
return `${chalk10.gray(date)} ${color(`${snapshot.coverageScore}%`)} ${snapshot.documentedExports}/${snapshot.totalExports} exports${version}${commit}`;
|
|
3874
2652
|
}
|
|
3875
2653
|
function formatWeekDate(timestamp) {
|
|
3876
2654
|
const date = new Date(timestamp);
|
|
@@ -3878,30 +2656,26 @@ function formatWeekDate(timestamp) {
|
|
|
3878
2656
|
}
|
|
3879
2657
|
function formatVelocity(velocity) {
|
|
3880
2658
|
if (velocity > 0)
|
|
3881
|
-
return
|
|
2659
|
+
return chalk10.green(`+${velocity}%/day`);
|
|
3882
2660
|
if (velocity < 0)
|
|
3883
|
-
return
|
|
3884
|
-
return
|
|
2661
|
+
return chalk10.red(`${velocity}%/day`);
|
|
2662
|
+
return chalk10.gray("0%/day");
|
|
3885
2663
|
}
|
|
3886
2664
|
function registerTrendsCommand(program) {
|
|
3887
|
-
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("--
|
|
2665
|
+
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) => {
|
|
3888
2666
|
const cwd = path9.resolve(options.cwd);
|
|
3889
|
-
const tier = options.tier ?? "pro";
|
|
3890
2667
|
if (options.prune) {
|
|
3891
2668
|
const keepCount = parseInt(options.prune, 10);
|
|
3892
2669
|
if (!Number.isNaN(keepCount)) {
|
|
3893
2670
|
const deleted = pruneHistory(cwd, keepCount);
|
|
3894
|
-
console.log(
|
|
3895
|
-
} else {
|
|
3896
|
-
const deleted = pruneByTier(cwd, tier);
|
|
3897
|
-
console.log(chalk12.green(`Pruned ${deleted} snapshots older than ${RETENTION_DAYS[tier]} days`));
|
|
2671
|
+
console.log(chalk10.green(`Pruned ${deleted} old snapshots, kept ${keepCount} most recent`));
|
|
3898
2672
|
}
|
|
3899
2673
|
return;
|
|
3900
2674
|
}
|
|
3901
2675
|
if (options.record) {
|
|
3902
2676
|
const specPath = path9.resolve(cwd, "openpkg.json");
|
|
3903
2677
|
if (!fs7.existsSync(specPath)) {
|
|
3904
|
-
console.error(
|
|
2678
|
+
console.error(chalk10.red("No openpkg.json found. Run `doccov spec` first to generate a spec."));
|
|
3905
2679
|
process.exit(1);
|
|
3906
2680
|
}
|
|
3907
2681
|
const spin = spinner("Recording coverage snapshot");
|
|
@@ -3914,21 +2688,21 @@ function registerTrendsCommand(program) {
|
|
|
3914
2688
|
console.log(formatSnapshot(trend.current));
|
|
3915
2689
|
if (trend.delta !== undefined) {
|
|
3916
2690
|
const deltaStr = formatDelta2(trend.delta);
|
|
3917
|
-
const deltaColor = trend.delta > 0 ?
|
|
3918
|
-
console.log(
|
|
2691
|
+
const deltaColor = trend.delta > 0 ? chalk10.green : trend.delta < 0 ? chalk10.red : chalk10.gray;
|
|
2692
|
+
console.log(chalk10.gray("Change from previous:"), deltaColor(deltaStr));
|
|
3919
2693
|
}
|
|
3920
2694
|
return;
|
|
3921
2695
|
} catch (error) {
|
|
3922
2696
|
spin.fail("Failed to record snapshot");
|
|
3923
|
-
console.error(
|
|
2697
|
+
console.error(chalk10.red("Failed to read openpkg.json:"), error instanceof Error ? error.message : error);
|
|
3924
2698
|
process.exit(1);
|
|
3925
2699
|
}
|
|
3926
2700
|
}
|
|
3927
2701
|
const snapshots = loadSnapshots(cwd);
|
|
3928
2702
|
const limit = parseInt(options.limit ?? "10", 10);
|
|
3929
2703
|
if (snapshots.length === 0) {
|
|
3930
|
-
console.log(
|
|
3931
|
-
console.log(
|
|
2704
|
+
console.log(chalk10.yellow("No coverage history found."));
|
|
2705
|
+
console.log(chalk10.gray("Run `doccov trends --record` to save the current coverage."));
|
|
3932
2706
|
return;
|
|
3933
2707
|
}
|
|
3934
2708
|
if (options.json) {
|
|
@@ -3943,17 +2717,17 @@ function registerTrendsCommand(program) {
|
|
|
3943
2717
|
}
|
|
3944
2718
|
const sparklineData = snapshots.slice(0, 10).map((s) => s.coverageScore).reverse();
|
|
3945
2719
|
const sparkline = renderSparkline(sparklineData);
|
|
3946
|
-
console.log(
|
|
3947
|
-
console.log(
|
|
3948
|
-
console.log(
|
|
2720
|
+
console.log(chalk10.bold("Coverage Trends"));
|
|
2721
|
+
console.log(chalk10.gray(`Package: ${snapshots[0].package}`));
|
|
2722
|
+
console.log(chalk10.gray(`Sparkline: ${sparkline}`));
|
|
3949
2723
|
console.log("");
|
|
3950
2724
|
if (snapshots.length >= 2) {
|
|
3951
2725
|
const oldest = snapshots[snapshots.length - 1];
|
|
3952
2726
|
const newest = snapshots[0];
|
|
3953
2727
|
const overallDelta = newest.coverageScore - oldest.coverageScore;
|
|
3954
2728
|
const deltaStr = formatDelta2(overallDelta);
|
|
3955
|
-
const deltaColor = overallDelta > 0 ?
|
|
3956
|
-
console.log(
|
|
2729
|
+
const deltaColor = overallDelta > 0 ? chalk10.green : overallDelta < 0 ? chalk10.red : chalk10.gray;
|
|
2730
|
+
console.log(chalk10.gray("Overall trend:"), deltaColor(deltaStr), chalk10.gray(`(${snapshots.length} snapshots)`));
|
|
3957
2731
|
console.log("");
|
|
3958
2732
|
}
|
|
3959
2733
|
if (options.extended) {
|
|
@@ -3962,58 +2736,56 @@ function registerTrendsCommand(program) {
|
|
|
3962
2736
|
try {
|
|
3963
2737
|
const specContent = fs7.readFileSync(specPath, "utf-8");
|
|
3964
2738
|
const spec = JSON.parse(specContent);
|
|
3965
|
-
const extended = getExtendedTrend(spec, cwd
|
|
3966
|
-
console.log(
|
|
3967
|
-
console.log(
|
|
2739
|
+
const extended = getExtendedTrend(spec, cwd);
|
|
2740
|
+
console.log(chalk10.bold("Extended Analysis"));
|
|
2741
|
+
console.log(chalk10.gray("90-day retention"));
|
|
3968
2742
|
console.log("");
|
|
3969
2743
|
console.log(" Velocity:");
|
|
3970
2744
|
console.log(` 7-day: ${formatVelocity(extended.velocity7d)}`);
|
|
3971
2745
|
console.log(` 30-day: ${formatVelocity(extended.velocity30d)}`);
|
|
3972
|
-
|
|
3973
|
-
console.log(` 90-day: ${formatVelocity(extended.velocity90d)}`);
|
|
3974
|
-
}
|
|
2746
|
+
console.log(` 90-day: ${formatVelocity(extended.velocity90d)}`);
|
|
3975
2747
|
console.log("");
|
|
3976
|
-
const projColor = extended.projected30d >= extended.trend.current.coverageScore ?
|
|
2748
|
+
const projColor = extended.projected30d >= extended.trend.current.coverageScore ? chalk10.green : chalk10.red;
|
|
3977
2749
|
console.log(` Projected (30d): ${projColor(`${extended.projected30d}%`)}`);
|
|
3978
|
-
console.log(` All-time high: ${
|
|
3979
|
-
console.log(` All-time low: ${
|
|
2750
|
+
console.log(` All-time high: ${chalk10.green(`${extended.allTimeHigh}%`)}`);
|
|
2751
|
+
console.log(` All-time low: ${chalk10.red(`${extended.allTimeLow}%`)}`);
|
|
3980
2752
|
if (extended.dataRange) {
|
|
3981
2753
|
const startDate = formatWeekDate(extended.dataRange.start);
|
|
3982
2754
|
const endDate = formatWeekDate(extended.dataRange.end);
|
|
3983
|
-
console.log(
|
|
2755
|
+
console.log(chalk10.gray(` Data range: ${startDate} - ${endDate}`));
|
|
3984
2756
|
}
|
|
3985
2757
|
console.log("");
|
|
3986
2758
|
if (options.weekly && extended.weeklySummaries.length > 0) {
|
|
3987
|
-
console.log(
|
|
2759
|
+
console.log(chalk10.bold("Weekly Summary"));
|
|
3988
2760
|
const weekLimit = Math.min(extended.weeklySummaries.length, 8);
|
|
3989
2761
|
for (let i = 0;i < weekLimit; i++) {
|
|
3990
2762
|
const week = extended.weeklySummaries[i];
|
|
3991
2763
|
const weekStart = formatWeekDate(week.weekStart);
|
|
3992
2764
|
const weekEnd = formatWeekDate(week.weekEnd);
|
|
3993
|
-
const deltaColor = week.delta > 0 ?
|
|
2765
|
+
const deltaColor = week.delta > 0 ? chalk10.green : week.delta < 0 ? chalk10.red : chalk10.gray;
|
|
3994
2766
|
const deltaStr = week.delta > 0 ? `+${week.delta}%` : `${week.delta}%`;
|
|
3995
2767
|
console.log(` ${weekStart} - ${weekEnd}: ${week.avgCoverage}% avg ${deltaColor(deltaStr)} (${week.snapshotCount} snapshots)`);
|
|
3996
2768
|
}
|
|
3997
2769
|
if (extended.weeklySummaries.length > weekLimit) {
|
|
3998
|
-
console.log(
|
|
2770
|
+
console.log(chalk10.gray(` ... and ${extended.weeklySummaries.length - weekLimit} more weeks`));
|
|
3999
2771
|
}
|
|
4000
2772
|
console.log("");
|
|
4001
2773
|
}
|
|
4002
2774
|
} catch {
|
|
4003
|
-
console.log(
|
|
2775
|
+
console.log(chalk10.yellow("Could not load openpkg.json for extended analysis"));
|
|
4004
2776
|
console.log("");
|
|
4005
2777
|
}
|
|
4006
2778
|
}
|
|
4007
2779
|
}
|
|
4008
|
-
console.log(
|
|
2780
|
+
console.log(chalk10.bold("History"));
|
|
4009
2781
|
const displaySnapshots = snapshots.slice(0, limit);
|
|
4010
2782
|
for (let i = 0;i < displaySnapshots.length; i++) {
|
|
4011
2783
|
const snapshot = displaySnapshots[i];
|
|
4012
|
-
const prefix2 = i === 0 ?
|
|
2784
|
+
const prefix2 = i === 0 ? chalk10.cyan("→") : " ";
|
|
4013
2785
|
console.log(`${prefix2} ${formatSnapshot(snapshot)}`);
|
|
4014
2786
|
}
|
|
4015
2787
|
if (snapshots.length > limit) {
|
|
4016
|
-
console.log(
|
|
2788
|
+
console.log(chalk10.gray(` ... and ${snapshots.length - limit} more`));
|
|
4017
2789
|
}
|
|
4018
2790
|
});
|
|
4019
2791
|
}
|
|
@@ -4025,7 +2797,6 @@ var packageJson = JSON.parse(readFileSync5(path10.join(__dirname2, "../package.j
|
|
|
4025
2797
|
var program = new Command;
|
|
4026
2798
|
program.name("doccov").description("DocCov - Documentation coverage and drift detection for TypeScript").version(packageJson.version);
|
|
4027
2799
|
registerCheckCommand(program);
|
|
4028
|
-
registerInfoCommand(program);
|
|
4029
2800
|
registerSpecCommand(program);
|
|
4030
2801
|
registerDiffCommand(program);
|
|
4031
2802
|
registerInitCommand(program);
|