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