@doccov/cli 0.25.7 → 0.25.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -20
- package/dist/cli.js +1199 -270
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -151,6 +151,995 @@ import * as path10 from "node:path";
|
|
|
151
151
|
import { fileURLToPath } from "node:url";
|
|
152
152
|
import { Command } from "commander";
|
|
153
153
|
|
|
154
|
+
// ../cli-utils/dist/index.js
|
|
155
|
+
import chalk from "chalk";
|
|
156
|
+
import chalk2 from "chalk";
|
|
157
|
+
var colors = {
|
|
158
|
+
success: chalk.green,
|
|
159
|
+
error: chalk.red,
|
|
160
|
+
warning: chalk.yellow,
|
|
161
|
+
info: chalk.cyan,
|
|
162
|
+
muted: chalk.gray,
|
|
163
|
+
bold: chalk.bold,
|
|
164
|
+
dim: chalk.dim,
|
|
165
|
+
underline: chalk.underline,
|
|
166
|
+
primary: chalk.cyan,
|
|
167
|
+
secondary: chalk.magenta,
|
|
168
|
+
path: chalk.cyan,
|
|
169
|
+
number: chalk.yellow,
|
|
170
|
+
code: chalk.gray
|
|
171
|
+
};
|
|
172
|
+
var symbols = {
|
|
173
|
+
success: "✓",
|
|
174
|
+
error: "✗",
|
|
175
|
+
warning: "⚠",
|
|
176
|
+
info: "ℹ",
|
|
177
|
+
spinner: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
|
178
|
+
bullet: "•",
|
|
179
|
+
arrow: "→",
|
|
180
|
+
arrowRight: "›",
|
|
181
|
+
line: "─",
|
|
182
|
+
corner: "└",
|
|
183
|
+
vertical: "│",
|
|
184
|
+
horizontalLine: "─"
|
|
185
|
+
};
|
|
186
|
+
var asciiSymbols = {
|
|
187
|
+
success: "+",
|
|
188
|
+
error: "x",
|
|
189
|
+
warning: "!",
|
|
190
|
+
info: "i",
|
|
191
|
+
spinner: ["-", "\\", "|", "/"],
|
|
192
|
+
bullet: "*",
|
|
193
|
+
arrow: "->",
|
|
194
|
+
arrowRight: ">",
|
|
195
|
+
line: "-",
|
|
196
|
+
corner: "\\",
|
|
197
|
+
vertical: "|",
|
|
198
|
+
horizontalLine: "-"
|
|
199
|
+
};
|
|
200
|
+
function getSymbols(unicodeSupport = true) {
|
|
201
|
+
return unicodeSupport ? symbols : asciiSymbols;
|
|
202
|
+
}
|
|
203
|
+
var prefix = {
|
|
204
|
+
success: colors.success(symbols.success),
|
|
205
|
+
error: colors.error(symbols.error),
|
|
206
|
+
warning: colors.warning(symbols.warning),
|
|
207
|
+
info: colors.info(symbols.info)
|
|
208
|
+
};
|
|
209
|
+
function isTTY() {
|
|
210
|
+
return Boolean(process.stdout.isTTY);
|
|
211
|
+
}
|
|
212
|
+
function isCI() {
|
|
213
|
+
return Boolean(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI || process.env.TRAVIS || process.env.JENKINS_URL || process.env.BUILDKITE || process.env.TEAMCITY_VERSION || process.env.TF_BUILD || process.env.CODEBUILD_BUILD_ID || process.env.BITBUCKET_BUILD_NUMBER);
|
|
214
|
+
}
|
|
215
|
+
function isInteractive() {
|
|
216
|
+
return isTTY() && !isCI();
|
|
217
|
+
}
|
|
218
|
+
function supportsUnicode() {
|
|
219
|
+
if (process.platform === "win32") {
|
|
220
|
+
return Boolean(process.env.WT_SESSION) || process.env.TERM_PROGRAM === "vscode";
|
|
221
|
+
}
|
|
222
|
+
return process.env.TERM !== "linux";
|
|
223
|
+
}
|
|
224
|
+
var MIN_TERMINAL_WIDTH = 40;
|
|
225
|
+
var DEFAULT_TERMINAL_WIDTH = 80;
|
|
226
|
+
function getTerminalWidth() {
|
|
227
|
+
const width = process.stdout.columns || DEFAULT_TERMINAL_WIDTH;
|
|
228
|
+
return Math.max(width, MIN_TERMINAL_WIDTH);
|
|
229
|
+
}
|
|
230
|
+
function formatDuration(ms) {
|
|
231
|
+
if (ms < 1000) {
|
|
232
|
+
return `${ms}ms`;
|
|
233
|
+
}
|
|
234
|
+
const seconds = ms / 1000;
|
|
235
|
+
if (seconds < 60) {
|
|
236
|
+
return `${seconds.toFixed(1)}s`;
|
|
237
|
+
}
|
|
238
|
+
const minutes = Math.floor(seconds / 60);
|
|
239
|
+
const remainingSeconds = Math.floor(seconds % 60);
|
|
240
|
+
return `${minutes}m ${remainingSeconds}s`;
|
|
241
|
+
}
|
|
242
|
+
var cursor = {
|
|
243
|
+
hide: "\x1B[?25l",
|
|
244
|
+
show: "\x1B[?25h",
|
|
245
|
+
up: (n = 1) => `\x1B[${n}A`,
|
|
246
|
+
down: (n = 1) => `\x1B[${n}B`,
|
|
247
|
+
forward: (n = 1) => `\x1B[${n}C`,
|
|
248
|
+
back: (n = 1) => `\x1B[${n}D`,
|
|
249
|
+
left: "\x1B[G",
|
|
250
|
+
clearLine: "\x1B[2K",
|
|
251
|
+
clearDown: "\x1B[J",
|
|
252
|
+
save: "\x1B[s",
|
|
253
|
+
restore: "\x1B[u"
|
|
254
|
+
};
|
|
255
|
+
function clearLine() {
|
|
256
|
+
if (isTTY()) {
|
|
257
|
+
process.stdout.write(cursor.clearLine + cursor.left);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function moveCursorUp(lines = 1) {
|
|
261
|
+
if (isTTY()) {
|
|
262
|
+
process.stdout.write(cursor.up(lines));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function hideCursor() {
|
|
266
|
+
if (isTTY()) {
|
|
267
|
+
process.stdout.write(cursor.hide);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function showCursor() {
|
|
271
|
+
if (isTTY()) {
|
|
272
|
+
process.stdout.write(cursor.show);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function truncate(text, maxLength) {
|
|
276
|
+
if (text.length <= maxLength)
|
|
277
|
+
return text;
|
|
278
|
+
return `${text.slice(0, maxLength - 1)}…`;
|
|
279
|
+
}
|
|
280
|
+
var ANSI_REGEX = /\x1B\[[0-9;]*[a-zA-Z]/g;
|
|
281
|
+
function stripAnsi(text) {
|
|
282
|
+
return text.replace(ANSI_REGEX, "");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
class MultiProgress {
|
|
286
|
+
bars = new Map;
|
|
287
|
+
barOrder = [];
|
|
288
|
+
options;
|
|
289
|
+
spinnerFrames;
|
|
290
|
+
spinnerIndex = 0;
|
|
291
|
+
timer = null;
|
|
292
|
+
lastRenderedLines = 0;
|
|
293
|
+
symbols = getSymbols(supportsUnicode());
|
|
294
|
+
sigintHandler = null;
|
|
295
|
+
filledChar;
|
|
296
|
+
emptyChar;
|
|
297
|
+
constructor(options = {}) {
|
|
298
|
+
this.options = {
|
|
299
|
+
barWidth: options.barWidth,
|
|
300
|
+
showPercent: options.showPercent ?? true,
|
|
301
|
+
showCount: options.showCount ?? true,
|
|
302
|
+
spinnerInterval: options.spinnerInterval ?? 80
|
|
303
|
+
};
|
|
304
|
+
this.spinnerFrames = supportsUnicode() ? ["◐", "◓", "◑", "◒"] : ["-", "\\", "|", "/"];
|
|
305
|
+
const unicode = supportsUnicode();
|
|
306
|
+
this.filledChar = unicode ? "█" : "#";
|
|
307
|
+
this.emptyChar = unicode ? "░" : "-";
|
|
308
|
+
}
|
|
309
|
+
start() {
|
|
310
|
+
if (!isInteractive())
|
|
311
|
+
return this;
|
|
312
|
+
hideCursor();
|
|
313
|
+
this.setupSignalHandler();
|
|
314
|
+
this.timer = setInterval(() => {
|
|
315
|
+
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;
|
|
316
|
+
if (this.bars.size > 0 && [...this.bars.values()].some((b) => b.status === "active")) {
|
|
317
|
+
this.render();
|
|
318
|
+
}
|
|
319
|
+
}, this.options.spinnerInterval);
|
|
320
|
+
return this;
|
|
321
|
+
}
|
|
322
|
+
add(config) {
|
|
323
|
+
const state = {
|
|
324
|
+
id: config.id,
|
|
325
|
+
label: config.label,
|
|
326
|
+
total: config.total ?? 100,
|
|
327
|
+
current: config.current ?? 0,
|
|
328
|
+
status: "active",
|
|
329
|
+
startTime: Date.now()
|
|
330
|
+
};
|
|
331
|
+
this.bars.set(config.id, state);
|
|
332
|
+
if (!this.barOrder.includes(config.id)) {
|
|
333
|
+
this.barOrder.push(config.id);
|
|
334
|
+
}
|
|
335
|
+
if (!isInteractive()) {
|
|
336
|
+
console.log(`${this.symbols.bullet} ${config.label}`);
|
|
337
|
+
} else {
|
|
338
|
+
this.render();
|
|
339
|
+
}
|
|
340
|
+
return this;
|
|
341
|
+
}
|
|
342
|
+
update(id, current, label) {
|
|
343
|
+
const bar = this.bars.get(id);
|
|
344
|
+
if (!bar)
|
|
345
|
+
return this;
|
|
346
|
+
bar.current = Math.min(current, bar.total);
|
|
347
|
+
if (label !== undefined)
|
|
348
|
+
bar.label = label;
|
|
349
|
+
if (isInteractive()) {
|
|
350
|
+
this.render();
|
|
351
|
+
}
|
|
352
|
+
return this;
|
|
353
|
+
}
|
|
354
|
+
increment(id, amount = 1) {
|
|
355
|
+
const bar = this.bars.get(id);
|
|
356
|
+
if (!bar)
|
|
357
|
+
return this;
|
|
358
|
+
return this.update(id, bar.current + amount);
|
|
359
|
+
}
|
|
360
|
+
complete(id, label) {
|
|
361
|
+
const bar = this.bars.get(id);
|
|
362
|
+
if (!bar)
|
|
363
|
+
return this;
|
|
364
|
+
bar.status = "completed";
|
|
365
|
+
bar.current = bar.total;
|
|
366
|
+
if (label !== undefined)
|
|
367
|
+
bar.label = label;
|
|
368
|
+
if (!isInteractive()) {
|
|
369
|
+
console.log(`${colors.success(this.symbols.success)} ${bar.label}`);
|
|
370
|
+
} else {
|
|
371
|
+
this.render();
|
|
372
|
+
}
|
|
373
|
+
return this;
|
|
374
|
+
}
|
|
375
|
+
fail(id, label) {
|
|
376
|
+
const bar = this.bars.get(id);
|
|
377
|
+
if (!bar)
|
|
378
|
+
return this;
|
|
379
|
+
bar.status = "failed";
|
|
380
|
+
if (label !== undefined)
|
|
381
|
+
bar.label = label;
|
|
382
|
+
if (!isInteractive()) {
|
|
383
|
+
console.log(`${colors.error(this.symbols.error)} ${bar.label}`);
|
|
384
|
+
} else {
|
|
385
|
+
this.render();
|
|
386
|
+
}
|
|
387
|
+
return this;
|
|
388
|
+
}
|
|
389
|
+
remove(id) {
|
|
390
|
+
this.bars.delete(id);
|
|
391
|
+
this.barOrder = this.barOrder.filter((i) => i !== id);
|
|
392
|
+
if (isInteractive()) {
|
|
393
|
+
this.render();
|
|
394
|
+
}
|
|
395
|
+
return this;
|
|
396
|
+
}
|
|
397
|
+
get(id) {
|
|
398
|
+
return this.bars.get(id);
|
|
399
|
+
}
|
|
400
|
+
get allDone() {
|
|
401
|
+
if (this.bars.size === 0)
|
|
402
|
+
return true;
|
|
403
|
+
return [...this.bars.values()].every((b) => b.status !== "active");
|
|
404
|
+
}
|
|
405
|
+
stop() {
|
|
406
|
+
if (this.timer) {
|
|
407
|
+
clearInterval(this.timer);
|
|
408
|
+
this.timer = null;
|
|
409
|
+
}
|
|
410
|
+
this.cleanup();
|
|
411
|
+
return this;
|
|
412
|
+
}
|
|
413
|
+
render() {
|
|
414
|
+
if (!isTTY())
|
|
415
|
+
return;
|
|
416
|
+
this.clearOutput();
|
|
417
|
+
const width = getTerminalWidth();
|
|
418
|
+
const lines = [];
|
|
419
|
+
for (const id of this.barOrder) {
|
|
420
|
+
const bar = this.bars.get(id);
|
|
421
|
+
if (!bar)
|
|
422
|
+
continue;
|
|
423
|
+
const line = this.renderBar(bar, width);
|
|
424
|
+
lines.push(line);
|
|
425
|
+
}
|
|
426
|
+
if (lines.length > 0) {
|
|
427
|
+
process.stdout.write(lines.join(`
|
|
428
|
+
`));
|
|
429
|
+
this.lastRenderedLines = lines.length;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
renderBar(bar, termWidth) {
|
|
433
|
+
const parts = [];
|
|
434
|
+
let symbol;
|
|
435
|
+
switch (bar.status) {
|
|
436
|
+
case "completed":
|
|
437
|
+
symbol = colors.success(this.symbols.success);
|
|
438
|
+
break;
|
|
439
|
+
case "failed":
|
|
440
|
+
symbol = colors.error(this.symbols.error);
|
|
441
|
+
break;
|
|
442
|
+
default:
|
|
443
|
+
symbol = colors.primary(this.spinnerFrames[this.spinnerIndex]);
|
|
444
|
+
}
|
|
445
|
+
parts.push(symbol);
|
|
446
|
+
parts.push(bar.label);
|
|
447
|
+
const suffixParts = [];
|
|
448
|
+
if (this.options.showPercent) {
|
|
449
|
+
const pct = bar.total === 0 ? 0 : Math.round(bar.current / bar.total * 100);
|
|
450
|
+
suffixParts.push(`${pct}%`);
|
|
451
|
+
}
|
|
452
|
+
if (this.options.showCount) {
|
|
453
|
+
suffixParts.push(`${bar.current}/${bar.total}`);
|
|
454
|
+
}
|
|
455
|
+
const suffix = suffixParts.length > 0 ? ` ${suffixParts.join(" ")}` : "";
|
|
456
|
+
const labelLen = stripAnsi(parts.join(" ")).length + 1;
|
|
457
|
+
const bracketLen = 2;
|
|
458
|
+
const suffixLen = stripAnsi(suffix).length;
|
|
459
|
+
const minBarWidth = 10;
|
|
460
|
+
const availableWidth = termWidth - labelLen - bracketLen - suffixLen - 1;
|
|
461
|
+
const barWidth = this.options.barWidth ?? Math.max(minBarWidth, Math.min(30, availableWidth));
|
|
462
|
+
const filledWidth = Math.round(bar.current / bar.total * barWidth);
|
|
463
|
+
const emptyWidth = barWidth - filledWidth;
|
|
464
|
+
const barViz = `[${this.filledChar.repeat(filledWidth)}${this.emptyChar.repeat(emptyWidth)}]`;
|
|
465
|
+
parts.push(barViz);
|
|
466
|
+
return truncate(parts.join(" ") + suffix, termWidth);
|
|
467
|
+
}
|
|
468
|
+
clearOutput() {
|
|
469
|
+
if (!isTTY() || this.lastRenderedLines === 0)
|
|
470
|
+
return;
|
|
471
|
+
for (let i = 0;i < this.lastRenderedLines; i++) {
|
|
472
|
+
if (i > 0)
|
|
473
|
+
process.stdout.write(cursor.up(1));
|
|
474
|
+
clearLine();
|
|
475
|
+
}
|
|
476
|
+
this.lastRenderedLines = 0;
|
|
477
|
+
}
|
|
478
|
+
setupSignalHandler() {
|
|
479
|
+
this.sigintHandler = () => {
|
|
480
|
+
this.cleanup();
|
|
481
|
+
process.exit(130);
|
|
482
|
+
};
|
|
483
|
+
process.on("SIGINT", this.sigintHandler);
|
|
484
|
+
}
|
|
485
|
+
cleanup() {
|
|
486
|
+
if (this.sigintHandler) {
|
|
487
|
+
process.removeListener("SIGINT", this.sigintHandler);
|
|
488
|
+
this.sigintHandler = null;
|
|
489
|
+
}
|
|
490
|
+
showCursor();
|
|
491
|
+
if (isTTY() && this.lastRenderedLines > 0) {
|
|
492
|
+
process.stdout.write(`
|
|
493
|
+
`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
class ProgressBar {
|
|
498
|
+
total;
|
|
499
|
+
current;
|
|
500
|
+
label;
|
|
501
|
+
width;
|
|
502
|
+
showPercent;
|
|
503
|
+
showCount;
|
|
504
|
+
showETA;
|
|
505
|
+
filledChar;
|
|
506
|
+
emptyChar;
|
|
507
|
+
startTime = null;
|
|
508
|
+
lastRender = "";
|
|
509
|
+
symbols = getSymbols(supportsUnicode());
|
|
510
|
+
sigintHandler = null;
|
|
511
|
+
isComplete = false;
|
|
512
|
+
constructor(options = {}) {
|
|
513
|
+
this.total = options.total ?? 100;
|
|
514
|
+
this.current = options.current ?? 0;
|
|
515
|
+
this.label = options.label ?? "";
|
|
516
|
+
this.width = options.width;
|
|
517
|
+
this.showPercent = options.showPercent ?? true;
|
|
518
|
+
this.showCount = options.showCount ?? true;
|
|
519
|
+
this.showETA = options.showETA ?? true;
|
|
520
|
+
const unicode = supportsUnicode();
|
|
521
|
+
this.filledChar = options.chars?.filled ?? (unicode ? "█" : "#");
|
|
522
|
+
this.emptyChar = options.chars?.empty ?? (unicode ? "░" : "-");
|
|
523
|
+
}
|
|
524
|
+
start(label) {
|
|
525
|
+
if (label !== undefined)
|
|
526
|
+
this.label = label;
|
|
527
|
+
this.startTime = Date.now();
|
|
528
|
+
this.current = 0;
|
|
529
|
+
this.isComplete = false;
|
|
530
|
+
if (!isInteractive()) {
|
|
531
|
+
console.log(`${this.symbols.bullet} ${this.label}`);
|
|
532
|
+
return this;
|
|
533
|
+
}
|
|
534
|
+
hideCursor();
|
|
535
|
+
this.setupSignalHandler();
|
|
536
|
+
this.render();
|
|
537
|
+
return this;
|
|
538
|
+
}
|
|
539
|
+
update(current) {
|
|
540
|
+
this.current = Math.min(current, this.total);
|
|
541
|
+
if (isInteractive()) {
|
|
542
|
+
this.render();
|
|
543
|
+
}
|
|
544
|
+
return this;
|
|
545
|
+
}
|
|
546
|
+
increment(amount = 1) {
|
|
547
|
+
return this.update(this.current + amount);
|
|
548
|
+
}
|
|
549
|
+
setLabel(label) {
|
|
550
|
+
this.label = label;
|
|
551
|
+
if (isInteractive()) {
|
|
552
|
+
this.render();
|
|
553
|
+
}
|
|
554
|
+
return this;
|
|
555
|
+
}
|
|
556
|
+
setTotal(total) {
|
|
557
|
+
this.total = total;
|
|
558
|
+
if (isInteractive()) {
|
|
559
|
+
this.render();
|
|
560
|
+
}
|
|
561
|
+
return this;
|
|
562
|
+
}
|
|
563
|
+
complete(label) {
|
|
564
|
+
if (label !== undefined)
|
|
565
|
+
this.label = label;
|
|
566
|
+
this.current = this.total;
|
|
567
|
+
this.isComplete = true;
|
|
568
|
+
if (!isInteractive()) {
|
|
569
|
+
console.log(`${colors.success(this.symbols.success)} ${this.label}`);
|
|
570
|
+
} else {
|
|
571
|
+
clearLine();
|
|
572
|
+
process.stdout.write(`${colors.success(this.symbols.success)} ${this.label}
|
|
573
|
+
`);
|
|
574
|
+
}
|
|
575
|
+
this.cleanup();
|
|
576
|
+
return this;
|
|
577
|
+
}
|
|
578
|
+
fail(label) {
|
|
579
|
+
if (label !== undefined)
|
|
580
|
+
this.label = label;
|
|
581
|
+
this.isComplete = true;
|
|
582
|
+
if (!isInteractive()) {
|
|
583
|
+
console.log(`${colors.error(this.symbols.error)} ${this.label}`);
|
|
584
|
+
} else {
|
|
585
|
+
clearLine();
|
|
586
|
+
process.stdout.write(`${colors.error(this.symbols.error)} ${this.label}
|
|
587
|
+
`);
|
|
588
|
+
}
|
|
589
|
+
this.cleanup();
|
|
590
|
+
return this;
|
|
591
|
+
}
|
|
592
|
+
get percentage() {
|
|
593
|
+
return this.total === 0 ? 0 : Math.round(this.current / this.total * 100);
|
|
594
|
+
}
|
|
595
|
+
get isDone() {
|
|
596
|
+
return this.isComplete || this.current >= this.total;
|
|
597
|
+
}
|
|
598
|
+
render() {
|
|
599
|
+
if (!isTTY())
|
|
600
|
+
return;
|
|
601
|
+
const termWidth = getTerminalWidth();
|
|
602
|
+
const parts = [];
|
|
603
|
+
if (this.label) {
|
|
604
|
+
parts.push(this.label);
|
|
605
|
+
}
|
|
606
|
+
const suffixParts = [];
|
|
607
|
+
if (this.showPercent) {
|
|
608
|
+
suffixParts.push(`${this.percentage}%`);
|
|
609
|
+
}
|
|
610
|
+
if (this.showCount) {
|
|
611
|
+
suffixParts.push(`${this.current}/${this.total}`);
|
|
612
|
+
}
|
|
613
|
+
if (this.showETA && this.startTime) {
|
|
614
|
+
const eta = this.calculateETA();
|
|
615
|
+
if (eta)
|
|
616
|
+
suffixParts.push(eta);
|
|
617
|
+
}
|
|
618
|
+
const suffix = suffixParts.length > 0 ? ` ${suffixParts.join(" ")}` : "";
|
|
619
|
+
const labelLen = this.label ? stripAnsi(this.label).length + 1 : 0;
|
|
620
|
+
const bracketLen = 2;
|
|
621
|
+
const suffixLen = stripAnsi(suffix).length;
|
|
622
|
+
const minBarWidth = 10;
|
|
623
|
+
const availableWidth = termWidth - labelLen - bracketLen - suffixLen - 1;
|
|
624
|
+
const barWidth = this.width ?? Math.max(minBarWidth, Math.min(40, availableWidth));
|
|
625
|
+
const filledWidth = Math.round(this.current / this.total * barWidth);
|
|
626
|
+
const emptyWidth = barWidth - filledWidth;
|
|
627
|
+
const bar = `[${this.filledChar.repeat(filledWidth)}${this.emptyChar.repeat(emptyWidth)}]`;
|
|
628
|
+
parts.push(bar);
|
|
629
|
+
const line = truncate(parts.join(" ") + suffix, termWidth);
|
|
630
|
+
if (line !== this.lastRender) {
|
|
631
|
+
clearLine();
|
|
632
|
+
process.stdout.write(line);
|
|
633
|
+
this.lastRender = line;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
calculateETA() {
|
|
637
|
+
if (!this.startTime || this.current === 0)
|
|
638
|
+
return null;
|
|
639
|
+
const elapsed = Date.now() - this.startTime;
|
|
640
|
+
if (elapsed < 1000)
|
|
641
|
+
return null;
|
|
642
|
+
const rate = this.current / elapsed;
|
|
643
|
+
const remaining = this.total - this.current;
|
|
644
|
+
const etaMs = remaining / rate;
|
|
645
|
+
return `ETA ${formatDuration(etaMs)}`;
|
|
646
|
+
}
|
|
647
|
+
setupSignalHandler() {
|
|
648
|
+
this.sigintHandler = () => {
|
|
649
|
+
this.cleanup();
|
|
650
|
+
process.exit(130);
|
|
651
|
+
};
|
|
652
|
+
process.on("SIGINT", this.sigintHandler);
|
|
653
|
+
}
|
|
654
|
+
cleanup() {
|
|
655
|
+
if (this.sigintHandler) {
|
|
656
|
+
process.removeListener("SIGINT", this.sigintHandler);
|
|
657
|
+
this.sigintHandler = null;
|
|
658
|
+
}
|
|
659
|
+
showCursor();
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
var spinnerColors = {
|
|
663
|
+
cyan: chalk2.cyan,
|
|
664
|
+
yellow: chalk2.yellow,
|
|
665
|
+
green: chalk2.green,
|
|
666
|
+
red: chalk2.red,
|
|
667
|
+
magenta: chalk2.magenta,
|
|
668
|
+
blue: chalk2.blue,
|
|
669
|
+
white: chalk2.white
|
|
670
|
+
};
|
|
671
|
+
var FRAME_SETS = {
|
|
672
|
+
dots: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
|
673
|
+
circle: ["◐", "◓", "◑", "◒"]
|
|
674
|
+
};
|
|
675
|
+
var ASCII_FRAME_SET = ["-", "\\", "|", "/"];
|
|
676
|
+
|
|
677
|
+
class Spinner {
|
|
678
|
+
label;
|
|
679
|
+
detail;
|
|
680
|
+
frames;
|
|
681
|
+
interval;
|
|
682
|
+
colorFn;
|
|
683
|
+
frameIndex = 0;
|
|
684
|
+
timer = null;
|
|
685
|
+
state = "stopped";
|
|
686
|
+
symbols = getSymbols(supportsUnicode());
|
|
687
|
+
lastRenderedLines = 0;
|
|
688
|
+
sigintHandler = null;
|
|
689
|
+
constructor(options = {}) {
|
|
690
|
+
this.label = options.label ?? "";
|
|
691
|
+
this.detail = options.detail;
|
|
692
|
+
this.interval = options.interval ?? 80;
|
|
693
|
+
this.colorFn = spinnerColors[options.color ?? "cyan"];
|
|
694
|
+
const style = options.style ?? "circle";
|
|
695
|
+
this.frames = supportsUnicode() ? FRAME_SETS[style] : ASCII_FRAME_SET;
|
|
696
|
+
}
|
|
697
|
+
start(label) {
|
|
698
|
+
if (label !== undefined)
|
|
699
|
+
this.label = label;
|
|
700
|
+
if (this.state === "spinning")
|
|
701
|
+
return this;
|
|
702
|
+
this.state = "spinning";
|
|
703
|
+
this.frameIndex = 0;
|
|
704
|
+
this.lastRenderedLines = 0;
|
|
705
|
+
if (!isInteractive()) {
|
|
706
|
+
console.log(`${this.symbols.bullet} ${this.label}`);
|
|
707
|
+
return this;
|
|
708
|
+
}
|
|
709
|
+
hideCursor();
|
|
710
|
+
this.setupSignalHandler();
|
|
711
|
+
this.render();
|
|
712
|
+
this.timer = setInterval(() => {
|
|
713
|
+
this.frameIndex = (this.frameIndex + 1) % this.frames.length;
|
|
714
|
+
this.render();
|
|
715
|
+
}, this.interval);
|
|
716
|
+
return this;
|
|
717
|
+
}
|
|
718
|
+
stop() {
|
|
719
|
+
if (this.timer) {
|
|
720
|
+
clearInterval(this.timer);
|
|
721
|
+
this.timer = null;
|
|
722
|
+
}
|
|
723
|
+
this.state = "stopped";
|
|
724
|
+
this.clearOutput();
|
|
725
|
+
this.cleanup();
|
|
726
|
+
return this;
|
|
727
|
+
}
|
|
728
|
+
success(label) {
|
|
729
|
+
if (label !== undefined)
|
|
730
|
+
this.label = label;
|
|
731
|
+
this.finish("success");
|
|
732
|
+
return this;
|
|
733
|
+
}
|
|
734
|
+
fail(label) {
|
|
735
|
+
if (label !== undefined)
|
|
736
|
+
this.label = label;
|
|
737
|
+
this.finish("error");
|
|
738
|
+
return this;
|
|
739
|
+
}
|
|
740
|
+
update(label) {
|
|
741
|
+
this.label = label;
|
|
742
|
+
if (this.state === "spinning" && isInteractive()) {
|
|
743
|
+
this.render();
|
|
744
|
+
}
|
|
745
|
+
return this;
|
|
746
|
+
}
|
|
747
|
+
setDetail(detail) {
|
|
748
|
+
this.detail = detail;
|
|
749
|
+
if (this.state === "spinning" && isInteractive()) {
|
|
750
|
+
this.render();
|
|
751
|
+
}
|
|
752
|
+
return this;
|
|
753
|
+
}
|
|
754
|
+
get isSpinning() {
|
|
755
|
+
return this.state === "spinning";
|
|
756
|
+
}
|
|
757
|
+
finish(state) {
|
|
758
|
+
if (this.timer) {
|
|
759
|
+
clearInterval(this.timer);
|
|
760
|
+
this.timer = null;
|
|
761
|
+
}
|
|
762
|
+
this.state = state;
|
|
763
|
+
if (!isInteractive()) {
|
|
764
|
+
const symbol = state === "success" ? this.symbols.success : this.symbols.error;
|
|
765
|
+
const colorFn = state === "success" ? colors.success : colors.error;
|
|
766
|
+
console.log(`${colorFn(symbol)} ${this.label}`);
|
|
767
|
+
} else {
|
|
768
|
+
this.clearOutput();
|
|
769
|
+
const symbol = state === "success" ? this.symbols.success : this.symbols.error;
|
|
770
|
+
const colorFn = state === "success" ? colors.success : colors.error;
|
|
771
|
+
process.stdout.write(`${colorFn(symbol)} ${this.label}
|
|
772
|
+
`);
|
|
773
|
+
}
|
|
774
|
+
this.cleanup();
|
|
775
|
+
}
|
|
776
|
+
render() {
|
|
777
|
+
if (!isTTY())
|
|
778
|
+
return;
|
|
779
|
+
this.clearOutput();
|
|
780
|
+
const frame = this.colorFn(this.frames[this.frameIndex]);
|
|
781
|
+
const width = getTerminalWidth();
|
|
782
|
+
const mainLine = truncate(`${frame} ${this.label}`, width);
|
|
783
|
+
process.stdout.write(mainLine);
|
|
784
|
+
let lines = 1;
|
|
785
|
+
if (this.detail) {
|
|
786
|
+
const detailLine = truncate(` ${colors.muted(this.detail)}`, width);
|
|
787
|
+
process.stdout.write(`
|
|
788
|
+
${detailLine}`);
|
|
789
|
+
lines = 2;
|
|
790
|
+
}
|
|
791
|
+
this.lastRenderedLines = lines;
|
|
792
|
+
}
|
|
793
|
+
clearOutput() {
|
|
794
|
+
if (!isTTY())
|
|
795
|
+
return;
|
|
796
|
+
for (let i = 0;i < this.lastRenderedLines; i++) {
|
|
797
|
+
if (i > 0)
|
|
798
|
+
process.stdout.write(cursor.up(1));
|
|
799
|
+
clearLine();
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
setupSignalHandler() {
|
|
803
|
+
this.sigintHandler = () => {
|
|
804
|
+
this.cleanup();
|
|
805
|
+
process.exit(130);
|
|
806
|
+
};
|
|
807
|
+
process.on("SIGINT", this.sigintHandler);
|
|
808
|
+
}
|
|
809
|
+
cleanup() {
|
|
810
|
+
if (this.sigintHandler) {
|
|
811
|
+
process.removeListener("SIGINT", this.sigintHandler);
|
|
812
|
+
this.sigintHandler = null;
|
|
813
|
+
}
|
|
814
|
+
showCursor();
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
function spinner(label, options) {
|
|
818
|
+
return new Spinner({ ...options, label }).start();
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
class StepProgress {
|
|
822
|
+
steps = [];
|
|
823
|
+
showNumbers;
|
|
824
|
+
spinnerInterval;
|
|
825
|
+
spinnerFrames;
|
|
826
|
+
spinnerIndex = 0;
|
|
827
|
+
timer = null;
|
|
828
|
+
symbols = getSymbols(supportsUnicode());
|
|
829
|
+
lastRenderedLines = 0;
|
|
830
|
+
sigintHandler = null;
|
|
831
|
+
constructor(options = {}) {
|
|
832
|
+
this.showNumbers = options.showNumbers ?? true;
|
|
833
|
+
this.spinnerInterval = options.spinnerInterval ?? 80;
|
|
834
|
+
this.spinnerFrames = supportsUnicode() ? ["◐", "◓", "◑", "◒"] : ["-", "\\", "|", "/"];
|
|
835
|
+
if (options.steps) {
|
|
836
|
+
this.steps = options.steps.map((label) => ({ label, status: "pending" }));
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
start() {
|
|
840
|
+
if (!isInteractive())
|
|
841
|
+
return this;
|
|
842
|
+
hideCursor();
|
|
843
|
+
this.setupSignalHandler();
|
|
844
|
+
this.render();
|
|
845
|
+
this.timer = setInterval(() => {
|
|
846
|
+
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;
|
|
847
|
+
if (this.steps.some((s) => s.status === "active")) {
|
|
848
|
+
this.render();
|
|
849
|
+
}
|
|
850
|
+
}, this.spinnerInterval);
|
|
851
|
+
return this;
|
|
852
|
+
}
|
|
853
|
+
addStep(label) {
|
|
854
|
+
this.steps.push({ label, status: "pending" });
|
|
855
|
+
if (isInteractive())
|
|
856
|
+
this.render();
|
|
857
|
+
return this;
|
|
858
|
+
}
|
|
859
|
+
startStep(index) {
|
|
860
|
+
if (index >= 0 && index < this.steps.length) {
|
|
861
|
+
this.steps[index].status = "active";
|
|
862
|
+
this.steps[index].startTime = Date.now();
|
|
863
|
+
if (isInteractive()) {
|
|
864
|
+
this.render();
|
|
865
|
+
} else {
|
|
866
|
+
const step = this.steps[index];
|
|
867
|
+
const prefix2 = this.showNumbers ? `[${index + 1}/${this.steps.length}] ` : "";
|
|
868
|
+
console.log(`${this.symbols.bullet} ${prefix2}${step.label}...`);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return this;
|
|
872
|
+
}
|
|
873
|
+
completeStep(index) {
|
|
874
|
+
if (index >= 0 && index < this.steps.length) {
|
|
875
|
+
this.steps[index].status = "completed";
|
|
876
|
+
this.steps[index].endTime = Date.now();
|
|
877
|
+
if (isInteractive()) {
|
|
878
|
+
this.render();
|
|
879
|
+
} else {
|
|
880
|
+
const step = this.steps[index];
|
|
881
|
+
const duration = this.getStepDuration(step);
|
|
882
|
+
const prefix2 = this.showNumbers ? `[${index + 1}/${this.steps.length}] ` : "";
|
|
883
|
+
console.log(`${colors.success(this.symbols.success)} ${prefix2}${step.label}${duration}`);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return this;
|
|
887
|
+
}
|
|
888
|
+
failStep(index) {
|
|
889
|
+
if (index >= 0 && index < this.steps.length) {
|
|
890
|
+
this.steps[index].status = "failed";
|
|
891
|
+
this.steps[index].endTime = Date.now();
|
|
892
|
+
if (isInteractive()) {
|
|
893
|
+
this.render();
|
|
894
|
+
} else {
|
|
895
|
+
const step = this.steps[index];
|
|
896
|
+
const prefix2 = this.showNumbers ? `[${index + 1}/${this.steps.length}] ` : "";
|
|
897
|
+
console.log(`${colors.error(this.symbols.error)} ${prefix2}${step.label}`);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return this;
|
|
901
|
+
}
|
|
902
|
+
skipStep(index) {
|
|
903
|
+
if (index >= 0 && index < this.steps.length) {
|
|
904
|
+
this.steps[index].status = "skipped";
|
|
905
|
+
if (isInteractive())
|
|
906
|
+
this.render();
|
|
907
|
+
}
|
|
908
|
+
return this;
|
|
909
|
+
}
|
|
910
|
+
async run(tasks) {
|
|
911
|
+
this.steps = tasks.map((t) => ({ label: t.label, status: "pending" }));
|
|
912
|
+
this.start();
|
|
913
|
+
const results = [];
|
|
914
|
+
let failed = false;
|
|
915
|
+
for (let i = 0;i < tasks.length; i++) {
|
|
916
|
+
if (failed) {
|
|
917
|
+
this.skipStep(i);
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
this.startStep(i);
|
|
921
|
+
try {
|
|
922
|
+
results.push(await tasks[i].task());
|
|
923
|
+
this.completeStep(i);
|
|
924
|
+
} catch {
|
|
925
|
+
this.failStep(i);
|
|
926
|
+
failed = true;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
this.stop();
|
|
930
|
+
return { results, failed };
|
|
931
|
+
}
|
|
932
|
+
stop() {
|
|
933
|
+
if (this.timer) {
|
|
934
|
+
clearInterval(this.timer);
|
|
935
|
+
this.timer = null;
|
|
936
|
+
}
|
|
937
|
+
this.cleanup();
|
|
938
|
+
return this;
|
|
939
|
+
}
|
|
940
|
+
get currentStepIndex() {
|
|
941
|
+
const activeIdx = this.steps.findIndex((s) => s.status === "active");
|
|
942
|
+
if (activeIdx >= 0)
|
|
943
|
+
return activeIdx;
|
|
944
|
+
return this.steps.findIndex((s) => s.status === "pending");
|
|
945
|
+
}
|
|
946
|
+
render() {
|
|
947
|
+
if (!isTTY())
|
|
948
|
+
return;
|
|
949
|
+
if (this.lastRenderedLines > 0) {
|
|
950
|
+
moveCursorUp(this.lastRenderedLines - 1);
|
|
951
|
+
for (let i = 0;i < this.lastRenderedLines; i++) {
|
|
952
|
+
clearLine();
|
|
953
|
+
if (i < this.lastRenderedLines - 1) {
|
|
954
|
+
process.stdout.write(cursor.down(1));
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
moveCursorUp(this.lastRenderedLines - 1);
|
|
958
|
+
}
|
|
959
|
+
const width = getTerminalWidth();
|
|
960
|
+
const lines = [];
|
|
961
|
+
for (let i = 0;i < this.steps.length; i++) {
|
|
962
|
+
const step = this.steps[i];
|
|
963
|
+
const prefix2 = this.showNumbers ? `[${i + 1}/${this.steps.length}] ` : "";
|
|
964
|
+
const duration = this.getStepDuration(step);
|
|
965
|
+
let symbol;
|
|
966
|
+
let text;
|
|
967
|
+
switch (step.status) {
|
|
968
|
+
case "completed":
|
|
969
|
+
symbol = colors.success(this.symbols.success);
|
|
970
|
+
text = `${prefix2}${step.label}${duration}`;
|
|
971
|
+
break;
|
|
972
|
+
case "failed":
|
|
973
|
+
symbol = colors.error(this.symbols.error);
|
|
974
|
+
text = `${prefix2}${step.label}`;
|
|
975
|
+
break;
|
|
976
|
+
case "active":
|
|
977
|
+
symbol = colors.primary(this.spinnerFrames[this.spinnerIndex]);
|
|
978
|
+
text = `${prefix2}${step.label}`;
|
|
979
|
+
break;
|
|
980
|
+
case "skipped":
|
|
981
|
+
symbol = colors.muted(this.symbols.bullet);
|
|
982
|
+
text = colors.muted(`${prefix2}${step.label} (skipped)`);
|
|
983
|
+
break;
|
|
984
|
+
default:
|
|
985
|
+
symbol = colors.muted("○");
|
|
986
|
+
text = colors.muted(`${prefix2}${step.label}`);
|
|
987
|
+
}
|
|
988
|
+
lines.push(truncate(`${symbol} ${text}`, width));
|
|
989
|
+
}
|
|
990
|
+
process.stdout.write(lines.join(`
|
|
991
|
+
`));
|
|
992
|
+
this.lastRenderedLines = lines.length;
|
|
993
|
+
}
|
|
994
|
+
getStepDuration(step) {
|
|
995
|
+
if (step.startTime && step.endTime) {
|
|
996
|
+
const ms = step.endTime - step.startTime;
|
|
997
|
+
return colors.muted(` (${formatDuration(ms)})`);
|
|
998
|
+
}
|
|
999
|
+
return "";
|
|
1000
|
+
}
|
|
1001
|
+
setupSignalHandler() {
|
|
1002
|
+
this.sigintHandler = () => {
|
|
1003
|
+
this.cleanup();
|
|
1004
|
+
process.exit(130);
|
|
1005
|
+
};
|
|
1006
|
+
process.on("SIGINT", this.sigintHandler);
|
|
1007
|
+
}
|
|
1008
|
+
cleanup() {
|
|
1009
|
+
if (this.sigintHandler) {
|
|
1010
|
+
process.removeListener("SIGINT", this.sigintHandler);
|
|
1011
|
+
this.sigintHandler = null;
|
|
1012
|
+
}
|
|
1013
|
+
showCursor();
|
|
1014
|
+
if (isTTY() && this.lastRenderedLines > 0) {
|
|
1015
|
+
process.stdout.write(`
|
|
1016
|
+
`);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
class Summary {
|
|
1021
|
+
items = [];
|
|
1022
|
+
title;
|
|
1023
|
+
boxed;
|
|
1024
|
+
keyWidth;
|
|
1025
|
+
symbols = getSymbols(supportsUnicode());
|
|
1026
|
+
constructor(options = {}) {
|
|
1027
|
+
this.title = options.title;
|
|
1028
|
+
this.boxed = options.boxed ?? false;
|
|
1029
|
+
this.keyWidth = options.keyWidth;
|
|
1030
|
+
}
|
|
1031
|
+
add(item) {
|
|
1032
|
+
this.items.push(item);
|
|
1033
|
+
return this;
|
|
1034
|
+
}
|
|
1035
|
+
addKeyValue(key, value, status) {
|
|
1036
|
+
return this.add({ key, value, status });
|
|
1037
|
+
}
|
|
1038
|
+
addWithThreshold(key, value, threshold) {
|
|
1039
|
+
const status = this.evaluateThreshold(value, threshold) ? "pass" : "fail";
|
|
1040
|
+
return this.add({ key, value, status, threshold });
|
|
1041
|
+
}
|
|
1042
|
+
print() {
|
|
1043
|
+
const output = this.render();
|
|
1044
|
+
console.log(output);
|
|
1045
|
+
}
|
|
1046
|
+
render() {
|
|
1047
|
+
if (this.items.length === 0)
|
|
1048
|
+
return "";
|
|
1049
|
+
const lines = [];
|
|
1050
|
+
const termWidth = getTerminalWidth();
|
|
1051
|
+
const calculatedKeyWidth = this.keyWidth ?? Math.max(...this.items.map((i) => i.key.length));
|
|
1052
|
+
if (this.boxed) {
|
|
1053
|
+
return this.renderBoxed(calculatedKeyWidth, termWidth);
|
|
1054
|
+
}
|
|
1055
|
+
if (this.title) {
|
|
1056
|
+
lines.push(colors.bold(this.title));
|
|
1057
|
+
lines.push("");
|
|
1058
|
+
}
|
|
1059
|
+
for (const item of this.items) {
|
|
1060
|
+
lines.push(this.formatItem(item, calculatedKeyWidth));
|
|
1061
|
+
}
|
|
1062
|
+
return lines.join(`
|
|
1063
|
+
`);
|
|
1064
|
+
}
|
|
1065
|
+
renderBoxed(keyWidth, termWidth) {
|
|
1066
|
+
const lines = [];
|
|
1067
|
+
const unicode = supportsUnicode();
|
|
1068
|
+
const box = unicode ? { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│" } : { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|" };
|
|
1069
|
+
const contentLines = this.items.map((item) => this.formatItem(item, keyWidth));
|
|
1070
|
+
const maxContentWidth = Math.max(...contentLines.map((l) => stripAnsi(l).length), this.title ? this.title.length : 0);
|
|
1071
|
+
const innerWidth = Math.min(maxContentWidth + 2, termWidth - 4);
|
|
1072
|
+
lines.push(box.tl + box.h.repeat(innerWidth) + box.tr);
|
|
1073
|
+
if (this.title) {
|
|
1074
|
+
const padding = innerWidth - this.title.length;
|
|
1075
|
+
const leftPad = Math.floor(padding / 2);
|
|
1076
|
+
const rightPad = padding - leftPad;
|
|
1077
|
+
lines.push(`${box.v}${" ".repeat(leftPad)}${colors.bold(this.title)}${" ".repeat(rightPad)}${box.v}`);
|
|
1078
|
+
lines.push(`${box.v}${" ".repeat(innerWidth)}${box.v}`);
|
|
1079
|
+
}
|
|
1080
|
+
for (const line of contentLines) {
|
|
1081
|
+
const visibleLen = stripAnsi(line).length;
|
|
1082
|
+
const padding = innerWidth - visibleLen - 1;
|
|
1083
|
+
lines.push(`${box.v} ${line}${" ".repeat(Math.max(0, padding))}${box.v}`);
|
|
1084
|
+
}
|
|
1085
|
+
lines.push(box.bl + box.h.repeat(innerWidth) + box.br);
|
|
1086
|
+
return lines.join(`
|
|
1087
|
+
`);
|
|
1088
|
+
}
|
|
1089
|
+
formatItem(item, keyWidth) {
|
|
1090
|
+
const key = item.key.padEnd(keyWidth);
|
|
1091
|
+
const value = String(item.value);
|
|
1092
|
+
let indicator = "";
|
|
1093
|
+
if (item.status) {
|
|
1094
|
+
switch (item.status) {
|
|
1095
|
+
case "pass":
|
|
1096
|
+
indicator = `${colors.success(this.symbols.success)} `;
|
|
1097
|
+
break;
|
|
1098
|
+
case "fail":
|
|
1099
|
+
indicator = `${colors.error(this.symbols.error)} `;
|
|
1100
|
+
break;
|
|
1101
|
+
case "warn":
|
|
1102
|
+
indicator = `${colors.warning(this.symbols.warning)} `;
|
|
1103
|
+
break;
|
|
1104
|
+
case "info":
|
|
1105
|
+
indicator = `${colors.info(this.symbols.info)} `;
|
|
1106
|
+
break;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
let thresholdStr = "";
|
|
1110
|
+
if (item.threshold) {
|
|
1111
|
+
const { operator, value: thresh } = item.threshold;
|
|
1112
|
+
thresholdStr = colors.muted(` (${operator} ${thresh})`);
|
|
1113
|
+
}
|
|
1114
|
+
let coloredValue = value;
|
|
1115
|
+
if (item.status === "pass")
|
|
1116
|
+
coloredValue = colors.success(value);
|
|
1117
|
+
else if (item.status === "fail")
|
|
1118
|
+
coloredValue = colors.error(value);
|
|
1119
|
+
else if (item.status === "warn")
|
|
1120
|
+
coloredValue = colors.warning(value);
|
|
1121
|
+
return `${indicator}${colors.muted(key)} ${coloredValue}${thresholdStr}`;
|
|
1122
|
+
}
|
|
1123
|
+
evaluateThreshold(value, threshold) {
|
|
1124
|
+
if (!threshold)
|
|
1125
|
+
return true;
|
|
1126
|
+
const { operator, value: thresh } = threshold;
|
|
1127
|
+
switch (operator) {
|
|
1128
|
+
case "<":
|
|
1129
|
+
return value < thresh;
|
|
1130
|
+
case ">":
|
|
1131
|
+
return value > thresh;
|
|
1132
|
+
case "<=":
|
|
1133
|
+
return value <= thresh;
|
|
1134
|
+
case ">=":
|
|
1135
|
+
return value >= thresh;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
function summary(options) {
|
|
1140
|
+
return new Summary(options);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
154
1143
|
// src/commands/check/index.ts
|
|
155
1144
|
import {
|
|
156
1145
|
buildDocCovSpec,
|
|
@@ -159,11 +1148,11 @@ import {
|
|
|
159
1148
|
parseExamplesFlag,
|
|
160
1149
|
resolveTarget
|
|
161
1150
|
} from "@doccov/sdk";
|
|
162
|
-
import
|
|
1151
|
+
import chalk7 from "chalk";
|
|
163
1152
|
|
|
164
1153
|
// src/utils/filter-options.ts
|
|
165
1154
|
import { mergeFilters, parseListFlag } from "@doccov/sdk";
|
|
166
|
-
import
|
|
1155
|
+
import chalk3 from "chalk";
|
|
167
1156
|
var parseVisibilityFlag = (value) => {
|
|
168
1157
|
if (!value)
|
|
169
1158
|
return;
|
|
@@ -180,7 +1169,7 @@ var parseVisibilityFlag = (value) => {
|
|
|
180
1169
|
}
|
|
181
1170
|
return result.length > 0 ? result : undefined;
|
|
182
1171
|
};
|
|
183
|
-
var formatList = (label, values) => `${label}: ${values.map((value) =>
|
|
1172
|
+
var formatList = (label, values) => `${label}: ${values.map((value) => chalk3.cyan(value)).join(", ")}`;
|
|
184
1173
|
var mergeFilterOptions = (config, cliOptions) => {
|
|
185
1174
|
const messages = [];
|
|
186
1175
|
if (config?.include) {
|
|
@@ -212,57 +1201,6 @@ var mergeFilterOptions = (config, cliOptions) => {
|
|
|
212
1201
|
};
|
|
213
1202
|
};
|
|
214
1203
|
|
|
215
|
-
// src/utils/progress.ts
|
|
216
|
-
import chalk2 from "chalk";
|
|
217
|
-
class StepProgress {
|
|
218
|
-
steps;
|
|
219
|
-
currentStep = 0;
|
|
220
|
-
startTime;
|
|
221
|
-
stepStartTime;
|
|
222
|
-
constructor(steps) {
|
|
223
|
-
this.steps = steps;
|
|
224
|
-
this.startTime = Date.now();
|
|
225
|
-
this.stepStartTime = Date.now();
|
|
226
|
-
}
|
|
227
|
-
start(stepIndex) {
|
|
228
|
-
this.currentStep = stepIndex ?? 0;
|
|
229
|
-
this.stepStartTime = Date.now();
|
|
230
|
-
this.render();
|
|
231
|
-
}
|
|
232
|
-
next() {
|
|
233
|
-
this.completeCurrentStep();
|
|
234
|
-
this.currentStep++;
|
|
235
|
-
if (this.currentStep < this.steps.length) {
|
|
236
|
-
this.stepStartTime = Date.now();
|
|
237
|
-
this.render();
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
complete(message) {
|
|
241
|
-
this.completeCurrentStep();
|
|
242
|
-
if (message) {
|
|
243
|
-
const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
|
|
244
|
-
console.log(`${chalk2.green("✓")} ${message} ${chalk2.dim(`(${elapsed}s)`)}`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
render() {
|
|
248
|
-
const step = this.steps[this.currentStep];
|
|
249
|
-
if (!step)
|
|
250
|
-
return;
|
|
251
|
-
const label = step.activeLabel ?? step.label;
|
|
252
|
-
const prefix = chalk2.dim(`[${this.currentStep + 1}/${this.steps.length}]`);
|
|
253
|
-
process.stdout.write(`\r${prefix} ${chalk2.cyan(label)}...`);
|
|
254
|
-
}
|
|
255
|
-
completeCurrentStep() {
|
|
256
|
-
const step = this.steps[this.currentStep];
|
|
257
|
-
if (!step)
|
|
258
|
-
return;
|
|
259
|
-
const elapsed = ((Date.now() - this.stepStartTime) / 1000).toFixed(1);
|
|
260
|
-
const prefix = chalk2.dim(`[${this.currentStep + 1}/${this.steps.length}]`);
|
|
261
|
-
process.stdout.write(`\r${" ".repeat(80)}\r`);
|
|
262
|
-
console.log(`${prefix} ${step.label} ${chalk2.green("✓")} ${chalk2.dim(`(${elapsed}s)`)}`);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
1204
|
// src/utils/validation.ts
|
|
267
1205
|
function clampPercentage(value, fallback = 80) {
|
|
268
1206
|
if (Number.isNaN(value))
|
|
@@ -287,7 +1225,7 @@ import {
|
|
|
287
1225
|
parseJSDocToPatch,
|
|
288
1226
|
serializeJSDoc
|
|
289
1227
|
} from "@doccov/sdk";
|
|
290
|
-
import
|
|
1228
|
+
import chalk5 from "chalk";
|
|
291
1229
|
|
|
292
1230
|
// src/commands/check/utils.ts
|
|
293
1231
|
import * as fs from "node:fs";
|
|
@@ -361,13 +1299,13 @@ async function handleFixes(openpkg, doccov, options, deps) {
|
|
|
361
1299
|
}
|
|
362
1300
|
const { fixable, nonFixable } = categorizeDrifts(allDrifts.map((d) => d.drift));
|
|
363
1301
|
if (fixable.length === 0) {
|
|
364
|
-
log(
|
|
1302
|
+
log(chalk5.yellow(`Found ${nonFixable.length} drift issue(s), but none are auto-fixable.`));
|
|
365
1303
|
return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
|
|
366
1304
|
}
|
|
367
1305
|
log("");
|
|
368
|
-
log(
|
|
1306
|
+
log(chalk5.bold(`Found ${fixable.length} fixable issue(s)`));
|
|
369
1307
|
if (nonFixable.length > 0) {
|
|
370
|
-
log(
|
|
1308
|
+
log(chalk5.gray(`(${nonFixable.length} non-fixable issue(s) skipped)`));
|
|
371
1309
|
}
|
|
372
1310
|
log("");
|
|
373
1311
|
const groupedDrifts = groupByExport(allDrifts.filter((d) => fixable.includes(d.drift)));
|
|
@@ -400,16 +1338,16 @@ async function handleFixes(openpkg, doccov, options, deps) {
|
|
|
400
1338
|
const applyResult = await applyEdits(edits);
|
|
401
1339
|
if (applyResult.errors.length > 0) {
|
|
402
1340
|
for (const err of applyResult.errors) {
|
|
403
|
-
error(
|
|
1341
|
+
error(chalk5.red(` ${err.file}: ${err.error}`));
|
|
404
1342
|
}
|
|
405
1343
|
}
|
|
406
1344
|
const totalFixes = Array.from(editsByFile.values()).reduce((sum, fileEdits) => sum + fileEdits.reduce((s, e) => s + e.fixes.length, 0), 0);
|
|
407
1345
|
log("");
|
|
408
|
-
log(
|
|
1346
|
+
log(chalk5.green(`✓ Applied ${totalFixes} fix(es) to ${applyResult.filesModified} file(s)`));
|
|
409
1347
|
for (const [filePath, fileEdits] of editsByFile) {
|
|
410
1348
|
const relativePath = path3.relative(targetDir, filePath);
|
|
411
1349
|
const fixCount = fileEdits.reduce((s, e) => s + e.fixes.length, 0);
|
|
412
|
-
log(
|
|
1350
|
+
log(chalk5.dim(` ${relativePath} (${fixCount} fixes)`));
|
|
413
1351
|
}
|
|
414
1352
|
return {
|
|
415
1353
|
fixedDriftKeys,
|
|
@@ -419,22 +1357,22 @@ async function handleFixes(openpkg, doccov, options, deps) {
|
|
|
419
1357
|
}
|
|
420
1358
|
function generateEditForExport(exp, drifts, targetDir, log) {
|
|
421
1359
|
if (!exp.source?.file) {
|
|
422
|
-
log(
|
|
1360
|
+
log(chalk5.gray(` Skipping ${exp.name}: no source location`));
|
|
423
1361
|
return null;
|
|
424
1362
|
}
|
|
425
1363
|
if (exp.source.file.endsWith(".d.ts")) {
|
|
426
|
-
log(
|
|
1364
|
+
log(chalk5.gray(` Skipping ${exp.name}: declaration file`));
|
|
427
1365
|
return null;
|
|
428
1366
|
}
|
|
429
1367
|
const filePath = path3.resolve(targetDir, exp.source.file);
|
|
430
1368
|
if (!fs2.existsSync(filePath)) {
|
|
431
|
-
log(
|
|
1369
|
+
log(chalk5.gray(` Skipping ${exp.name}: file not found`));
|
|
432
1370
|
return null;
|
|
433
1371
|
}
|
|
434
1372
|
const sourceFile = createSourceFile(filePath);
|
|
435
1373
|
const location = findJSDocLocation(sourceFile, exp.name, exp.source.line);
|
|
436
1374
|
if (!location) {
|
|
437
|
-
log(
|
|
1375
|
+
log(chalk5.gray(` Skipping ${exp.name}: could not find declaration`));
|
|
438
1376
|
return null;
|
|
439
1377
|
}
|
|
440
1378
|
let existingPatch = {};
|
|
@@ -459,13 +1397,13 @@ function generateEditForExport(exp, drifts, targetDir, log) {
|
|
|
459
1397
|
return { filePath, edit, fixes, existingPatch };
|
|
460
1398
|
}
|
|
461
1399
|
function displayPreview(editsByFile, targetDir, log) {
|
|
462
|
-
log(
|
|
1400
|
+
log(chalk5.bold("Preview - changes that would be made:"));
|
|
463
1401
|
log("");
|
|
464
1402
|
for (const [filePath, fileEdits] of editsByFile) {
|
|
465
1403
|
const relativePath = path3.relative(targetDir, filePath);
|
|
466
1404
|
for (const { export: exp, edit, fixes } of fileEdits) {
|
|
467
|
-
log(
|
|
468
|
-
log(
|
|
1405
|
+
log(chalk5.cyan(`${relativePath}:${edit.startLine + 1}`));
|
|
1406
|
+
log(chalk5.bold(` ${exp.name}`));
|
|
469
1407
|
log("");
|
|
470
1408
|
if (edit.hasExisting && edit.existingJSDoc) {
|
|
471
1409
|
const oldLines = edit.existingJSDoc.split(`
|
|
@@ -473,31 +1411,30 @@ function displayPreview(editsByFile, targetDir, log) {
|
|
|
473
1411
|
const newLines = edit.newJSDoc.split(`
|
|
474
1412
|
`);
|
|
475
1413
|
for (const line of oldLines) {
|
|
476
|
-
log(
|
|
1414
|
+
log(chalk5.red(` - ${line}`));
|
|
477
1415
|
}
|
|
478
1416
|
for (const line of newLines) {
|
|
479
|
-
log(
|
|
1417
|
+
log(chalk5.green(` + ${line}`));
|
|
480
1418
|
}
|
|
481
1419
|
} else {
|
|
482
1420
|
const newLines = edit.newJSDoc.split(`
|
|
483
1421
|
`);
|
|
484
1422
|
for (const line of newLines) {
|
|
485
|
-
log(
|
|
1423
|
+
log(chalk5.green(` + ${line}`));
|
|
486
1424
|
}
|
|
487
1425
|
}
|
|
488
1426
|
log("");
|
|
489
|
-
log(
|
|
1427
|
+
log(chalk5.dim(` Fixes: ${fixes.map((f) => f.description).join(", ")}`));
|
|
490
1428
|
log("");
|
|
491
1429
|
}
|
|
492
1430
|
}
|
|
493
1431
|
const totalFixes = Array.from(editsByFile.values()).reduce((sum, fileEdits) => sum + fileEdits.reduce((s, e) => s + e.fixes.length, 0), 0);
|
|
494
|
-
log(
|
|
495
|
-
log(
|
|
1432
|
+
log(chalk5.yellow(`${totalFixes} fix(es) across ${editsByFile.size} file(s) would be applied.`));
|
|
1433
|
+
log(chalk5.gray("Run with --fix to apply these changes."));
|
|
496
1434
|
}
|
|
497
1435
|
|
|
498
1436
|
// src/commands/check/output.ts
|
|
499
1437
|
import { generateReportFromDocCov } from "@doccov/sdk";
|
|
500
|
-
import chalk5 from "chalk";
|
|
501
1438
|
|
|
502
1439
|
// src/reports/changelog-renderer.ts
|
|
503
1440
|
function renderChangelog(data, options = {}) {
|
|
@@ -1117,18 +2054,18 @@ function getExportKeyword(kind) {
|
|
|
1117
2054
|
}
|
|
1118
2055
|
}
|
|
1119
2056
|
function formatExportSignature(exp) {
|
|
1120
|
-
const
|
|
2057
|
+
const prefix2 = `export ${getExportKeyword(exp.kind)}`;
|
|
1121
2058
|
if (exp.kind === "function" && exp.signatures?.[0]) {
|
|
1122
2059
|
const sig = exp.signatures[0];
|
|
1123
2060
|
const params = sig.parameters?.map((p) => `${p.name}${p.required === false ? "?" : ""}`).join(", ") ?? "";
|
|
1124
2061
|
const ret = extractReturnType(sig.returns?.schema);
|
|
1125
|
-
return `${
|
|
2062
|
+
return `${prefix2} ${exp.name}(${params}): ${ret}`;
|
|
1126
2063
|
}
|
|
1127
2064
|
if (exp.kind === "type" || exp.kind === "interface") {
|
|
1128
|
-
return `${
|
|
2065
|
+
return `${prefix2} ${exp.name}`;
|
|
1129
2066
|
}
|
|
1130
2067
|
if (exp.kind === "class") {
|
|
1131
|
-
return `${
|
|
2068
|
+
return `${prefix2} ${exp.name}`;
|
|
1132
2069
|
}
|
|
1133
2070
|
return `export ${exp.kind} ${exp.name}`;
|
|
1134
2071
|
}
|
|
@@ -1322,7 +2259,7 @@ function computeStats(openpkg, doccov) {
|
|
|
1322
2259
|
import * as fs3 from "node:fs";
|
|
1323
2260
|
import * as path5 from "node:path";
|
|
1324
2261
|
import { DEFAULT_REPORT_DIR, getReportPath } from "@doccov/sdk";
|
|
1325
|
-
import
|
|
2262
|
+
import chalk6 from "chalk";
|
|
1326
2263
|
function writeReport(options) {
|
|
1327
2264
|
const { format, content, outputPath, cwd = process.cwd(), silent = false } = options;
|
|
1328
2265
|
const reportPath = outputPath ? path5.resolve(cwd, outputPath) : path5.resolve(cwd, getReportPath(format));
|
|
@@ -1333,7 +2270,7 @@ function writeReport(options) {
|
|
|
1333
2270
|
fs3.writeFileSync(reportPath, content);
|
|
1334
2271
|
const relativePath = path5.relative(cwd, reportPath);
|
|
1335
2272
|
if (!silent) {
|
|
1336
|
-
console.log(
|
|
2273
|
+
console.log(chalk6.green(`✓ Wrote ${format} report to ${relativePath}`));
|
|
1337
2274
|
}
|
|
1338
2275
|
return { path: reportPath, format, relativePath };
|
|
1339
2276
|
}
|
|
@@ -1371,6 +2308,7 @@ function displayTextOutput(options, deps) {
|
|
|
1371
2308
|
specInfos
|
|
1372
2309
|
} = options;
|
|
1373
2310
|
const { log } = deps;
|
|
2311
|
+
const sym = getSymbols(supportsUnicode());
|
|
1374
2312
|
const totalExportsForDrift = openpkg.exports?.length ?? 0;
|
|
1375
2313
|
const exportsWithDrift = new Set(driftExports.map((d) => d.name)).size;
|
|
1376
2314
|
const driftScore = totalExportsForDrift === 0 ? 0 : Math.round(exportsWithDrift / totalExportsForDrift * 100);
|
|
@@ -1380,15 +2318,15 @@ function displayTextOutput(options, deps) {
|
|
|
1380
2318
|
if (specWarnings.length > 0 || specInfos.length > 0) {
|
|
1381
2319
|
log("");
|
|
1382
2320
|
for (const diag of specWarnings) {
|
|
1383
|
-
log(
|
|
2321
|
+
log(colors.warning(`${sym.warning} ${diag.message}`));
|
|
1384
2322
|
if (diag.suggestion) {
|
|
1385
|
-
log(
|
|
2323
|
+
log(colors.muted(` ${diag.suggestion}`));
|
|
1386
2324
|
}
|
|
1387
2325
|
}
|
|
1388
2326
|
for (const diag of specInfos) {
|
|
1389
|
-
log(
|
|
2327
|
+
log(colors.info(`${sym.info} ${diag.message}`));
|
|
1390
2328
|
if (diag.suggestion) {
|
|
1391
|
-
log(
|
|
2329
|
+
log(colors.muted(` ${diag.suggestion}`));
|
|
1392
2330
|
}
|
|
1393
2331
|
}
|
|
1394
2332
|
}
|
|
@@ -1396,47 +2334,47 @@ function displayTextOutput(options, deps) {
|
|
|
1396
2334
|
const pkgVersion = openpkg.meta?.version ?? "";
|
|
1397
2335
|
const totalExports = openpkg.exports?.length ?? 0;
|
|
1398
2336
|
log("");
|
|
1399
|
-
log(
|
|
2337
|
+
log(colors.bold(`${pkgName}${pkgVersion ? `@${pkgVersion}` : ""}`));
|
|
1400
2338
|
log("");
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
} else {
|
|
1405
|
-
log(chalk5.green(` Coverage: ✓ ${coverageScore}%`) + chalk5.dim(` (min ${minCoverage}%)`));
|
|
1406
|
-
}
|
|
2339
|
+
const summaryBuilder = summary({ keyWidth: 10 });
|
|
2340
|
+
summaryBuilder.addKeyValue("Exports", totalExports);
|
|
2341
|
+
summaryBuilder.addKeyValue("Coverage", `${coverageScore}%`, coverageFailed ? "fail" : "pass");
|
|
1407
2342
|
if (maxDrift !== undefined) {
|
|
1408
|
-
|
|
1409
|
-
log(chalk5.red(` Drift: ✗ ${driftScore}%`) + chalk5.dim(` (max ${maxDrift}%)`));
|
|
1410
|
-
} else {
|
|
1411
|
-
log(chalk5.green(` Drift: ✓ ${driftScore}%`) + chalk5.dim(` (max ${maxDrift}%)`));
|
|
1412
|
-
}
|
|
2343
|
+
summaryBuilder.addKeyValue("Drift", `${driftScore}%`, driftFailed ? "fail" : "pass");
|
|
1413
2344
|
} else {
|
|
1414
|
-
|
|
2345
|
+
summaryBuilder.addKeyValue("Drift", `${driftScore}%`);
|
|
1415
2346
|
}
|
|
1416
2347
|
if (exampleResult) {
|
|
1417
2348
|
const typecheckCount = exampleResult.typecheck?.errors.length ?? 0;
|
|
1418
2349
|
if (typecheckCount > 0) {
|
|
1419
|
-
|
|
1420
|
-
for (const err of typecheckErrors.slice(0, 5)) {
|
|
1421
|
-
const loc = `example[${err.error.exampleIndex}]:${err.error.line}:${err.error.column}`;
|
|
1422
|
-
log(chalk5.dim(` ${err.exportName} ${loc}`));
|
|
1423
|
-
log(chalk5.red(` ${err.error.message}`));
|
|
1424
|
-
}
|
|
1425
|
-
if (typecheckErrors.length > 5) {
|
|
1426
|
-
log(chalk5.dim(` ... and ${typecheckErrors.length - 5} more`));
|
|
1427
|
-
}
|
|
2350
|
+
summaryBuilder.addKeyValue("Examples", `${typecheckCount} type error(s)`, "warn");
|
|
1428
2351
|
} else {
|
|
1429
|
-
|
|
2352
|
+
summaryBuilder.addKeyValue("Examples", "validated", "pass");
|
|
1430
2353
|
}
|
|
1431
2354
|
}
|
|
1432
2355
|
const hasStaleRefs = staleRefs.length > 0;
|
|
1433
2356
|
if (hasStaleRefs) {
|
|
1434
|
-
|
|
2357
|
+
summaryBuilder.addKeyValue("Docs", `${staleRefs.length} stale ref(s)`, "warn");
|
|
2358
|
+
}
|
|
2359
|
+
summaryBuilder.print();
|
|
2360
|
+
if (hasTypecheckErrors) {
|
|
2361
|
+
log("");
|
|
2362
|
+
for (const err of typecheckErrors.slice(0, 5)) {
|
|
2363
|
+
const loc = `example[${err.error.exampleIndex}]:${err.error.line}:${err.error.column}`;
|
|
2364
|
+
log(colors.muted(` ${err.exportName} ${loc}`));
|
|
2365
|
+
log(colors.error(` ${err.error.message}`));
|
|
2366
|
+
}
|
|
2367
|
+
if (typecheckErrors.length > 5) {
|
|
2368
|
+
log(colors.muted(` ... and ${typecheckErrors.length - 5} more`));
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
if (hasStaleRefs) {
|
|
2372
|
+
log("");
|
|
1435
2373
|
for (const ref of staleRefs.slice(0, 5)) {
|
|
1436
|
-
log(
|
|
2374
|
+
log(colors.muted(` ${ref.file}:${ref.line} - "${ref.exportName}"`));
|
|
1437
2375
|
}
|
|
1438
2376
|
if (staleRefs.length > 5) {
|
|
1439
|
-
log(
|
|
2377
|
+
log(colors.muted(` ... and ${staleRefs.length - 5} more`));
|
|
1440
2378
|
}
|
|
1441
2379
|
}
|
|
1442
2380
|
log("");
|
|
@@ -1447,17 +2385,23 @@ function displayTextOutput(options, deps) {
|
|
|
1447
2385
|
if (maxDrift !== undefined) {
|
|
1448
2386
|
thresholdParts.push(`drift ${driftScore}% ≤ ${maxDrift}%`);
|
|
1449
2387
|
}
|
|
1450
|
-
log(
|
|
2388
|
+
log(colors.success(`${sym.success} Check passed (${thresholdParts.join(", ")})`));
|
|
1451
2389
|
return true;
|
|
1452
2390
|
}
|
|
2391
|
+
if (coverageFailed) {
|
|
2392
|
+
log(colors.error(`${sym.error} Coverage ${coverageScore}% below minimum ${minCoverage}%`));
|
|
2393
|
+
}
|
|
2394
|
+
if (driftFailed) {
|
|
2395
|
+
log(colors.error(`${sym.error} Drift ${driftScore}% exceeds maximum ${maxDrift}%`));
|
|
2396
|
+
}
|
|
1453
2397
|
if (hasTypecheckErrors) {
|
|
1454
|
-
log(
|
|
2398
|
+
log(colors.error(`${sym.error} ${typecheckErrors.length} example type errors`));
|
|
1455
2399
|
}
|
|
1456
2400
|
if (hasStaleRefs) {
|
|
1457
|
-
log(
|
|
2401
|
+
log(colors.error(`${sym.error} ${staleRefs.length} stale references in docs`));
|
|
1458
2402
|
}
|
|
1459
2403
|
log("");
|
|
1460
|
-
log(
|
|
2404
|
+
log(colors.muted("Use --format json or --format markdown for detailed reports"));
|
|
1461
2405
|
return false;
|
|
1462
2406
|
}
|
|
1463
2407
|
function handleNonTextOutput(options, deps) {
|
|
@@ -1608,18 +2552,9 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
1608
2552
|
return n;
|
|
1609
2553
|
}).option("--no-cache", "Bypass spec cache and force regeneration").option("--visibility <tags>", "Filter by release stage: public,beta,alpha,internal (comma-separated)").action(async (entry, options) => {
|
|
1610
2554
|
try {
|
|
2555
|
+
const spin = spinner("Analyzing...");
|
|
1611
2556
|
let validations = parseExamplesFlag(options.examples);
|
|
1612
2557
|
let hasExamples = validations.length > 0;
|
|
1613
|
-
const stepList = [
|
|
1614
|
-
{ label: "Resolved target", activeLabel: "Resolving target" },
|
|
1615
|
-
{ label: "Loaded config", activeLabel: "Loading config" },
|
|
1616
|
-
{ label: "Generated spec", activeLabel: "Generating spec" },
|
|
1617
|
-
{ label: "Enriched spec", activeLabel: "Enriching spec" },
|
|
1618
|
-
...hasExamples ? [{ label: "Validated examples", activeLabel: "Validating examples" }] : [],
|
|
1619
|
-
{ label: "Processed results", activeLabel: "Processing results" }
|
|
1620
|
-
];
|
|
1621
|
-
const steps = new StepProgress(stepList);
|
|
1622
|
-
steps.start();
|
|
1623
2558
|
const fileSystem = new NodeFileSystem(options.cwd);
|
|
1624
2559
|
const resolved = await resolveTarget(fileSystem, {
|
|
1625
2560
|
cwd: options.cwd,
|
|
@@ -1627,7 +2562,6 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
1627
2562
|
entry
|
|
1628
2563
|
});
|
|
1629
2564
|
const { targetDir, entryFile } = resolved;
|
|
1630
|
-
steps.next();
|
|
1631
2565
|
const config = await loadDocCovConfig(targetDir);
|
|
1632
2566
|
if (!hasExamples && config?.check?.examples) {
|
|
1633
2567
|
const configExamples = config.check.examples;
|
|
@@ -1650,9 +2584,8 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
1650
2584
|
};
|
|
1651
2585
|
const resolvedFilters = mergeFilterOptions(config, cliFilters);
|
|
1652
2586
|
if (resolvedFilters.visibility) {
|
|
1653
|
-
log(
|
|
2587
|
+
log(chalk7.dim(`Filtering by visibility: ${resolvedFilters.visibility.join(", ")}`));
|
|
1654
2588
|
}
|
|
1655
|
-
steps.next();
|
|
1656
2589
|
const resolveExternalTypes = !options.skipResolve;
|
|
1657
2590
|
const analyzer = createDocCov({
|
|
1658
2591
|
resolveExternalTypes,
|
|
@@ -1663,9 +2596,9 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
1663
2596
|
const analyzeOptions = resolvedFilters.visibility ? { filters: { visibility: resolvedFilters.visibility } } : {};
|
|
1664
2597
|
const specResult = await analyzer.analyzeFileWithDiagnostics(entryFile, analyzeOptions);
|
|
1665
2598
|
if (!specResult) {
|
|
2599
|
+
spin.fail("Analysis failed");
|
|
1666
2600
|
throw new Error("Failed to analyze documentation coverage.");
|
|
1667
2601
|
}
|
|
1668
|
-
steps.next();
|
|
1669
2602
|
const openpkg = specResult.spec;
|
|
1670
2603
|
const doccov = buildDocCovSpec({
|
|
1671
2604
|
openpkg,
|
|
@@ -1673,7 +2606,6 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
1673
2606
|
packagePath: targetDir
|
|
1674
2607
|
});
|
|
1675
2608
|
const format = options.format ?? "text";
|
|
1676
|
-
steps.next();
|
|
1677
2609
|
const specWarnings = specResult.diagnostics.filter((d) => d.severity === "warning");
|
|
1678
2610
|
const specInfos = specResult.diagnostics.filter((d) => d.severity === "info");
|
|
1679
2611
|
const isPreview = options.preview;
|
|
@@ -1689,7 +2621,6 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
1689
2621
|
exampleResult = validation.result;
|
|
1690
2622
|
typecheckErrors = validation.typecheckErrors;
|
|
1691
2623
|
runtimeDrifts = validation.runtimeDrifts;
|
|
1692
|
-
steps.next();
|
|
1693
2624
|
}
|
|
1694
2625
|
let docsPatterns = options.docs;
|
|
1695
2626
|
if (docsPatterns.length === 0 && config?.docs?.include) {
|
|
@@ -1709,7 +2640,7 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
1709
2640
|
driftExports = driftExports.filter((d) => !fixResult.fixedDriftKeys.has(`${d.name}:${d.issue}`));
|
|
1710
2641
|
}
|
|
1711
2642
|
}
|
|
1712
|
-
|
|
2643
|
+
spin.success("Analysis complete");
|
|
1713
2644
|
if (format !== "text") {
|
|
1714
2645
|
const passed2 = handleNonTextOutput({
|
|
1715
2646
|
format,
|
|
@@ -1747,7 +2678,7 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
1747
2678
|
process.exit(1);
|
|
1748
2679
|
}
|
|
1749
2680
|
} catch (commandError) {
|
|
1750
|
-
error(
|
|
2681
|
+
error(chalk7.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
|
|
1751
2682
|
process.exit(1);
|
|
1752
2683
|
}
|
|
1753
2684
|
});
|
|
@@ -1765,7 +2696,7 @@ import {
|
|
|
1765
2696
|
parseMarkdownFiles as parseMarkdownFiles2
|
|
1766
2697
|
} from "@doccov/sdk";
|
|
1767
2698
|
import { calculateNextVersion, recommendSemverBump } from "@openpkg-ts/spec";
|
|
1768
|
-
import
|
|
2699
|
+
import chalk8 from "chalk";
|
|
1769
2700
|
import { glob as glob2 } from "glob";
|
|
1770
2701
|
var defaultDependencies2 = {
|
|
1771
2702
|
readFileSync: fs4.readFileSync,
|
|
@@ -1800,6 +2731,7 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
1800
2731
|
` + `Usage: doccov diff <base> <head>
|
|
1801
2732
|
` + " or: doccov diff --base main.json --head feature.json");
|
|
1802
2733
|
}
|
|
2734
|
+
const spin = spinner("Comparing specs...");
|
|
1803
2735
|
const baseSpec = loadSpec(baseFile, readFileSync3);
|
|
1804
2736
|
const headSpec = loadSpec(headFile, readFileSync3);
|
|
1805
2737
|
const config = await loadDocCovConfig(options.cwd);
|
|
@@ -1823,6 +2755,7 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
1823
2755
|
const minCoverage = resolveThreshold(options.minCoverage, config?.check?.minCoverage);
|
|
1824
2756
|
const maxDrift = resolveThreshold(options.maxDrift, config?.check?.maxDrift);
|
|
1825
2757
|
if (options.recommendVersion) {
|
|
2758
|
+
spin.success("Comparison complete");
|
|
1826
2759
|
const recommendation = recommendSemverBump(diff);
|
|
1827
2760
|
const currentVersion = headSpec.meta?.version ?? "0.0.0";
|
|
1828
2761
|
const nextVersion = calculateNextVersion(currentVersion, recommendation.bump);
|
|
@@ -1838,9 +2771,9 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
1838
2771
|
}, null, 2));
|
|
1839
2772
|
} else {
|
|
1840
2773
|
log("");
|
|
1841
|
-
log(
|
|
2774
|
+
log(chalk8.bold("Semver Recommendation"));
|
|
1842
2775
|
log(` Current version: ${currentVersion}`);
|
|
1843
|
-
log(` Recommended: ${
|
|
2776
|
+
log(` Recommended: ${chalk8.cyan(nextVersion)} (${chalk8.yellow(recommendation.bump.toUpperCase())})`);
|
|
1844
2777
|
log(` Reason: ${recommendation.reason}`);
|
|
1845
2778
|
}
|
|
1846
2779
|
return;
|
|
@@ -1855,6 +2788,7 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
1855
2788
|
headName,
|
|
1856
2789
|
...diff
|
|
1857
2790
|
};
|
|
2791
|
+
spin.success("Comparison complete");
|
|
1858
2792
|
switch (format) {
|
|
1859
2793
|
case "text":
|
|
1860
2794
|
printSummary(diff, baseName, headName, fromCache, log);
|
|
@@ -1869,8 +2803,8 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
1869
2803
|
silent: true
|
|
1870
2804
|
});
|
|
1871
2805
|
}
|
|
1872
|
-
const cacheNote = fromCache ?
|
|
1873
|
-
log(
|
|
2806
|
+
const cacheNote = fromCache ? chalk8.cyan(" (cached)") : "";
|
|
2807
|
+
log(chalk8.dim(`Report: ${jsonPath}`) + cacheNote);
|
|
1874
2808
|
}
|
|
1875
2809
|
break;
|
|
1876
2810
|
case "json": {
|
|
@@ -1965,18 +2899,18 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
1965
2899
|
checks
|
|
1966
2900
|
});
|
|
1967
2901
|
if (failures.length > 0) {
|
|
1968
|
-
log(
|
|
2902
|
+
log(chalk8.red(`
|
|
1969
2903
|
✗ Check failed`));
|
|
1970
2904
|
for (const f of failures) {
|
|
1971
|
-
log(
|
|
2905
|
+
log(chalk8.red(` - ${f}`));
|
|
1972
2906
|
}
|
|
1973
2907
|
process.exitCode = 1;
|
|
1974
2908
|
} else if (options.strict || minCoverage !== undefined || maxDrift !== undefined) {
|
|
1975
|
-
log(
|
|
2909
|
+
log(chalk8.green(`
|
|
1976
2910
|
✓ All checks passed`));
|
|
1977
2911
|
}
|
|
1978
2912
|
} catch (commandError) {
|
|
1979
|
-
error(
|
|
2913
|
+
error(chalk8.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
|
|
1980
2914
|
process.exitCode = 1;
|
|
1981
2915
|
}
|
|
1982
2916
|
});
|
|
@@ -2003,7 +2937,7 @@ async function generateDiff(baseSpec, headSpec, options, config, log) {
|
|
|
2003
2937
|
if (!docsPatterns || docsPatterns.length === 0) {
|
|
2004
2938
|
if (config?.docs?.include) {
|
|
2005
2939
|
docsPatterns = config.docs.include;
|
|
2006
|
-
log(
|
|
2940
|
+
log(chalk8.gray(`Using docs patterns from config: ${docsPatterns.join(", ")}`));
|
|
2007
2941
|
}
|
|
2008
2942
|
}
|
|
2009
2943
|
if (docsPatterns && docsPatterns.length > 0) {
|
|
@@ -2026,37 +2960,37 @@ function loadSpec(filePath, readFileSync3) {
|
|
|
2026
2960
|
}
|
|
2027
2961
|
function printSummary(diff, baseName, headName, fromCache, log) {
|
|
2028
2962
|
log("");
|
|
2029
|
-
const cacheIndicator = fromCache ?
|
|
2030
|
-
log(
|
|
2963
|
+
const cacheIndicator = fromCache ? chalk8.cyan(" (cached)") : "";
|
|
2964
|
+
log(chalk8.bold(`Comparing: ${baseName} → ${headName}`) + cacheIndicator);
|
|
2031
2965
|
log("─".repeat(40));
|
|
2032
2966
|
log("");
|
|
2033
|
-
const coverageColor = diff.coverageDelta > 0 ?
|
|
2967
|
+
const coverageColor = diff.coverageDelta > 0 ? chalk8.green : diff.coverageDelta < 0 ? chalk8.red : chalk8.gray;
|
|
2034
2968
|
const coverageSign = diff.coverageDelta > 0 ? "+" : "";
|
|
2035
2969
|
log(` Coverage: ${diff.oldCoverage}% → ${diff.newCoverage}% ${coverageColor(`(${coverageSign}${diff.coverageDelta}%)`)}`);
|
|
2036
2970
|
const breakingCount = diff.breaking.length;
|
|
2037
2971
|
const highSeverity = diff.categorizedBreaking?.filter((c) => c.severity === "high").length ?? 0;
|
|
2038
2972
|
if (breakingCount > 0) {
|
|
2039
|
-
const severityNote = highSeverity > 0 ?
|
|
2040
|
-
log(` Breaking: ${
|
|
2973
|
+
const severityNote = highSeverity > 0 ? chalk8.red(` (${highSeverity} high severity)`) : "";
|
|
2974
|
+
log(` Breaking: ${chalk8.red(breakingCount)} changes${severityNote}`);
|
|
2041
2975
|
} else {
|
|
2042
|
-
log(` Breaking: ${
|
|
2976
|
+
log(` Breaking: ${chalk8.green("0")} changes`);
|
|
2043
2977
|
}
|
|
2044
2978
|
const newCount = diff.nonBreaking.length;
|
|
2045
2979
|
const undocCount = diff.newUndocumented.length;
|
|
2046
2980
|
if (newCount > 0) {
|
|
2047
|
-
const undocNote = undocCount > 0 ?
|
|
2048
|
-
log(` New: ${
|
|
2981
|
+
const undocNote = undocCount > 0 ? chalk8.yellow(` (${undocCount} undocumented)`) : "";
|
|
2982
|
+
log(` New: ${chalk8.green(newCount)} exports${undocNote}`);
|
|
2049
2983
|
}
|
|
2050
2984
|
if (diff.driftIntroduced > 0 || diff.driftResolved > 0) {
|
|
2051
2985
|
const parts = [];
|
|
2052
2986
|
if (diff.driftIntroduced > 0)
|
|
2053
|
-
parts.push(
|
|
2987
|
+
parts.push(chalk8.red(`+${diff.driftIntroduced}`));
|
|
2054
2988
|
if (diff.driftResolved > 0)
|
|
2055
|
-
parts.push(
|
|
2989
|
+
parts.push(chalk8.green(`-${diff.driftResolved}`));
|
|
2056
2990
|
log(` Drift: ${parts.join(", ")}`);
|
|
2057
2991
|
}
|
|
2058
2992
|
const recommendation = recommendSemverBump(diff);
|
|
2059
|
-
const bumpColor = recommendation.bump === "major" ?
|
|
2993
|
+
const bumpColor = recommendation.bump === "major" ? chalk8.red : recommendation.bump === "minor" ? chalk8.yellow : chalk8.green;
|
|
2060
2994
|
log(` Semver: ${bumpColor(recommendation.bump.toUpperCase())} (${recommendation.reason})`);
|
|
2061
2995
|
log("");
|
|
2062
2996
|
}
|
|
@@ -2084,8 +3018,8 @@ function validateDiff(diff, headSpec, options) {
|
|
|
2084
3018
|
failures.push(`${diff.newUndocumented.length} undocumented export(s)`);
|
|
2085
3019
|
}
|
|
2086
3020
|
if (checks.has("docs-impact") && hasDocsImpact(diff)) {
|
|
2087
|
-
const
|
|
2088
|
-
failures.push(`${
|
|
3021
|
+
const summary2 = getDocsImpactSummary(diff);
|
|
3022
|
+
failures.push(`${summary2.totalIssues} docs issue(s)`);
|
|
2089
3023
|
}
|
|
2090
3024
|
return failures;
|
|
2091
3025
|
}
|
|
@@ -2138,9 +3072,10 @@ function printGitHubAnnotations(diff, log) {
|
|
|
2138
3072
|
|
|
2139
3073
|
// src/commands/info.ts
|
|
2140
3074
|
import { buildDocCovSpec as buildDocCovSpec2, DocCov as DocCov2, NodeFileSystem as NodeFileSystem2, resolveTarget as resolveTarget2 } from "@doccov/sdk";
|
|
2141
|
-
import
|
|
3075
|
+
import chalk9 from "chalk";
|
|
2142
3076
|
function registerInfoCommand(program) {
|
|
2143
3077
|
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) => {
|
|
3078
|
+
const spin = spinner("Analyzing documentation coverage");
|
|
2144
3079
|
try {
|
|
2145
3080
|
const fileSystem = new NodeFileSystem2(options.cwd);
|
|
2146
3081
|
const resolved = await resolveTarget2(fileSystem, {
|
|
@@ -2155,6 +3090,7 @@ function registerInfoCommand(program) {
|
|
|
2155
3090
|
});
|
|
2156
3091
|
const specResult = await analyzer.analyzeFileWithDiagnostics(entryFile);
|
|
2157
3092
|
if (!specResult) {
|
|
3093
|
+
spin.fail("Failed to analyze");
|
|
2158
3094
|
throw new Error("Failed to analyze documentation coverage.");
|
|
2159
3095
|
}
|
|
2160
3096
|
const openpkg = specResult.spec;
|
|
@@ -2164,15 +3100,17 @@ function registerInfoCommand(program) {
|
|
|
2164
3100
|
packagePath: targetDir
|
|
2165
3101
|
});
|
|
2166
3102
|
const stats = computeStats(openpkg, doccov);
|
|
3103
|
+
spin.success("Analysis complete");
|
|
2167
3104
|
console.log("");
|
|
2168
|
-
console.log(
|
|
3105
|
+
console.log(chalk9.bold(`${stats.packageName}@${stats.version}`));
|
|
2169
3106
|
console.log("");
|
|
2170
|
-
console.log(` Exports: ${
|
|
2171
|
-
console.log(` Coverage: ${
|
|
2172
|
-
console.log(` Drift: ${
|
|
3107
|
+
console.log(` Exports: ${chalk9.bold(stats.totalExports.toString())}`);
|
|
3108
|
+
console.log(` Coverage: ${chalk9.bold(`${stats.coverageScore}%`)}`);
|
|
3109
|
+
console.log(` Drift: ${chalk9.bold(`${stats.driftScore}%`)}`);
|
|
2173
3110
|
console.log("");
|
|
2174
3111
|
} catch (err) {
|
|
2175
|
-
|
|
3112
|
+
spin.fail("Analysis failed");
|
|
3113
|
+
console.error(chalk9.red("Error:"), err instanceof Error ? err.message : err);
|
|
2176
3114
|
process.exit(1);
|
|
2177
3115
|
}
|
|
2178
3116
|
});
|
|
@@ -2181,7 +3119,7 @@ function registerInfoCommand(program) {
|
|
|
2181
3119
|
// src/commands/init.ts
|
|
2182
3120
|
import * as fs5 from "node:fs";
|
|
2183
3121
|
import * as path7 from "node:path";
|
|
2184
|
-
import
|
|
3122
|
+
import chalk10 from "chalk";
|
|
2185
3123
|
var defaultDependencies3 = {
|
|
2186
3124
|
fileExists: fs5.existsSync,
|
|
2187
3125
|
writeFileSync: fs5.writeFileSync,
|
|
@@ -2199,7 +3137,7 @@ function registerInitCommand(program, dependencies = {}) {
|
|
|
2199
3137
|
const cwd = path7.resolve(options.cwd);
|
|
2200
3138
|
const existing = findExistingConfig(cwd, fileExists2);
|
|
2201
3139
|
if (existing) {
|
|
2202
|
-
error(
|
|
3140
|
+
error(chalk10.red(`A DocCov config already exists at ${path7.relative(cwd, existing) || "./doccov.config.*"}.`));
|
|
2203
3141
|
process.exitCode = 1;
|
|
2204
3142
|
return;
|
|
2205
3143
|
}
|
|
@@ -2208,36 +3146,37 @@ function registerInitCommand(program, dependencies = {}) {
|
|
|
2208
3146
|
const fileName = `doccov.config.${targetFormat}`;
|
|
2209
3147
|
const outputPath = path7.join(cwd, fileName);
|
|
2210
3148
|
if (fileExists2(outputPath)) {
|
|
2211
|
-
error(
|
|
3149
|
+
error(chalk10.red(`Cannot create ${fileName}; file already exists.`));
|
|
2212
3150
|
process.exitCode = 1;
|
|
2213
3151
|
return;
|
|
2214
3152
|
}
|
|
3153
|
+
const sym = getSymbols(supportsUnicode());
|
|
2215
3154
|
const template = buildConfigTemplate();
|
|
2216
3155
|
writeFileSync3(outputPath, template, { encoding: "utf8" });
|
|
2217
|
-
log(
|
|
3156
|
+
log(colors.success(`${sym.success} Created ${fileName}`));
|
|
2218
3157
|
if (!options.skipAction) {
|
|
2219
3158
|
const workflowDir = path7.join(cwd, ".github", "workflows");
|
|
2220
3159
|
const workflowPath = path7.join(workflowDir, "doccov.yml");
|
|
2221
3160
|
if (!fileExists2(workflowPath)) {
|
|
2222
3161
|
mkdirSync3(workflowDir, { recursive: true });
|
|
2223
3162
|
writeFileSync3(workflowPath, buildWorkflowTemplate(), { encoding: "utf8" });
|
|
2224
|
-
log(
|
|
3163
|
+
log(colors.success(`${sym.success} Created .github/workflows/doccov.yml`));
|
|
2225
3164
|
} else {
|
|
2226
|
-
log(
|
|
3165
|
+
log(colors.warning(`${sym.bullet} Skipped .github/workflows/doccov.yml (already exists)`));
|
|
2227
3166
|
}
|
|
2228
3167
|
}
|
|
2229
3168
|
const repoInfo = detectRepoInfo(cwd, fileExists2, readFileSync4);
|
|
2230
3169
|
log("");
|
|
2231
|
-
log(
|
|
3170
|
+
log(chalk10.bold("Add this badge to your README:"));
|
|
2232
3171
|
log("");
|
|
2233
3172
|
if (repoInfo) {
|
|
2234
|
-
log(
|
|
3173
|
+
log(chalk10.cyan(`[](https://doccov.dev/${repoInfo.owner}/${repoInfo.repo})`));
|
|
2235
3174
|
} else {
|
|
2236
|
-
log(
|
|
2237
|
-
log(
|
|
3175
|
+
log(chalk10.cyan(`[](https://doccov.dev/OWNER/REPO)`));
|
|
3176
|
+
log(chalk10.dim(" Replace OWNER/REPO with your GitHub repo"));
|
|
2238
3177
|
}
|
|
2239
3178
|
log("");
|
|
2240
|
-
log(
|
|
3179
|
+
log(chalk10.dim("Run `doccov check` to verify your documentation coverage"));
|
|
2241
3180
|
});
|
|
2242
3181
|
}
|
|
2243
3182
|
var findExistingConfig = (cwd, fileExists2) => {
|
|
@@ -2373,9 +3312,9 @@ import {
|
|
|
2373
3312
|
} from "@doccov/sdk";
|
|
2374
3313
|
import { validateDocCovSpec } from "@doccov/spec";
|
|
2375
3314
|
import { normalize, validateSpec } from "@openpkg-ts/spec";
|
|
2376
|
-
import
|
|
3315
|
+
import chalk11 from "chalk";
|
|
2377
3316
|
// package.json
|
|
2378
|
-
var version = "0.25.
|
|
3317
|
+
var version = "0.25.8";
|
|
2379
3318
|
|
|
2380
3319
|
// src/commands/spec.ts
|
|
2381
3320
|
var defaultDependencies4 = {
|
|
@@ -2387,12 +3326,12 @@ var defaultDependencies4 = {
|
|
|
2387
3326
|
function getArrayLength(value) {
|
|
2388
3327
|
return Array.isArray(value) ? value.length : 0;
|
|
2389
3328
|
}
|
|
2390
|
-
function formatDiagnosticOutput(
|
|
3329
|
+
function formatDiagnosticOutput(prefix2, diagnostic, baseDir) {
|
|
2391
3330
|
const location = diagnostic.location;
|
|
2392
3331
|
const relativePath = location?.file ? path8.relative(baseDir, location.file) || location.file : undefined;
|
|
2393
|
-
const locationText = location && relativePath ?
|
|
3332
|
+
const locationText = location && relativePath ? chalk11.gray(`${relativePath}:${location.line ?? 1}:${location.column ?? 1}`) : null;
|
|
2394
3333
|
const locationPrefix = locationText ? `${locationText} ` : "";
|
|
2395
|
-
return `${
|
|
3334
|
+
return `${prefix2} ${locationPrefix}${diagnostic.message}`;
|
|
2396
3335
|
}
|
|
2397
3336
|
function registerSpecCommand(program, dependencies = {}) {
|
|
2398
3337
|
const { createDocCov, writeFileSync: writeFileSync4, log, error } = {
|
|
@@ -2401,18 +3340,7 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
2401
3340
|
};
|
|
2402
3341
|
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) => {
|
|
2403
3342
|
try {
|
|
2404
|
-
const
|
|
2405
|
-
{ label: "Resolved target", activeLabel: "Resolving target" },
|
|
2406
|
-
{ label: "Loaded config", activeLabel: "Loading config" },
|
|
2407
|
-
{ label: "Generated spec", activeLabel: "Generating spec" },
|
|
2408
|
-
{ label: "Validated schema", activeLabel: "Validating schema" }
|
|
2409
|
-
];
|
|
2410
|
-
if (!options.openpkgOnly) {
|
|
2411
|
-
stepsList.push({ label: "Built coverage analysis", activeLabel: "Building coverage" });
|
|
2412
|
-
}
|
|
2413
|
-
stepsList.push({ label: "Wrote output", activeLabel: "Writing output" });
|
|
2414
|
-
const steps = new StepProgress(stepsList);
|
|
2415
|
-
steps.start();
|
|
3343
|
+
const spin = spinner("Generating spec...");
|
|
2416
3344
|
const fileSystem = new NodeFileSystem3(options.cwd);
|
|
2417
3345
|
const resolved = await resolveTarget3(fileSystem, {
|
|
2418
3346
|
cwd: options.cwd,
|
|
@@ -2420,15 +3348,14 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
2420
3348
|
entry
|
|
2421
3349
|
});
|
|
2422
3350
|
const { targetDir, entryFile, packageInfo, entryPointInfo } = resolved;
|
|
2423
|
-
steps.next();
|
|
2424
3351
|
let config = null;
|
|
2425
3352
|
try {
|
|
2426
3353
|
config = await loadDocCovConfig(targetDir);
|
|
2427
3354
|
} catch (configError) {
|
|
2428
|
-
|
|
3355
|
+
spin.fail("Failed to load config");
|
|
3356
|
+
error(chalk11.red("Failed to load DocCov config:"), configError instanceof Error ? configError.message : configError);
|
|
2429
3357
|
process.exit(1);
|
|
2430
3358
|
}
|
|
2431
|
-
steps.next();
|
|
2432
3359
|
const cliFilters = {
|
|
2433
3360
|
include: parseListFlag(options.include),
|
|
2434
3361
|
exclude: parseListFlag(options.exclude),
|
|
@@ -2463,19 +3390,19 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
2463
3390
|
} : { generationInput };
|
|
2464
3391
|
const result = await doccov.analyzeFileWithDiagnostics(entryFile, analyzeOptions);
|
|
2465
3392
|
if (!result) {
|
|
3393
|
+
spin.fail("Generation failed");
|
|
2466
3394
|
throw new Error("Failed to produce an OpenPkg spec.");
|
|
2467
3395
|
}
|
|
2468
|
-
steps.next();
|
|
2469
3396
|
const normalized = normalize(result.spec);
|
|
2470
3397
|
const validation = validateSpec(normalized);
|
|
2471
3398
|
if (!validation.ok) {
|
|
2472
|
-
|
|
3399
|
+
spin.fail("Validation failed");
|
|
3400
|
+
error(chalk11.red("Spec failed schema validation"));
|
|
2473
3401
|
for (const err of validation.errors) {
|
|
2474
|
-
error(
|
|
3402
|
+
error(chalk11.red(`schema: ${err.instancePath || "/"} ${err.message}`));
|
|
2475
3403
|
}
|
|
2476
3404
|
process.exit(1);
|
|
2477
3405
|
}
|
|
2478
|
-
steps.next();
|
|
2479
3406
|
let doccovSpec = null;
|
|
2480
3407
|
if (!options.openpkgOnly) {
|
|
2481
3408
|
doccovSpec = buildDocCovSpec3({
|
|
@@ -2485,13 +3412,13 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
2485
3412
|
});
|
|
2486
3413
|
const doccovValidation = validateDocCovSpec(doccovSpec);
|
|
2487
3414
|
if (!doccovValidation.ok) {
|
|
2488
|
-
|
|
3415
|
+
spin.fail("DocCov validation failed");
|
|
3416
|
+
error(chalk11.red("DocCov spec failed schema validation"));
|
|
2489
3417
|
for (const err of doccovValidation.errors) {
|
|
2490
|
-
error(
|
|
3418
|
+
error(chalk11.red(`doccov: ${err.instancePath || "/"} ${err.message}`));
|
|
2491
3419
|
}
|
|
2492
3420
|
process.exit(1);
|
|
2493
3421
|
}
|
|
2494
|
-
steps.next();
|
|
2495
3422
|
}
|
|
2496
3423
|
const format = options.format ?? "json";
|
|
2497
3424
|
const outputDir = path8.resolve(options.cwd, options.output);
|
|
@@ -2500,20 +3427,20 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
2500
3427
|
const apiSurface = renderApiSurface(normalized);
|
|
2501
3428
|
const apiSurfacePath = path8.join(outputDir, "api-surface.txt");
|
|
2502
3429
|
writeFileSync4(apiSurfacePath, apiSurface);
|
|
2503
|
-
|
|
3430
|
+
spin.success(`Generated ${options.output}/ (API surface)`);
|
|
2504
3431
|
} else {
|
|
2505
3432
|
const openpkgPath = path8.join(outputDir, "openpkg.json");
|
|
2506
3433
|
writeFileSync4(openpkgPath, JSON.stringify(normalized, null, 2));
|
|
2507
3434
|
if (doccovSpec) {
|
|
2508
3435
|
const doccovPath = path8.join(outputDir, "doccov.json");
|
|
2509
3436
|
writeFileSync4(doccovPath, JSON.stringify(doccovSpec, null, 2));
|
|
2510
|
-
|
|
2511
|
-
log(
|
|
2512
|
-
log(
|
|
3437
|
+
spin.success(`Generated ${options.output}/`);
|
|
3438
|
+
log(chalk11.gray(` openpkg.json: ${getArrayLength(normalized.exports)} exports`));
|
|
3439
|
+
log(chalk11.gray(` doccov.json: ${doccovSpec.summary.score}% coverage, ${doccovSpec.summary.drift.total} drift issues`));
|
|
2513
3440
|
} else {
|
|
2514
|
-
|
|
2515
|
-
log(
|
|
2516
|
-
log(
|
|
3441
|
+
spin.success(`Generated ${options.output}/openpkg.json`);
|
|
3442
|
+
log(chalk11.gray(` ${getArrayLength(normalized.exports)} exports`));
|
|
3443
|
+
log(chalk11.gray(` ${getArrayLength(normalized.types)} types`));
|
|
2517
3444
|
}
|
|
2518
3445
|
}
|
|
2519
3446
|
const schemaExtraction = normalized.generation?.analysis?.schemaExtraction;
|
|
@@ -2521,63 +3448,63 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
2521
3448
|
const pm = await detectPackageManager(fileSystem);
|
|
2522
3449
|
const buildCmd = pm.name === "npm" ? "npm run build" : `${pm.name} run build`;
|
|
2523
3450
|
log("");
|
|
2524
|
-
log(
|
|
2525
|
-
log(
|
|
3451
|
+
log(chalk11.yellow("⚠ Runtime extraction requested but no schemas extracted."));
|
|
3452
|
+
log(chalk11.yellow(` Ensure project is built (${buildCmd}) and dist/ exists.`));
|
|
2526
3453
|
}
|
|
2527
3454
|
if (options.verbose && normalized.generation) {
|
|
2528
3455
|
const gen = normalized.generation;
|
|
2529
3456
|
log("");
|
|
2530
|
-
log(
|
|
2531
|
-
log(
|
|
2532
|
-
log(
|
|
2533
|
-
log(
|
|
2534
|
-
log(
|
|
2535
|
-
log(
|
|
2536
|
-
log(
|
|
3457
|
+
log(chalk11.bold("Generation Info"));
|
|
3458
|
+
log(chalk11.gray(` Timestamp: ${gen.timestamp}`));
|
|
3459
|
+
log(chalk11.gray(` Generator: ${gen.generator.name}@${gen.generator.version}`));
|
|
3460
|
+
log(chalk11.gray(` Entry point: ${gen.analysis.entryPoint}`));
|
|
3461
|
+
log(chalk11.gray(` Detected via: ${gen.analysis.entryPointSource}`));
|
|
3462
|
+
log(chalk11.gray(` Declaration only: ${gen.analysis.isDeclarationOnly ? "yes" : "no"}`));
|
|
3463
|
+
log(chalk11.gray(` External types: ${gen.analysis.resolvedExternalTypes ? "resolved" : "skipped"}`));
|
|
2537
3464
|
if (gen.analysis.maxTypeDepth) {
|
|
2538
|
-
log(
|
|
3465
|
+
log(chalk11.gray(` Max type depth: ${gen.analysis.maxTypeDepth}`));
|
|
2539
3466
|
}
|
|
2540
3467
|
if (gen.analysis.schemaExtraction) {
|
|
2541
3468
|
const se = gen.analysis.schemaExtraction;
|
|
2542
|
-
log(
|
|
3469
|
+
log(chalk11.gray(` Schema extraction: ${se.method}`));
|
|
2543
3470
|
if (se.runtimeCount) {
|
|
2544
|
-
log(
|
|
3471
|
+
log(chalk11.gray(` Runtime schemas: ${se.runtimeCount} (${se.vendors?.join(", ")})`));
|
|
2545
3472
|
}
|
|
2546
3473
|
}
|
|
2547
3474
|
log("");
|
|
2548
|
-
log(
|
|
2549
|
-
log(
|
|
3475
|
+
log(chalk11.bold("Environment"));
|
|
3476
|
+
log(chalk11.gray(` node_modules: ${gen.environment.hasNodeModules ? "found" : "not found"}`));
|
|
2550
3477
|
if (gen.environment.packageManager) {
|
|
2551
|
-
log(
|
|
3478
|
+
log(chalk11.gray(` Package manager: ${gen.environment.packageManager}`));
|
|
2552
3479
|
}
|
|
2553
3480
|
if (gen.environment.isMonorepo) {
|
|
2554
|
-
log(
|
|
3481
|
+
log(chalk11.gray(` Monorepo: yes`));
|
|
2555
3482
|
}
|
|
2556
3483
|
if (gen.environment.targetPackage) {
|
|
2557
|
-
log(
|
|
3484
|
+
log(chalk11.gray(` Target package: ${gen.environment.targetPackage}`));
|
|
2558
3485
|
}
|
|
2559
3486
|
if (gen.issues.length > 0) {
|
|
2560
3487
|
log("");
|
|
2561
|
-
log(
|
|
3488
|
+
log(chalk11.bold("Issues"));
|
|
2562
3489
|
for (const issue of gen.issues) {
|
|
2563
|
-
const
|
|
2564
|
-
log(`${
|
|
3490
|
+
const prefix2 = issue.severity === "error" ? chalk11.red(">") : issue.severity === "warning" ? chalk11.yellow(">") : chalk11.cyan(">");
|
|
3491
|
+
log(`${prefix2} [${issue.code}] ${issue.message}`);
|
|
2565
3492
|
if (issue.suggestion) {
|
|
2566
|
-
log(
|
|
3493
|
+
log(chalk11.gray(` ${issue.suggestion}`));
|
|
2567
3494
|
}
|
|
2568
3495
|
}
|
|
2569
3496
|
}
|
|
2570
3497
|
}
|
|
2571
3498
|
if (options.showDiagnostics && result.diagnostics.length > 0) {
|
|
2572
3499
|
log("");
|
|
2573
|
-
log(
|
|
3500
|
+
log(chalk11.bold("Diagnostics"));
|
|
2574
3501
|
for (const diagnostic of result.diagnostics) {
|
|
2575
|
-
const
|
|
2576
|
-
log(formatDiagnosticOutput(
|
|
3502
|
+
const prefix2 = diagnostic.severity === "error" ? chalk11.red(">") : diagnostic.severity === "warning" ? chalk11.yellow(">") : chalk11.cyan(">");
|
|
3503
|
+
log(formatDiagnosticOutput(prefix2, diagnostic, targetDir));
|
|
2577
3504
|
}
|
|
2578
3505
|
}
|
|
2579
3506
|
} catch (commandError) {
|
|
2580
|
-
error(
|
|
3507
|
+
error(chalk11.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
|
|
2581
3508
|
process.exit(1);
|
|
2582
3509
|
}
|
|
2583
3510
|
});
|
|
@@ -2597,7 +3524,7 @@ import {
|
|
|
2597
3524
|
renderSparkline,
|
|
2598
3525
|
saveSnapshot
|
|
2599
3526
|
} from "@doccov/sdk";
|
|
2600
|
-
import
|
|
3527
|
+
import chalk12 from "chalk";
|
|
2601
3528
|
function formatDate(timestamp) {
|
|
2602
3529
|
const date = new Date(timestamp);
|
|
2603
3530
|
return date.toLocaleDateString("en-US", {
|
|
@@ -2610,19 +3537,19 @@ function formatDate(timestamp) {
|
|
|
2610
3537
|
}
|
|
2611
3538
|
function getColorForScore(score) {
|
|
2612
3539
|
if (score >= 90)
|
|
2613
|
-
return
|
|
3540
|
+
return chalk12.green;
|
|
2614
3541
|
if (score >= 70)
|
|
2615
|
-
return
|
|
3542
|
+
return chalk12.yellow;
|
|
2616
3543
|
if (score >= 50)
|
|
2617
|
-
return
|
|
2618
|
-
return
|
|
3544
|
+
return chalk12.hex("#FFA500");
|
|
3545
|
+
return chalk12.red;
|
|
2619
3546
|
}
|
|
2620
3547
|
function formatSnapshot(snapshot) {
|
|
2621
3548
|
const color = getColorForScore(snapshot.coverageScore);
|
|
2622
3549
|
const date = formatDate(snapshot.timestamp);
|
|
2623
3550
|
const version2 = snapshot.version ? ` v${snapshot.version}` : "";
|
|
2624
|
-
const commit = snapshot.commit ?
|
|
2625
|
-
return `${
|
|
3551
|
+
const commit = snapshot.commit ? chalk12.gray(` (${snapshot.commit.slice(0, 7)})`) : "";
|
|
3552
|
+
return `${chalk12.gray(date)} ${color(`${snapshot.coverageScore}%`)} ${snapshot.documentedExports}/${snapshot.totalExports} exports${version2}${commit}`;
|
|
2626
3553
|
}
|
|
2627
3554
|
function formatWeekDate(timestamp) {
|
|
2628
3555
|
const date = new Date(timestamp);
|
|
@@ -2630,10 +3557,10 @@ function formatWeekDate(timestamp) {
|
|
|
2630
3557
|
}
|
|
2631
3558
|
function formatVelocity(velocity) {
|
|
2632
3559
|
if (velocity > 0)
|
|
2633
|
-
return
|
|
3560
|
+
return chalk12.green(`+${velocity}%/day`);
|
|
2634
3561
|
if (velocity < 0)
|
|
2635
|
-
return
|
|
2636
|
-
return
|
|
3562
|
+
return chalk12.red(`${velocity}%/day`);
|
|
3563
|
+
return chalk12.gray("0%/day");
|
|
2637
3564
|
}
|
|
2638
3565
|
function registerTrendsCommand(program) {
|
|
2639
3566
|
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("--tier <tier>", "Retention tier: free (7d), team (30d), pro (90d)", "pro").option("--weekly", "Show weekly summary breakdown").action(async (options) => {
|
|
@@ -2643,42 +3570,44 @@ function registerTrendsCommand(program) {
|
|
|
2643
3570
|
const keepCount = parseInt(options.prune, 10);
|
|
2644
3571
|
if (!Number.isNaN(keepCount)) {
|
|
2645
3572
|
const deleted = pruneHistory(cwd, keepCount);
|
|
2646
|
-
console.log(
|
|
3573
|
+
console.log(chalk12.green(`Pruned ${deleted} old snapshots, kept ${keepCount} most recent`));
|
|
2647
3574
|
} else {
|
|
2648
3575
|
const deleted = pruneByTier(cwd, tier);
|
|
2649
|
-
console.log(
|
|
3576
|
+
console.log(chalk12.green(`Pruned ${deleted} snapshots older than ${RETENTION_DAYS[tier]} days`));
|
|
2650
3577
|
}
|
|
2651
3578
|
return;
|
|
2652
3579
|
}
|
|
2653
3580
|
if (options.record) {
|
|
2654
3581
|
const specPath = path9.resolve(cwd, "openpkg.json");
|
|
2655
3582
|
if (!fs7.existsSync(specPath)) {
|
|
2656
|
-
console.error(
|
|
3583
|
+
console.error(chalk12.red("No openpkg.json found. Run `doccov spec` first to generate a spec."));
|
|
2657
3584
|
process.exit(1);
|
|
2658
3585
|
}
|
|
3586
|
+
const spin = spinner("Recording coverage snapshot");
|
|
2659
3587
|
try {
|
|
2660
3588
|
const specContent = fs7.readFileSync(specPath, "utf-8");
|
|
2661
3589
|
const spec = JSON.parse(specContent);
|
|
2662
3590
|
const trend = getTrend(spec, cwd);
|
|
2663
3591
|
saveSnapshot(trend.current, cwd);
|
|
2664
|
-
|
|
3592
|
+
spin.success("Recorded coverage snapshot");
|
|
2665
3593
|
console.log(formatSnapshot(trend.current));
|
|
2666
3594
|
if (trend.delta !== undefined) {
|
|
2667
3595
|
const deltaStr = formatDelta2(trend.delta);
|
|
2668
|
-
const deltaColor = trend.delta > 0 ?
|
|
2669
|
-
console.log(
|
|
3596
|
+
const deltaColor = trend.delta > 0 ? chalk12.green : trend.delta < 0 ? chalk12.red : chalk12.gray;
|
|
3597
|
+
console.log(chalk12.gray("Change from previous:"), deltaColor(deltaStr));
|
|
2670
3598
|
}
|
|
2671
3599
|
return;
|
|
2672
3600
|
} catch (error) {
|
|
2673
|
-
|
|
3601
|
+
spin.fail("Failed to record snapshot");
|
|
3602
|
+
console.error(chalk12.red("Failed to read openpkg.json:"), error instanceof Error ? error.message : error);
|
|
2674
3603
|
process.exit(1);
|
|
2675
3604
|
}
|
|
2676
3605
|
}
|
|
2677
3606
|
const snapshots = loadSnapshots(cwd);
|
|
2678
3607
|
const limit = parseInt(options.limit ?? "10", 10);
|
|
2679
3608
|
if (snapshots.length === 0) {
|
|
2680
|
-
console.log(
|
|
2681
|
-
console.log(
|
|
3609
|
+
console.log(chalk12.yellow("No coverage history found."));
|
|
3610
|
+
console.log(chalk12.gray("Run `doccov trends --record` to save the current coverage."));
|
|
2682
3611
|
return;
|
|
2683
3612
|
}
|
|
2684
3613
|
if (options.json) {
|
|
@@ -2693,17 +3622,17 @@ function registerTrendsCommand(program) {
|
|
|
2693
3622
|
}
|
|
2694
3623
|
const sparklineData = snapshots.slice(0, 10).map((s) => s.coverageScore).reverse();
|
|
2695
3624
|
const sparkline = renderSparkline(sparklineData);
|
|
2696
|
-
console.log(
|
|
2697
|
-
console.log(
|
|
2698
|
-
console.log(
|
|
3625
|
+
console.log(chalk12.bold("Coverage Trends"));
|
|
3626
|
+
console.log(chalk12.gray(`Package: ${snapshots[0].package}`));
|
|
3627
|
+
console.log(chalk12.gray(`Sparkline: ${sparkline}`));
|
|
2699
3628
|
console.log("");
|
|
2700
3629
|
if (snapshots.length >= 2) {
|
|
2701
3630
|
const oldest = snapshots[snapshots.length - 1];
|
|
2702
3631
|
const newest = snapshots[0];
|
|
2703
3632
|
const overallDelta = newest.coverageScore - oldest.coverageScore;
|
|
2704
3633
|
const deltaStr = formatDelta2(overallDelta);
|
|
2705
|
-
const deltaColor = overallDelta > 0 ?
|
|
2706
|
-
console.log(
|
|
3634
|
+
const deltaColor = overallDelta > 0 ? chalk12.green : overallDelta < 0 ? chalk12.red : chalk12.gray;
|
|
3635
|
+
console.log(chalk12.gray("Overall trend:"), deltaColor(deltaStr), chalk12.gray(`(${snapshots.length} snapshots)`));
|
|
2707
3636
|
console.log("");
|
|
2708
3637
|
}
|
|
2709
3638
|
if (options.extended) {
|
|
@@ -2713,8 +3642,8 @@ function registerTrendsCommand(program) {
|
|
|
2713
3642
|
const specContent = fs7.readFileSync(specPath, "utf-8");
|
|
2714
3643
|
const spec = JSON.parse(specContent);
|
|
2715
3644
|
const extended = getExtendedTrend(spec, cwd, { tier });
|
|
2716
|
-
console.log(
|
|
2717
|
-
console.log(
|
|
3645
|
+
console.log(chalk12.bold("Extended Analysis"));
|
|
3646
|
+
console.log(chalk12.gray(`Tier: ${tier} (${RETENTION_DAYS[tier]}-day retention)`));
|
|
2718
3647
|
console.log("");
|
|
2719
3648
|
console.log(" Velocity:");
|
|
2720
3649
|
console.log(` 7-day: ${formatVelocity(extended.velocity7d)}`);
|
|
@@ -2723,47 +3652,47 @@ function registerTrendsCommand(program) {
|
|
|
2723
3652
|
console.log(` 90-day: ${formatVelocity(extended.velocity90d)}`);
|
|
2724
3653
|
}
|
|
2725
3654
|
console.log("");
|
|
2726
|
-
const projColor = extended.projected30d >= extended.trend.current.coverageScore ?
|
|
3655
|
+
const projColor = extended.projected30d >= extended.trend.current.coverageScore ? chalk12.green : chalk12.red;
|
|
2727
3656
|
console.log(` Projected (30d): ${projColor(`${extended.projected30d}%`)}`);
|
|
2728
|
-
console.log(` All-time high: ${
|
|
2729
|
-
console.log(` All-time low: ${
|
|
3657
|
+
console.log(` All-time high: ${chalk12.green(`${extended.allTimeHigh}%`)}`);
|
|
3658
|
+
console.log(` All-time low: ${chalk12.red(`${extended.allTimeLow}%`)}`);
|
|
2730
3659
|
if (extended.dataRange) {
|
|
2731
3660
|
const startDate = formatWeekDate(extended.dataRange.start);
|
|
2732
3661
|
const endDate = formatWeekDate(extended.dataRange.end);
|
|
2733
|
-
console.log(
|
|
3662
|
+
console.log(chalk12.gray(` Data range: ${startDate} - ${endDate}`));
|
|
2734
3663
|
}
|
|
2735
3664
|
console.log("");
|
|
2736
3665
|
if (options.weekly && extended.weeklySummaries.length > 0) {
|
|
2737
|
-
console.log(
|
|
3666
|
+
console.log(chalk12.bold("Weekly Summary"));
|
|
2738
3667
|
const weekLimit = Math.min(extended.weeklySummaries.length, 8);
|
|
2739
3668
|
for (let i = 0;i < weekLimit; i++) {
|
|
2740
3669
|
const week = extended.weeklySummaries[i];
|
|
2741
3670
|
const weekStart = formatWeekDate(week.weekStart);
|
|
2742
3671
|
const weekEnd = formatWeekDate(week.weekEnd);
|
|
2743
|
-
const deltaColor = week.delta > 0 ?
|
|
3672
|
+
const deltaColor = week.delta > 0 ? chalk12.green : week.delta < 0 ? chalk12.red : chalk12.gray;
|
|
2744
3673
|
const deltaStr = week.delta > 0 ? `+${week.delta}%` : `${week.delta}%`;
|
|
2745
3674
|
console.log(` ${weekStart} - ${weekEnd}: ${week.avgCoverage}% avg ${deltaColor(deltaStr)} (${week.snapshotCount} snapshots)`);
|
|
2746
3675
|
}
|
|
2747
3676
|
if (extended.weeklySummaries.length > weekLimit) {
|
|
2748
|
-
console.log(
|
|
3677
|
+
console.log(chalk12.gray(` ... and ${extended.weeklySummaries.length - weekLimit} more weeks`));
|
|
2749
3678
|
}
|
|
2750
3679
|
console.log("");
|
|
2751
3680
|
}
|
|
2752
3681
|
} catch {
|
|
2753
|
-
console.log(
|
|
3682
|
+
console.log(chalk12.yellow("Could not load openpkg.json for extended analysis"));
|
|
2754
3683
|
console.log("");
|
|
2755
3684
|
}
|
|
2756
3685
|
}
|
|
2757
3686
|
}
|
|
2758
|
-
console.log(
|
|
3687
|
+
console.log(chalk12.bold("History"));
|
|
2759
3688
|
const displaySnapshots = snapshots.slice(0, limit);
|
|
2760
3689
|
for (let i = 0;i < displaySnapshots.length; i++) {
|
|
2761
3690
|
const snapshot = displaySnapshots[i];
|
|
2762
|
-
const
|
|
2763
|
-
console.log(`${
|
|
3691
|
+
const prefix2 = i === 0 ? chalk12.cyan("→") : " ";
|
|
3692
|
+
console.log(`${prefix2} ${formatSnapshot(snapshot)}`);
|
|
2764
3693
|
}
|
|
2765
3694
|
if (snapshots.length > limit) {
|
|
2766
|
-
console.log(
|
|
3695
|
+
console.log(chalk12.gray(` ... and ${snapshots.length - limit} more`));
|
|
2767
3696
|
}
|
|
2768
3697
|
});
|
|
2769
3698
|
}
|