@doccov/cli 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +829 -655
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -147,36 +147,358 @@ ${formatIssues(issues)}`);
|
|
|
147
147
|
var defineConfig = (config) => config;
|
|
148
148
|
// src/cli.ts
|
|
149
149
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
150
|
-
import * as
|
|
150
|
+
import * as path10 from "node:path";
|
|
151
151
|
import { fileURLToPath } from "node:url";
|
|
152
152
|
import { Command } from "commander";
|
|
153
153
|
|
|
154
|
-
// src/commands/check.ts
|
|
154
|
+
// src/commands/check/index.ts
|
|
155
|
+
import {
|
|
156
|
+
DocCov,
|
|
157
|
+
enrichSpec,
|
|
158
|
+
NodeFileSystem,
|
|
159
|
+
parseExamplesFlag,
|
|
160
|
+
resolveTarget
|
|
161
|
+
} from "@doccov/sdk";
|
|
162
|
+
import chalk6 from "chalk";
|
|
163
|
+
|
|
164
|
+
// src/utils/filter-options.ts
|
|
165
|
+
import { mergeFilters, parseListFlag } from "@doccov/sdk";
|
|
166
|
+
import chalk from "chalk";
|
|
167
|
+
var parseVisibilityFlag = (value) => {
|
|
168
|
+
if (!value)
|
|
169
|
+
return;
|
|
170
|
+
const validTags = ["public", "beta", "alpha", "internal"];
|
|
171
|
+
const parsed = parseListFlag(value);
|
|
172
|
+
if (!parsed)
|
|
173
|
+
return;
|
|
174
|
+
const result = [];
|
|
175
|
+
for (const tag of parsed) {
|
|
176
|
+
const lower = tag.toLowerCase();
|
|
177
|
+
if (validTags.includes(lower)) {
|
|
178
|
+
result.push(lower);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return result.length > 0 ? result : undefined;
|
|
182
|
+
};
|
|
183
|
+
var formatList = (label, values) => `${label}: ${values.map((value) => chalk.cyan(value)).join(", ")}`;
|
|
184
|
+
var mergeFilterOptions = (config, cliOptions) => {
|
|
185
|
+
const messages = [];
|
|
186
|
+
if (config?.include) {
|
|
187
|
+
messages.push(formatList("include filters from config", config.include));
|
|
188
|
+
}
|
|
189
|
+
if (config?.exclude) {
|
|
190
|
+
messages.push(formatList("exclude filters from config", config.exclude));
|
|
191
|
+
}
|
|
192
|
+
if (cliOptions.include) {
|
|
193
|
+
messages.push(formatList("apply include filters from CLI", cliOptions.include));
|
|
194
|
+
}
|
|
195
|
+
if (cliOptions.exclude) {
|
|
196
|
+
messages.push(formatList("apply exclude filters from CLI", cliOptions.exclude));
|
|
197
|
+
}
|
|
198
|
+
if (cliOptions.visibility) {
|
|
199
|
+
messages.push(formatList("apply visibility filter from CLI", cliOptions.visibility));
|
|
200
|
+
}
|
|
201
|
+
const resolved = mergeFilters(config, cliOptions);
|
|
202
|
+
if (!resolved.include && !resolved.exclude && !cliOptions.visibility) {
|
|
203
|
+
return { messages };
|
|
204
|
+
}
|
|
205
|
+
const source = resolved.source === "override" ? "cli" : resolved.source;
|
|
206
|
+
return {
|
|
207
|
+
include: resolved.include,
|
|
208
|
+
exclude: resolved.exclude,
|
|
209
|
+
visibility: cliOptions.visibility,
|
|
210
|
+
source,
|
|
211
|
+
messages
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
|
|
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
|
+
// src/utils/validation.ts
|
|
267
|
+
function clampPercentage(value, fallback = 80) {
|
|
268
|
+
if (Number.isNaN(value))
|
|
269
|
+
return fallback;
|
|
270
|
+
return Math.min(100, Math.max(0, Math.round(value)));
|
|
271
|
+
}
|
|
272
|
+
function resolveThreshold(cliValue, configValue) {
|
|
273
|
+
const raw = cliValue ?? configValue;
|
|
274
|
+
return raw !== undefined ? clampPercentage(Number(raw)) : undefined;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/commands/check/fix-handler.ts
|
|
155
278
|
import * as fs2 from "node:fs";
|
|
156
|
-
import * as
|
|
279
|
+
import * as path3 from "node:path";
|
|
157
280
|
import {
|
|
158
281
|
applyEdits,
|
|
159
282
|
categorizeDrifts,
|
|
160
283
|
createSourceFile,
|
|
161
|
-
DocCov,
|
|
162
|
-
enrichSpec,
|
|
163
284
|
findJSDocLocation,
|
|
164
285
|
generateFixesForExport,
|
|
165
|
-
generateReport,
|
|
166
286
|
mergeFixes,
|
|
167
|
-
NodeFileSystem,
|
|
168
|
-
parseExamplesFlag,
|
|
169
287
|
parseJSDocToPatch,
|
|
170
|
-
|
|
171
|
-
resolveTarget,
|
|
172
|
-
serializeJSDoc,
|
|
173
|
-
validateExamples
|
|
288
|
+
serializeJSDoc
|
|
174
289
|
} from "@doccov/sdk";
|
|
175
|
-
import
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
import
|
|
290
|
+
import chalk3 from "chalk";
|
|
291
|
+
|
|
292
|
+
// src/commands/check/utils.ts
|
|
293
|
+
import * as fs from "node:fs";
|
|
294
|
+
import * as path2 from "node:path";
|
|
295
|
+
import { parseMarkdownFiles } from "@doccov/sdk";
|
|
296
|
+
import { DRIFT_CATEGORIES } from "@openpkg-ts/spec";
|
|
179
297
|
import { glob } from "glob";
|
|
298
|
+
function collectDriftsFromExports(exports) {
|
|
299
|
+
const results = [];
|
|
300
|
+
for (const exp of exports) {
|
|
301
|
+
for (const drift of exp.docs?.drift ?? []) {
|
|
302
|
+
results.push({ export: exp, drift });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return results;
|
|
306
|
+
}
|
|
307
|
+
function groupByExport(drifts) {
|
|
308
|
+
const map = new Map;
|
|
309
|
+
for (const { export: exp, drift } of drifts) {
|
|
310
|
+
const existing = map.get(exp) ?? [];
|
|
311
|
+
existing.push(drift);
|
|
312
|
+
map.set(exp, existing);
|
|
313
|
+
}
|
|
314
|
+
return map;
|
|
315
|
+
}
|
|
316
|
+
function collectDrift(exportsList) {
|
|
317
|
+
const drifts = [];
|
|
318
|
+
for (const entry of exportsList) {
|
|
319
|
+
const drift = entry.docs?.drift;
|
|
320
|
+
if (!drift || drift.length === 0) {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
for (const d of drift) {
|
|
324
|
+
drifts.push({
|
|
325
|
+
name: entry.name,
|
|
326
|
+
type: d.type,
|
|
327
|
+
issue: d.issue ?? "Documentation drift detected.",
|
|
328
|
+
suggestion: d.suggestion,
|
|
329
|
+
category: DRIFT_CATEGORIES[d.type]
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return drifts;
|
|
334
|
+
}
|
|
335
|
+
function collect(value, previous) {
|
|
336
|
+
return previous.concat([value]);
|
|
337
|
+
}
|
|
338
|
+
async function loadMarkdownFiles(patterns, cwd) {
|
|
339
|
+
const files = [];
|
|
340
|
+
for (const pattern of patterns) {
|
|
341
|
+
const matches = await glob(pattern, { nodir: true, cwd });
|
|
342
|
+
for (const filePath of matches) {
|
|
343
|
+
try {
|
|
344
|
+
const fullPath = path2.resolve(cwd, filePath);
|
|
345
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
346
|
+
files.push({ path: filePath, content });
|
|
347
|
+
} catch {}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return parseMarkdownFiles(files);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/commands/check/fix-handler.ts
|
|
354
|
+
async function handleFixes(spec, options, deps) {
|
|
355
|
+
const { isPreview, targetDir } = options;
|
|
356
|
+
const { log, error } = deps;
|
|
357
|
+
const fixedDriftKeys = new Set;
|
|
358
|
+
const allDrifts = collectDriftsFromExports(spec.exports ?? []);
|
|
359
|
+
if (allDrifts.length === 0) {
|
|
360
|
+
return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
|
|
361
|
+
}
|
|
362
|
+
const { fixable, nonFixable } = categorizeDrifts(allDrifts.map((d) => d.drift));
|
|
363
|
+
if (fixable.length === 0) {
|
|
364
|
+
log(chalk3.yellow(`Found ${nonFixable.length} drift issue(s), but none are auto-fixable.`));
|
|
365
|
+
return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
|
|
366
|
+
}
|
|
367
|
+
log("");
|
|
368
|
+
log(chalk3.bold(`Found ${fixable.length} fixable issue(s)`));
|
|
369
|
+
if (nonFixable.length > 0) {
|
|
370
|
+
log(chalk3.gray(`(${nonFixable.length} non-fixable issue(s) skipped)`));
|
|
371
|
+
}
|
|
372
|
+
log("");
|
|
373
|
+
const groupedDrifts = groupByExport(allDrifts.filter((d) => fixable.includes(d.drift)));
|
|
374
|
+
const edits = [];
|
|
375
|
+
const editsByFile = new Map;
|
|
376
|
+
for (const [exp, drifts] of groupedDrifts) {
|
|
377
|
+
const edit = generateEditForExport(exp, drifts, targetDir, log);
|
|
378
|
+
if (!edit)
|
|
379
|
+
continue;
|
|
380
|
+
for (const drift of drifts) {
|
|
381
|
+
fixedDriftKeys.add(`${exp.name}:${drift.issue}`);
|
|
382
|
+
}
|
|
383
|
+
edits.push(edit.edit);
|
|
384
|
+
const fileEdits = editsByFile.get(edit.filePath) ?? [];
|
|
385
|
+
fileEdits.push({
|
|
386
|
+
export: exp,
|
|
387
|
+
edit: edit.edit,
|
|
388
|
+
fixes: edit.fixes,
|
|
389
|
+
existingPatch: edit.existingPatch
|
|
390
|
+
});
|
|
391
|
+
editsByFile.set(edit.filePath, fileEdits);
|
|
392
|
+
}
|
|
393
|
+
if (edits.length === 0) {
|
|
394
|
+
return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
|
|
395
|
+
}
|
|
396
|
+
if (isPreview) {
|
|
397
|
+
displayPreview(editsByFile, targetDir, log);
|
|
398
|
+
return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
|
|
399
|
+
}
|
|
400
|
+
const applyResult = await applyEdits(edits);
|
|
401
|
+
if (applyResult.errors.length > 0) {
|
|
402
|
+
for (const err of applyResult.errors) {
|
|
403
|
+
error(chalk3.red(` ${err.file}: ${err.error}`));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const totalFixes = Array.from(editsByFile.values()).reduce((sum, fileEdits) => sum + fileEdits.reduce((s, e) => s + e.fixes.length, 0), 0);
|
|
407
|
+
log("");
|
|
408
|
+
log(chalk3.green(`✓ Applied ${totalFixes} fix(es) to ${applyResult.filesModified} file(s)`));
|
|
409
|
+
for (const [filePath, fileEdits] of editsByFile) {
|
|
410
|
+
const relativePath = path3.relative(targetDir, filePath);
|
|
411
|
+
const fixCount = fileEdits.reduce((s, e) => s + e.fixes.length, 0);
|
|
412
|
+
log(chalk3.dim(` ${relativePath} (${fixCount} fixes)`));
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
fixedDriftKeys,
|
|
416
|
+
editsApplied: totalFixes,
|
|
417
|
+
filesModified: applyResult.filesModified
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
function generateEditForExport(exp, drifts, targetDir, log) {
|
|
421
|
+
if (!exp.source?.file) {
|
|
422
|
+
log(chalk3.gray(` Skipping ${exp.name}: no source location`));
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
if (exp.source.file.endsWith(".d.ts")) {
|
|
426
|
+
log(chalk3.gray(` Skipping ${exp.name}: declaration file`));
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
const filePath = path3.resolve(targetDir, exp.source.file);
|
|
430
|
+
if (!fs2.existsSync(filePath)) {
|
|
431
|
+
log(chalk3.gray(` Skipping ${exp.name}: file not found`));
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
const sourceFile = createSourceFile(filePath);
|
|
435
|
+
const location = findJSDocLocation(sourceFile, exp.name, exp.source.line);
|
|
436
|
+
if (!location) {
|
|
437
|
+
log(chalk3.gray(` Skipping ${exp.name}: could not find declaration`));
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
let existingPatch = {};
|
|
441
|
+
if (location.hasExisting && location.existingJSDoc) {
|
|
442
|
+
existingPatch = parseJSDocToPatch(location.existingJSDoc);
|
|
443
|
+
}
|
|
444
|
+
const expWithDrift = { ...exp, docs: { ...exp.docs, drift: drifts } };
|
|
445
|
+
const fixes = generateFixesForExport(expWithDrift, existingPatch);
|
|
446
|
+
if (fixes.length === 0)
|
|
447
|
+
return null;
|
|
448
|
+
const mergedPatch = mergeFixes(fixes, existingPatch);
|
|
449
|
+
const newJSDoc = serializeJSDoc(mergedPatch, location.indent);
|
|
450
|
+
const edit = {
|
|
451
|
+
filePath,
|
|
452
|
+
symbolName: exp.name,
|
|
453
|
+
startLine: location.startLine,
|
|
454
|
+
endLine: location.endLine,
|
|
455
|
+
hasExisting: location.hasExisting,
|
|
456
|
+
existingJSDoc: location.existingJSDoc,
|
|
457
|
+
newJSDoc,
|
|
458
|
+
indent: location.indent
|
|
459
|
+
};
|
|
460
|
+
return { filePath, edit, fixes, existingPatch };
|
|
461
|
+
}
|
|
462
|
+
function displayPreview(editsByFile, targetDir, log) {
|
|
463
|
+
log(chalk3.bold("Preview - changes that would be made:"));
|
|
464
|
+
log("");
|
|
465
|
+
for (const [filePath, fileEdits] of editsByFile) {
|
|
466
|
+
const relativePath = path3.relative(targetDir, filePath);
|
|
467
|
+
for (const { export: exp, edit, fixes } of fileEdits) {
|
|
468
|
+
log(chalk3.cyan(`${relativePath}:${edit.startLine + 1}`));
|
|
469
|
+
log(chalk3.bold(` ${exp.name}`));
|
|
470
|
+
log("");
|
|
471
|
+
if (edit.hasExisting && edit.existingJSDoc) {
|
|
472
|
+
const oldLines = edit.existingJSDoc.split(`
|
|
473
|
+
`);
|
|
474
|
+
const newLines = edit.newJSDoc.split(`
|
|
475
|
+
`);
|
|
476
|
+
for (const line of oldLines) {
|
|
477
|
+
log(chalk3.red(` - ${line}`));
|
|
478
|
+
}
|
|
479
|
+
for (const line of newLines) {
|
|
480
|
+
log(chalk3.green(` + ${line}`));
|
|
481
|
+
}
|
|
482
|
+
} else {
|
|
483
|
+
const newLines = edit.newJSDoc.split(`
|
|
484
|
+
`);
|
|
485
|
+
for (const line of newLines) {
|
|
486
|
+
log(chalk3.green(` + ${line}`));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
log("");
|
|
490
|
+
log(chalk3.dim(` Fixes: ${fixes.map((f) => f.description).join(", ")}`));
|
|
491
|
+
log("");
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const totalFixes = Array.from(editsByFile.values()).reduce((sum, fileEdits) => sum + fileEdits.reduce((s, e) => s + e.fixes.length, 0), 0);
|
|
495
|
+
log(chalk3.yellow(`${totalFixes} fix(es) across ${editsByFile.size} file(s) would be applied.`));
|
|
496
|
+
log(chalk3.gray("Run with --fix to apply these changes."));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// src/commands/check/output.ts
|
|
500
|
+
import { generateReport } from "@doccov/sdk";
|
|
501
|
+
import chalk5 from "chalk";
|
|
180
502
|
|
|
181
503
|
// src/reports/changelog-renderer.ts
|
|
182
504
|
function renderChangelog(data, options = {}) {
|
|
@@ -246,7 +568,7 @@ function renderChangelog(data, options = {}) {
|
|
|
246
568
|
`);
|
|
247
569
|
}
|
|
248
570
|
// src/reports/diff-markdown.ts
|
|
249
|
-
import * as
|
|
571
|
+
import * as path4 from "node:path";
|
|
250
572
|
function bar(pct, width = 10) {
|
|
251
573
|
const filled = Math.round(pct / 100 * width);
|
|
252
574
|
return "█".repeat(filled) + "░".repeat(width - filled);
|
|
@@ -397,7 +719,7 @@ function renderDocsImpactSection(lines, docsImpact, limit) {
|
|
|
397
719
|
lines.push("| File | Issues | Details |");
|
|
398
720
|
lines.push("|------|--------|---------|");
|
|
399
721
|
for (const file of impactedFiles.slice(0, limit)) {
|
|
400
|
-
const filename =
|
|
722
|
+
const filename = path4.basename(file.file);
|
|
401
723
|
const issueCount = file.references.length;
|
|
402
724
|
const firstRef = file.references[0];
|
|
403
725
|
const detail = firstRef ? `L${firstRef.line}: ${firstRef.memberName ?? firstRef.exportName}` : "-";
|
|
@@ -613,6 +935,22 @@ function renderHtml(stats, options = {}) {
|
|
|
613
935
|
</html>`;
|
|
614
936
|
}
|
|
615
937
|
// src/reports/pr-comment.ts
|
|
938
|
+
function extractReturnType(schema) {
|
|
939
|
+
if (!schema)
|
|
940
|
+
return "void";
|
|
941
|
+
if (typeof schema === "string")
|
|
942
|
+
return schema;
|
|
943
|
+
if (typeof schema === "object") {
|
|
944
|
+
const s = schema;
|
|
945
|
+
if (typeof s.type === "string")
|
|
946
|
+
return s.type;
|
|
947
|
+
if (typeof s.$ref === "string") {
|
|
948
|
+
const ref = s.$ref;
|
|
949
|
+
return ref.startsWith("#/types/") ? ref.slice("#/types/".length) : ref;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return "unknown";
|
|
953
|
+
}
|
|
616
954
|
function renderPRComment(data, opts = {}) {
|
|
617
955
|
const { diff, headSpec } = data;
|
|
618
956
|
const limit = opts.limit ?? 10;
|
|
@@ -768,12 +1106,24 @@ function buildFileLink(file, opts) {
|
|
|
768
1106
|
}
|
|
769
1107
|
return `\`${file}\``;
|
|
770
1108
|
}
|
|
1109
|
+
function getExportKeyword(kind) {
|
|
1110
|
+
switch (kind) {
|
|
1111
|
+
case "type":
|
|
1112
|
+
return "type";
|
|
1113
|
+
case "interface":
|
|
1114
|
+
return "interface";
|
|
1115
|
+
case "class":
|
|
1116
|
+
return "class";
|
|
1117
|
+
default:
|
|
1118
|
+
return "function";
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
771
1121
|
function formatExportSignature(exp) {
|
|
772
|
-
const prefix = `export ${exp.kind
|
|
1122
|
+
const prefix = `export ${getExportKeyword(exp.kind)}`;
|
|
773
1123
|
if (exp.kind === "function" && exp.signatures?.[0]) {
|
|
774
1124
|
const sig = exp.signatures[0];
|
|
775
1125
|
const params = sig.parameters?.map((p) => `${p.name}${p.required === false ? "?" : ""}`).join(", ") ?? "";
|
|
776
|
-
const ret = sig.returns?.
|
|
1126
|
+
const ret = extractReturnType(sig.returns?.schema);
|
|
777
1127
|
return `${prefix} ${exp.name}(${params}): ${ret}`;
|
|
778
1128
|
}
|
|
779
1129
|
if (exp.kind === "type" || exp.kind === "interface") {
|
|
@@ -795,7 +1145,7 @@ function getMissingSignals(exp) {
|
|
|
795
1145
|
if (undocParams.length > 0) {
|
|
796
1146
|
missing.push(`\`@param ${undocParams.map((p) => p.name).join(", ")}\``);
|
|
797
1147
|
}
|
|
798
|
-
if (!sig.returns?.description && sig.returns?.
|
|
1148
|
+
if (!sig.returns?.description && extractReturnType(sig.returns?.schema) !== "void") {
|
|
799
1149
|
missing.push("`@returns`");
|
|
800
1150
|
}
|
|
801
1151
|
}
|
|
@@ -837,7 +1187,7 @@ function renderFixGuidance(diff, opts) {
|
|
|
837
1187
|
|
|
838
1188
|
**Quick fix:** Run \`npx doccov check --fix\` to auto-fix ${opts.fixableDriftCount} issue(s).` : "";
|
|
839
1189
|
sections.push(`**For doc drift:**
|
|
840
|
-
|
|
1190
|
+
Update JSDoc to match current code signatures.${fixableNote}`);
|
|
841
1191
|
}
|
|
842
1192
|
if (opts.staleDocsRefs && opts.staleDocsRefs.length > 0) {
|
|
843
1193
|
sections.push(`**For stale docs:**
|
|
@@ -872,7 +1222,7 @@ function renderDetailsTable(lines, diff) {
|
|
|
872
1222
|
// src/reports/stats.ts
|
|
873
1223
|
import { isFixableDrift } from "@doccov/sdk";
|
|
874
1224
|
import {
|
|
875
|
-
DRIFT_CATEGORIES
|
|
1225
|
+
DRIFT_CATEGORIES as DRIFT_CATEGORIES2
|
|
876
1226
|
} from "@openpkg-ts/spec";
|
|
877
1227
|
function computeStats(spec) {
|
|
878
1228
|
const exports = spec.exports ?? [];
|
|
@@ -918,7 +1268,7 @@ function computeStats(spec) {
|
|
|
918
1268
|
suggestion: d.suggestion
|
|
919
1269
|
};
|
|
920
1270
|
driftIssues.push(item);
|
|
921
|
-
const category =
|
|
1271
|
+
const category = DRIFT_CATEGORIES2[d.type] ?? "semantic";
|
|
922
1272
|
driftByCategory[category].push(item);
|
|
923
1273
|
}
|
|
924
1274
|
}
|
|
@@ -967,21 +1317,21 @@ function computeStats(spec) {
|
|
|
967
1317
|
};
|
|
968
1318
|
}
|
|
969
1319
|
// src/reports/writer.ts
|
|
970
|
-
import * as
|
|
971
|
-
import * as
|
|
1320
|
+
import * as fs3 from "node:fs";
|
|
1321
|
+
import * as path5 from "node:path";
|
|
972
1322
|
import { DEFAULT_REPORT_DIR, getReportPath } from "@doccov/sdk";
|
|
973
|
-
import
|
|
1323
|
+
import chalk4 from "chalk";
|
|
974
1324
|
function writeReport(options) {
|
|
975
1325
|
const { format, content, outputPath, cwd = process.cwd(), silent = false } = options;
|
|
976
|
-
const reportPath = outputPath ?
|
|
977
|
-
const dir =
|
|
978
|
-
if (!
|
|
979
|
-
|
|
1326
|
+
const reportPath = outputPath ? path5.resolve(cwd, outputPath) : path5.resolve(cwd, getReportPath(format));
|
|
1327
|
+
const dir = path5.dirname(reportPath);
|
|
1328
|
+
if (!fs3.existsSync(dir)) {
|
|
1329
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
980
1330
|
}
|
|
981
|
-
|
|
982
|
-
const relativePath =
|
|
1331
|
+
fs3.writeFileSync(reportPath, content);
|
|
1332
|
+
const relativePath = path5.relative(cwd, reportPath);
|
|
983
1333
|
if (!silent) {
|
|
984
|
-
console.log(
|
|
1334
|
+
console.log(chalk4.green(`✓ Wrote ${format} report to ${relativePath}`));
|
|
985
1335
|
}
|
|
986
1336
|
return { path: reportPath, format, relativePath };
|
|
987
1337
|
}
|
|
@@ -1004,149 +1354,257 @@ function writeReports(options) {
|
|
|
1004
1354
|
}));
|
|
1005
1355
|
return results;
|
|
1006
1356
|
}
|
|
1007
|
-
// src/
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1357
|
+
// src/commands/check/output.ts
|
|
1358
|
+
function displayTextOutput(options, deps) {
|
|
1359
|
+
const {
|
|
1360
|
+
spec,
|
|
1361
|
+
coverageScore,
|
|
1362
|
+
minCoverage,
|
|
1363
|
+
maxDrift,
|
|
1364
|
+
driftExports,
|
|
1365
|
+
typecheckErrors,
|
|
1366
|
+
staleRefs,
|
|
1367
|
+
exampleResult,
|
|
1368
|
+
specWarnings,
|
|
1369
|
+
specInfos
|
|
1370
|
+
} = options;
|
|
1371
|
+
const { log } = deps;
|
|
1372
|
+
const totalExportsForDrift = spec.exports?.length ?? 0;
|
|
1373
|
+
const exportsWithDrift = new Set(driftExports.map((d) => d.name)).size;
|
|
1374
|
+
const driftScore = totalExportsForDrift === 0 ? 0 : Math.round(exportsWithDrift / totalExportsForDrift * 100);
|
|
1375
|
+
const coverageFailed = coverageScore < minCoverage;
|
|
1376
|
+
const driftFailed = maxDrift !== undefined && driftScore > maxDrift;
|
|
1377
|
+
const hasTypecheckErrors = typecheckErrors.length > 0;
|
|
1378
|
+
if (specWarnings.length > 0 || specInfos.length > 0) {
|
|
1379
|
+
log("");
|
|
1380
|
+
for (const diag of specWarnings) {
|
|
1381
|
+
log(chalk5.yellow(`⚠ ${diag.message}`));
|
|
1382
|
+
if (diag.suggestion) {
|
|
1383
|
+
log(chalk5.gray(` ${diag.suggestion}`));
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
for (const diag of specInfos) {
|
|
1387
|
+
log(chalk5.cyan(`ℹ ${diag.message}`));
|
|
1388
|
+
if (diag.suggestion) {
|
|
1389
|
+
log(chalk5.gray(` ${diag.suggestion}`));
|
|
1390
|
+
}
|
|
1022
1391
|
}
|
|
1023
1392
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1393
|
+
const pkgName = spec.meta?.name ?? "unknown";
|
|
1394
|
+
const pkgVersion = spec.meta?.version ?? "";
|
|
1395
|
+
const totalExports = spec.exports?.length ?? 0;
|
|
1396
|
+
log("");
|
|
1397
|
+
log(chalk5.bold(`${pkgName}${pkgVersion ? `@${pkgVersion}` : ""}`));
|
|
1398
|
+
log("");
|
|
1399
|
+
log(` Exports: ${totalExports}`);
|
|
1400
|
+
if (coverageFailed) {
|
|
1401
|
+
log(chalk5.red(` Coverage: ✗ ${coverageScore}%`) + chalk5.dim(` (min ${minCoverage}%)`));
|
|
1402
|
+
} else {
|
|
1403
|
+
log(chalk5.green(` Coverage: ✓ ${coverageScore}%`) + chalk5.dim(` (min ${minCoverage}%)`));
|
|
1031
1404
|
}
|
|
1032
|
-
if (
|
|
1033
|
-
|
|
1405
|
+
if (maxDrift !== undefined) {
|
|
1406
|
+
if (driftFailed) {
|
|
1407
|
+
log(chalk5.red(` Drift: ✗ ${driftScore}%`) + chalk5.dim(` (max ${maxDrift}%)`));
|
|
1408
|
+
} else {
|
|
1409
|
+
log(chalk5.green(` Drift: ✓ ${driftScore}%`) + chalk5.dim(` (max ${maxDrift}%)`));
|
|
1410
|
+
}
|
|
1411
|
+
} else {
|
|
1412
|
+
log(` Drift: ${driftScore}%`);
|
|
1413
|
+
}
|
|
1414
|
+
if (exampleResult) {
|
|
1415
|
+
const typecheckCount = exampleResult.typecheck?.errors.length ?? 0;
|
|
1416
|
+
if (typecheckCount > 0) {
|
|
1417
|
+
log(chalk5.yellow(` Examples: ${typecheckCount} type error(s)`));
|
|
1418
|
+
for (const err of typecheckErrors.slice(0, 5)) {
|
|
1419
|
+
const loc = `example[${err.error.exampleIndex}]:${err.error.line}:${err.error.column}`;
|
|
1420
|
+
log(chalk5.dim(` ${err.exportName} ${loc}`));
|
|
1421
|
+
log(chalk5.red(` ${err.error.message}`));
|
|
1422
|
+
}
|
|
1423
|
+
if (typecheckErrors.length > 5) {
|
|
1424
|
+
log(chalk5.dim(` ... and ${typecheckErrors.length - 5} more`));
|
|
1425
|
+
}
|
|
1426
|
+
} else {
|
|
1427
|
+
log(chalk5.green(` Examples: ✓ validated`));
|
|
1428
|
+
}
|
|
1034
1429
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1430
|
+
const hasStaleRefs = staleRefs.length > 0;
|
|
1431
|
+
if (hasStaleRefs) {
|
|
1432
|
+
log(chalk5.yellow(` Docs: ${staleRefs.length} stale ref(s)`));
|
|
1433
|
+
for (const ref of staleRefs.slice(0, 5)) {
|
|
1434
|
+
log(chalk5.dim(` ${ref.file}:${ref.line} - "${ref.exportName}"`));
|
|
1435
|
+
}
|
|
1436
|
+
if (staleRefs.length > 5) {
|
|
1437
|
+
log(chalk5.dim(` ... and ${staleRefs.length - 5} more`));
|
|
1438
|
+
}
|
|
1037
1439
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1440
|
+
log("");
|
|
1441
|
+
const failed = coverageFailed || driftFailed || hasTypecheckErrors || hasStaleRefs;
|
|
1442
|
+
if (!failed) {
|
|
1443
|
+
const thresholdParts = [];
|
|
1444
|
+
thresholdParts.push(`coverage ${coverageScore}% ≥ ${minCoverage}%`);
|
|
1445
|
+
if (maxDrift !== undefined) {
|
|
1446
|
+
thresholdParts.push(`drift ${driftScore}% ≤ ${maxDrift}%`);
|
|
1447
|
+
}
|
|
1448
|
+
log(chalk5.green(`✓ Check passed (${thresholdParts.join(", ")})`));
|
|
1449
|
+
return true;
|
|
1040
1450
|
}
|
|
1041
|
-
if (
|
|
1042
|
-
|
|
1451
|
+
if (hasTypecheckErrors) {
|
|
1452
|
+
log(chalk5.red(`✗ ${typecheckErrors.length} example type errors`));
|
|
1043
1453
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
return { messages };
|
|
1454
|
+
if (hasStaleRefs) {
|
|
1455
|
+
log(chalk5.red(`✗ ${staleRefs.length} stale references in docs`));
|
|
1047
1456
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1457
|
+
log("");
|
|
1458
|
+
log(chalk5.dim("Use --format json or --format markdown for detailed reports"));
|
|
1459
|
+
return false;
|
|
1460
|
+
}
|
|
1461
|
+
function handleNonTextOutput(options, deps) {
|
|
1462
|
+
const {
|
|
1463
|
+
format,
|
|
1464
|
+
spec,
|
|
1465
|
+
rawSpec,
|
|
1466
|
+
coverageScore,
|
|
1467
|
+
minCoverage,
|
|
1468
|
+
maxDrift,
|
|
1469
|
+
driftExports,
|
|
1470
|
+
typecheckErrors,
|
|
1471
|
+
limit,
|
|
1472
|
+
stdout,
|
|
1473
|
+
outputPath,
|
|
1474
|
+
cwd
|
|
1475
|
+
} = options;
|
|
1476
|
+
const { log } = deps;
|
|
1477
|
+
const stats = computeStats(spec);
|
|
1478
|
+
const report = generateReport(rawSpec);
|
|
1479
|
+
const jsonContent = JSON.stringify(report, null, 2);
|
|
1480
|
+
let formatContent;
|
|
1481
|
+
switch (format) {
|
|
1482
|
+
case "json":
|
|
1483
|
+
formatContent = jsonContent;
|
|
1484
|
+
break;
|
|
1485
|
+
case "markdown":
|
|
1486
|
+
formatContent = renderMarkdown(stats, { limit });
|
|
1487
|
+
break;
|
|
1488
|
+
case "html":
|
|
1489
|
+
formatContent = renderHtml(stats, { limit });
|
|
1490
|
+
break;
|
|
1491
|
+
case "github":
|
|
1492
|
+
formatContent = renderGithubSummary(stats, {
|
|
1493
|
+
coverageScore,
|
|
1494
|
+
driftCount: driftExports.length
|
|
1495
|
+
});
|
|
1496
|
+
break;
|
|
1497
|
+
default:
|
|
1498
|
+
throw new Error(`Unknown format: ${format}`);
|
|
1069
1499
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1500
|
+
if (stdout) {
|
|
1501
|
+
log(formatContent);
|
|
1502
|
+
} else {
|
|
1503
|
+
writeReports({
|
|
1504
|
+
format,
|
|
1505
|
+
formatContent,
|
|
1506
|
+
jsonContent,
|
|
1507
|
+
outputPath,
|
|
1508
|
+
cwd
|
|
1509
|
+
});
|
|
1074
1510
|
}
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1511
|
+
const totalExportsForDrift = spec.exports?.length ?? 0;
|
|
1512
|
+
const exportsWithDrift = new Set(driftExports.map((d) => d.name)).size;
|
|
1513
|
+
const driftScore = totalExportsForDrift === 0 ? 0 : Math.round(exportsWithDrift / totalExportsForDrift * 100);
|
|
1514
|
+
const coverageFailed = coverageScore < minCoverage;
|
|
1515
|
+
const driftFailed = maxDrift !== undefined && driftScore > maxDrift;
|
|
1516
|
+
const hasTypecheckErrors = typecheckErrors.length > 0;
|
|
1517
|
+
return !(coverageFailed || driftFailed || hasTypecheckErrors);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// src/commands/check/validation.ts
|
|
1521
|
+
import { validateExamples } from "@doccov/sdk";
|
|
1522
|
+
async function runExampleValidation(spec, options) {
|
|
1523
|
+
const { validations, targetDir, timeout = 5000, installTimeout = 60000 } = options;
|
|
1524
|
+
const typecheckErrors = [];
|
|
1525
|
+
const runtimeDrifts = [];
|
|
1526
|
+
const result = await validateExamples(spec.exports ?? [], {
|
|
1527
|
+
validations,
|
|
1528
|
+
packagePath: targetDir,
|
|
1529
|
+
exportNames: (spec.exports ?? []).map((e) => e.name),
|
|
1530
|
+
timeout,
|
|
1531
|
+
installTimeout
|
|
1532
|
+
});
|
|
1533
|
+
if (result.typecheck) {
|
|
1534
|
+
for (const err of result.typecheck.errors) {
|
|
1535
|
+
typecheckErrors.push({
|
|
1536
|
+
exportName: err.exportName,
|
|
1537
|
+
error: err.error
|
|
1538
|
+
});
|
|
1081
1539
|
}
|
|
1082
1540
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1541
|
+
if (result.run) {
|
|
1542
|
+
for (const drift of result.run.drifts) {
|
|
1543
|
+
runtimeDrifts.push({
|
|
1544
|
+
name: drift.exportName,
|
|
1545
|
+
type: "example-runtime-error",
|
|
1546
|
+
issue: drift.issue,
|
|
1547
|
+
suggestion: drift.suggestion,
|
|
1548
|
+
category: "example"
|
|
1549
|
+
});
|
|
1088
1550
|
}
|
|
1089
1551
|
}
|
|
1090
|
-
|
|
1091
|
-
const step = this.steps[this.currentStep];
|
|
1092
|
-
if (!step)
|
|
1093
|
-
return;
|
|
1094
|
-
const label = step.activeLabel ?? step.label;
|
|
1095
|
-
const prefix = chalk3.dim(`[${this.currentStep + 1}/${this.steps.length}]`);
|
|
1096
|
-
process.stdout.write(`\r${prefix} ${chalk3.cyan(label)}...`);
|
|
1097
|
-
}
|
|
1098
|
-
completeCurrentStep() {
|
|
1099
|
-
const step = this.steps[this.currentStep];
|
|
1100
|
-
if (!step)
|
|
1101
|
-
return;
|
|
1102
|
-
const elapsed = ((Date.now() - this.stepStartTime) / 1000).toFixed(1);
|
|
1103
|
-
const prefix = chalk3.dim(`[${this.currentStep + 1}/${this.steps.length}]`);
|
|
1104
|
-
process.stdout.write(`\r${" ".repeat(80)}\r`);
|
|
1105
|
-
console.log(`${prefix} ${step.label} ${chalk3.green("✓")} ${chalk3.dim(`(${elapsed}s)`)}`);
|
|
1106
|
-
}
|
|
1552
|
+
return { result, typecheckErrors, runtimeDrifts };
|
|
1107
1553
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
if (
|
|
1112
|
-
return
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1554
|
+
async function validateMarkdownDocs(options) {
|
|
1555
|
+
const { docsPatterns, targetDir, exportNames } = options;
|
|
1556
|
+
const staleRefs = [];
|
|
1557
|
+
if (docsPatterns.length === 0) {
|
|
1558
|
+
return staleRefs;
|
|
1559
|
+
}
|
|
1560
|
+
const markdownFiles = await loadMarkdownFiles(docsPatterns, targetDir);
|
|
1561
|
+
if (markdownFiles.length === 0) {
|
|
1562
|
+
return staleRefs;
|
|
1563
|
+
}
|
|
1564
|
+
const exportSet = new Set(exportNames);
|
|
1565
|
+
for (const mdFile of markdownFiles) {
|
|
1566
|
+
for (const block of mdFile.codeBlocks) {
|
|
1567
|
+
const codeLines = block.code.split(`
|
|
1568
|
+
`);
|
|
1569
|
+
for (let i = 0;i < codeLines.length; i++) {
|
|
1570
|
+
const line = codeLines[i];
|
|
1571
|
+
const importMatch = line.match(/import\s*\{([^}]+)\}\s*from\s*['"][^'"]*['"]/);
|
|
1572
|
+
if (importMatch) {
|
|
1573
|
+
const imports = importMatch[1].split(",").map((s) => s.trim().split(/\s+/)[0]);
|
|
1574
|
+
for (const imp of imports) {
|
|
1575
|
+
if (imp && !exportSet.has(imp)) {
|
|
1576
|
+
staleRefs.push({
|
|
1577
|
+
file: mdFile.path,
|
|
1578
|
+
line: block.lineStart + i,
|
|
1579
|
+
exportName: imp,
|
|
1580
|
+
context: line.trim()
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
return staleRefs;
|
|
1118
1589
|
}
|
|
1119
1590
|
|
|
1120
|
-
// src/commands/check.ts
|
|
1591
|
+
// src/commands/check/index.ts
|
|
1121
1592
|
var defaultDependencies = {
|
|
1122
1593
|
createDocCov: (options) => new DocCov(options),
|
|
1123
1594
|
log: console.log,
|
|
1124
1595
|
error: console.error
|
|
1125
1596
|
};
|
|
1126
|
-
function collectDriftsFromExports(exports) {
|
|
1127
|
-
const results = [];
|
|
1128
|
-
for (const exp of exports) {
|
|
1129
|
-
for (const drift of exp.docs?.drift ?? []) {
|
|
1130
|
-
results.push({ export: exp, drift });
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
return results;
|
|
1134
|
-
}
|
|
1135
|
-
function groupByExport(drifts) {
|
|
1136
|
-
const map = new Map;
|
|
1137
|
-
for (const { export: exp, drift } of drifts) {
|
|
1138
|
-
const existing = map.get(exp) ?? [];
|
|
1139
|
-
existing.push(drift);
|
|
1140
|
-
map.set(exp, existing);
|
|
1141
|
-
}
|
|
1142
|
-
return map;
|
|
1143
|
-
}
|
|
1144
1597
|
function registerCheckCommand(program, dependencies = {}) {
|
|
1145
1598
|
const { createDocCov, log, error } = {
|
|
1146
1599
|
...defaultDependencies,
|
|
1147
1600
|
...dependencies
|
|
1148
1601
|
};
|
|
1149
|
-
program.command("check [entry]").description("Check documentation coverage and output reports").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--min-coverage <percentage>", "Minimum docs coverage percentage (0-100)", (value) => Number(value)).option("--max-drift <percentage>", "Maximum drift percentage allowed (0-100)", (value) => Number(value)).option("--examples [mode]", "Example validation: presence, typecheck, run (comma-separated). Bare flag runs all.").option("--skip-resolve", "Skip external type resolution from node_modules").option("--docs <glob>", "Glob pattern for markdown docs to check for stale refs", collect, []).option("--fix", "Auto-fix drift issues").option("--
|
|
1602
|
+
program.command("check [entry]").description("Check documentation coverage and output reports").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--min-coverage <percentage>", "Minimum docs coverage percentage (0-100)", (value) => Number(value)).option("--max-drift <percentage>", "Maximum drift percentage allowed (0-100)", (value) => Number(value)).option("--examples [mode]", "Example validation: presence, typecheck, run (comma-separated). Bare flag runs all.").option("--skip-resolve", "Skip external type resolution from node_modules").option("--docs <glob>", "Glob pattern for markdown docs to check for stale refs", collect, []).option("--fix", "Auto-fix drift issues").option("--preview", "Preview fixes with diff output (implies --fix)").option("--format <format>", "Output format: text, json, markdown, html, github", "text").option("-o, --output <file>", "Custom output path (overrides default .doccov/ path)").option("--stdout", "Output to stdout instead of writing to .doccov/").option("--update-snapshot", "Force regenerate .doccov/report.json").option("--limit <n>", "Max exports to show in report tables", "20").option("--max-type-depth <number>", "Maximum depth for type conversion (default: 20)", (value) => {
|
|
1603
|
+
const n = parseInt(value, 10);
|
|
1604
|
+
if (Number.isNaN(n) || n < 1)
|
|
1605
|
+
throw new Error("--max-type-depth must be a positive integer");
|
|
1606
|
+
return n;
|
|
1607
|
+
}).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) => {
|
|
1150
1608
|
try {
|
|
1151
1609
|
let validations = parseExamplesFlag(options.examples);
|
|
1152
1610
|
let hasExamples = validations.length > 0;
|
|
@@ -1190,19 +1648,18 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
1190
1648
|
};
|
|
1191
1649
|
const resolvedFilters = mergeFilterOptions(config, cliFilters);
|
|
1192
1650
|
if (resolvedFilters.visibility) {
|
|
1193
|
-
log(
|
|
1651
|
+
log(chalk6.dim(`Filtering by visibility: ${resolvedFilters.visibility.join(", ")}`));
|
|
1194
1652
|
}
|
|
1195
1653
|
steps.next();
|
|
1196
1654
|
const resolveExternalTypes = !options.skipResolve;
|
|
1197
|
-
let specResult;
|
|
1198
1655
|
const doccov = createDocCov({
|
|
1199
1656
|
resolveExternalTypes,
|
|
1200
|
-
maxDepth: options.maxTypeDepth
|
|
1657
|
+
maxDepth: options.maxTypeDepth,
|
|
1201
1658
|
useCache: options.cache !== false,
|
|
1202
1659
|
cwd: options.cwd
|
|
1203
1660
|
});
|
|
1204
1661
|
const analyzeOptions = resolvedFilters.visibility ? { filters: { visibility: resolvedFilters.visibility } } : {};
|
|
1205
|
-
specResult = await doccov.analyzeFileWithDiagnostics(entryFile, analyzeOptions);
|
|
1662
|
+
const specResult = await doccov.analyzeFileWithDiagnostics(entryFile, analyzeOptions);
|
|
1206
1663
|
if (!specResult) {
|
|
1207
1664
|
throw new Error("Failed to analyze documentation coverage.");
|
|
1208
1665
|
}
|
|
@@ -1212,384 +1669,84 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
1212
1669
|
steps.next();
|
|
1213
1670
|
const specWarnings = specResult.diagnostics.filter((d) => d.severity === "warning");
|
|
1214
1671
|
const specInfos = specResult.diagnostics.filter((d) => d.severity === "info");
|
|
1215
|
-
const isPreview = options.preview
|
|
1216
|
-
const shouldFix = options.fix ||
|
|
1672
|
+
const isPreview = options.preview;
|
|
1673
|
+
const shouldFix = options.fix || isPreview;
|
|
1217
1674
|
let exampleResult;
|
|
1218
|
-
|
|
1219
|
-
|
|
1675
|
+
let typecheckErrors = [];
|
|
1676
|
+
let runtimeDrifts = [];
|
|
1220
1677
|
if (hasExamples) {
|
|
1221
|
-
|
|
1678
|
+
const validation = await runExampleValidation(spec, {
|
|
1222
1679
|
validations,
|
|
1223
|
-
|
|
1224
|
-
exportNames: (spec.exports ?? []).map((e) => e.name),
|
|
1225
|
-
timeout: 5000,
|
|
1226
|
-
installTimeout: 60000
|
|
1680
|
+
targetDir
|
|
1227
1681
|
});
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
exportName: err.exportName,
|
|
1232
|
-
error: err.error
|
|
1233
|
-
});
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
if (exampleResult.run) {
|
|
1237
|
-
for (const drift of exampleResult.run.drifts) {
|
|
1238
|
-
runtimeDrifts.push({
|
|
1239
|
-
name: drift.exportName,
|
|
1240
|
-
type: "example-runtime-error",
|
|
1241
|
-
issue: drift.issue,
|
|
1242
|
-
suggestion: drift.suggestion,
|
|
1243
|
-
category: "example"
|
|
1244
|
-
});
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1682
|
+
exampleResult = validation.result;
|
|
1683
|
+
typecheckErrors = validation.typecheckErrors;
|
|
1684
|
+
runtimeDrifts = validation.runtimeDrifts;
|
|
1247
1685
|
steps.next();
|
|
1248
1686
|
}
|
|
1249
|
-
const staleRefs = [];
|
|
1250
1687
|
let docsPatterns = options.docs;
|
|
1251
1688
|
if (docsPatterns.length === 0 && config?.docs?.include) {
|
|
1252
1689
|
docsPatterns = config.docs.include;
|
|
1253
1690
|
}
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
for (const mdFile of markdownFiles) {
|
|
1260
|
-
for (const block of mdFile.codeBlocks) {
|
|
1261
|
-
const codeLines = block.code.split(`
|
|
1262
|
-
`);
|
|
1263
|
-
for (let i = 0;i < codeLines.length; i++) {
|
|
1264
|
-
const line = codeLines[i];
|
|
1265
|
-
const importMatch = line.match(/import\s*\{([^}]+)\}\s*from\s*['"][^'"]*['"]/);
|
|
1266
|
-
if (importMatch) {
|
|
1267
|
-
const imports = importMatch[1].split(",").map((s) => s.trim().split(/\s+/)[0]);
|
|
1268
|
-
for (const imp of imports) {
|
|
1269
|
-
if (imp && !exportSet.has(imp)) {
|
|
1270
|
-
staleRefs.push({
|
|
1271
|
-
file: mdFile.path,
|
|
1272
|
-
line: block.lineStart + i,
|
|
1273
|
-
exportName: imp,
|
|
1274
|
-
context: line.trim()
|
|
1275
|
-
});
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1691
|
+
const staleRefs = await validateMarkdownDocs({
|
|
1692
|
+
docsPatterns,
|
|
1693
|
+
targetDir,
|
|
1694
|
+
exportNames: (spec.exports ?? []).map((e) => e.name)
|
|
1695
|
+
});
|
|
1284
1696
|
const coverageScore = spec.docs?.coverageScore ?? 0;
|
|
1285
1697
|
const allDriftExports = [...collectDrift(spec.exports ?? []), ...runtimeDrifts];
|
|
1286
1698
|
let driftExports = hasExamples ? allDriftExports : allDriftExports.filter((d) => d.category !== "example");
|
|
1287
|
-
const fixedDriftKeys = new Set;
|
|
1288
1699
|
if (shouldFix && driftExports.length > 0) {
|
|
1289
|
-
const
|
|
1290
|
-
if (allDrifts.length > 0) {
|
|
1291
|
-
const { fixable, nonFixable } = categorizeDrifts(allDrifts.map((d) => d.drift));
|
|
1292
|
-
if (fixable.length === 0) {
|
|
1293
|
-
log(chalk4.yellow(`Found ${nonFixable.length} drift issue(s), but none are auto-fixable.`));
|
|
1294
|
-
} else {
|
|
1295
|
-
log("");
|
|
1296
|
-
log(chalk4.bold(`Found ${fixable.length} fixable issue(s)`));
|
|
1297
|
-
if (nonFixable.length > 0) {
|
|
1298
|
-
log(chalk4.gray(`(${nonFixable.length} non-fixable issue(s) skipped)`));
|
|
1299
|
-
}
|
|
1300
|
-
log("");
|
|
1301
|
-
const groupedDrifts = groupByExport(allDrifts.filter((d) => fixable.includes(d.drift)));
|
|
1302
|
-
const edits = [];
|
|
1303
|
-
const editsByFile = new Map;
|
|
1304
|
-
for (const [exp, drifts] of groupedDrifts) {
|
|
1305
|
-
if (!exp.source?.file) {
|
|
1306
|
-
log(chalk4.gray(` Skipping ${exp.name}: no source location`));
|
|
1307
|
-
continue;
|
|
1308
|
-
}
|
|
1309
|
-
if (exp.source.file.endsWith(".d.ts")) {
|
|
1310
|
-
log(chalk4.gray(` Skipping ${exp.name}: declaration file`));
|
|
1311
|
-
continue;
|
|
1312
|
-
}
|
|
1313
|
-
const filePath = path4.resolve(targetDir, exp.source.file);
|
|
1314
|
-
if (!fs2.existsSync(filePath)) {
|
|
1315
|
-
log(chalk4.gray(` Skipping ${exp.name}: file not found`));
|
|
1316
|
-
continue;
|
|
1317
|
-
}
|
|
1318
|
-
const sourceFile = createSourceFile(filePath);
|
|
1319
|
-
const location = findJSDocLocation(sourceFile, exp.name, exp.source.line);
|
|
1320
|
-
if (!location) {
|
|
1321
|
-
log(chalk4.gray(` Skipping ${exp.name}: could not find declaration`));
|
|
1322
|
-
continue;
|
|
1323
|
-
}
|
|
1324
|
-
let existingPatch = {};
|
|
1325
|
-
if (location.hasExisting && location.existingJSDoc) {
|
|
1326
|
-
existingPatch = parseJSDocToPatch(location.existingJSDoc);
|
|
1327
|
-
}
|
|
1328
|
-
const expWithDrift = { ...exp, docs: { ...exp.docs, drift: drifts } };
|
|
1329
|
-
const fixes = generateFixesForExport(expWithDrift, existingPatch);
|
|
1330
|
-
if (fixes.length === 0)
|
|
1331
|
-
continue;
|
|
1332
|
-
for (const drift of drifts) {
|
|
1333
|
-
fixedDriftKeys.add(`${exp.name}:${drift.issue}`);
|
|
1334
|
-
}
|
|
1335
|
-
const mergedPatch = mergeFixes(fixes, existingPatch);
|
|
1336
|
-
const newJSDoc = serializeJSDoc(mergedPatch, location.indent);
|
|
1337
|
-
const edit = {
|
|
1338
|
-
filePath,
|
|
1339
|
-
symbolName: exp.name,
|
|
1340
|
-
startLine: location.startLine,
|
|
1341
|
-
endLine: location.endLine,
|
|
1342
|
-
hasExisting: location.hasExisting,
|
|
1343
|
-
existingJSDoc: location.existingJSDoc,
|
|
1344
|
-
newJSDoc,
|
|
1345
|
-
indent: location.indent
|
|
1346
|
-
};
|
|
1347
|
-
edits.push(edit);
|
|
1348
|
-
const fileEdits = editsByFile.get(filePath) ?? [];
|
|
1349
|
-
fileEdits.push({ export: exp, edit, fixes, existingPatch });
|
|
1350
|
-
editsByFile.set(filePath, fileEdits);
|
|
1351
|
-
}
|
|
1352
|
-
if (edits.length > 0) {
|
|
1353
|
-
if (isPreview) {
|
|
1354
|
-
log(chalk4.bold("Preview - changes that would be made:"));
|
|
1355
|
-
log("");
|
|
1356
|
-
for (const [filePath, fileEdits] of editsByFile) {
|
|
1357
|
-
const relativePath = path4.relative(targetDir, filePath);
|
|
1358
|
-
for (const { export: exp, edit, fixes } of fileEdits) {
|
|
1359
|
-
log(chalk4.cyan(`${relativePath}:${edit.startLine + 1}`));
|
|
1360
|
-
log(chalk4.bold(` ${exp.name}`));
|
|
1361
|
-
log("");
|
|
1362
|
-
if (edit.hasExisting && edit.existingJSDoc) {
|
|
1363
|
-
const oldLines = edit.existingJSDoc.split(`
|
|
1364
|
-
`);
|
|
1365
|
-
const newLines = edit.newJSDoc.split(`
|
|
1366
|
-
`);
|
|
1367
|
-
for (const line of oldLines) {
|
|
1368
|
-
log(chalk4.red(` - ${line}`));
|
|
1369
|
-
}
|
|
1370
|
-
for (const line of newLines) {
|
|
1371
|
-
log(chalk4.green(` + ${line}`));
|
|
1372
|
-
}
|
|
1373
|
-
} else {
|
|
1374
|
-
const newLines = edit.newJSDoc.split(`
|
|
1375
|
-
`);
|
|
1376
|
-
for (const line of newLines) {
|
|
1377
|
-
log(chalk4.green(` + ${line}`));
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
log("");
|
|
1381
|
-
log(chalk4.dim(` Fixes: ${fixes.map((f) => f.description).join(", ")}`));
|
|
1382
|
-
log("");
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
const totalFixes = Array.from(editsByFile.values()).reduce((sum, edits2) => sum + edits2.reduce((s, e) => s + e.fixes.length, 0), 0);
|
|
1386
|
-
log(chalk4.yellow(`${totalFixes} fix(es) across ${editsByFile.size} file(s) would be applied.`));
|
|
1387
|
-
log(chalk4.gray("Run with --fix to apply these changes."));
|
|
1388
|
-
} else {
|
|
1389
|
-
const applyResult = await applyEdits(edits);
|
|
1390
|
-
if (applyResult.errors.length > 0) {
|
|
1391
|
-
for (const err of applyResult.errors) {
|
|
1392
|
-
error(chalk4.red(` ${err.file}: ${err.error}`));
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
const totalFixes = Array.from(editsByFile.values()).reduce((sum, edits2) => sum + edits2.reduce((s, e) => s + e.fixes.length, 0), 0);
|
|
1396
|
-
log("");
|
|
1397
|
-
log(chalk4.green(`✓ Applied ${totalFixes} fix(es) to ${applyResult.filesModified} file(s)`));
|
|
1398
|
-
for (const [filePath, fileEdits] of editsByFile) {
|
|
1399
|
-
const relativePath = path4.relative(targetDir, filePath);
|
|
1400
|
-
const fixCount = fileEdits.reduce((s, e) => s + e.fixes.length, 0);
|
|
1401
|
-
log(chalk4.dim(` ${relativePath} (${fixCount} fixes)`));
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1700
|
+
const fixResult = await handleFixes(spec, { isPreview, targetDir }, { log, error });
|
|
1407
1701
|
if (!isPreview) {
|
|
1408
|
-
driftExports = driftExports.filter((d) => !fixedDriftKeys.has(`${d.name}:${d.issue}`));
|
|
1702
|
+
driftExports = driftExports.filter((d) => !fixResult.fixedDriftKeys.has(`${d.name}:${d.issue}`));
|
|
1409
1703
|
}
|
|
1410
1704
|
}
|
|
1411
1705
|
steps.complete("Check complete");
|
|
1412
1706
|
if (format !== "text") {
|
|
1413
|
-
const
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
case "github":
|
|
1429
|
-
formatContent = renderGithubSummary(stats, {
|
|
1430
|
-
coverageScore,
|
|
1431
|
-
driftCount: driftExports.length
|
|
1432
|
-
});
|
|
1433
|
-
break;
|
|
1434
|
-
default:
|
|
1435
|
-
throw new Error(`Unknown format: ${format}`);
|
|
1436
|
-
}
|
|
1437
|
-
if (options.stdout) {
|
|
1438
|
-
log(formatContent);
|
|
1439
|
-
} else {
|
|
1440
|
-
writeReports({
|
|
1441
|
-
format,
|
|
1442
|
-
formatContent,
|
|
1443
|
-
jsonContent,
|
|
1444
|
-
outputPath: options.output,
|
|
1445
|
-
cwd: options.cwd
|
|
1446
|
-
});
|
|
1447
|
-
}
|
|
1448
|
-
const totalExportsForDrift2 = spec.exports?.length ?? 0;
|
|
1449
|
-
const exportsWithDrift2 = new Set(driftExports.map((d) => d.name)).size;
|
|
1450
|
-
const driftScore2 = totalExportsForDrift2 === 0 ? 0 : Math.round(exportsWithDrift2 / totalExportsForDrift2 * 100);
|
|
1451
|
-
const coverageFailed2 = coverageScore < minCoverage;
|
|
1452
|
-
const driftFailed2 = maxDrift !== undefined && driftScore2 > maxDrift;
|
|
1453
|
-
const hasTypecheckErrors2 = typecheckErrors.length > 0;
|
|
1454
|
-
if (coverageFailed2 || driftFailed2 || hasTypecheckErrors2) {
|
|
1707
|
+
const passed2 = handleNonTextOutput({
|
|
1708
|
+
format,
|
|
1709
|
+
spec,
|
|
1710
|
+
rawSpec: specResult.spec,
|
|
1711
|
+
coverageScore,
|
|
1712
|
+
minCoverage,
|
|
1713
|
+
maxDrift,
|
|
1714
|
+
driftExports,
|
|
1715
|
+
typecheckErrors,
|
|
1716
|
+
limit: parseInt(options.limit, 10) || 20,
|
|
1717
|
+
stdout: options.stdout,
|
|
1718
|
+
outputPath: options.output,
|
|
1719
|
+
cwd: options.cwd
|
|
1720
|
+
}, { log });
|
|
1721
|
+
if (!passed2) {
|
|
1455
1722
|
process.exit(1);
|
|
1456
1723
|
}
|
|
1457
1724
|
return;
|
|
1458
1725
|
}
|
|
1459
|
-
const
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
for (const diag of specInfos) {
|
|
1474
|
-
log(chalk4.cyan(`ℹ ${diag.message}`));
|
|
1475
|
-
if (diag.suggestion) {
|
|
1476
|
-
log(chalk4.gray(` ${diag.suggestion}`));
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
const pkgName = spec.meta?.name ?? "unknown";
|
|
1481
|
-
const pkgVersion = spec.meta?.version ?? "";
|
|
1482
|
-
const totalExports = spec.exports?.length ?? 0;
|
|
1483
|
-
log("");
|
|
1484
|
-
log(chalk4.bold(`${pkgName}${pkgVersion ? `@${pkgVersion}` : ""}`));
|
|
1485
|
-
log("");
|
|
1486
|
-
log(` Exports: ${totalExports}`);
|
|
1487
|
-
if (coverageFailed) {
|
|
1488
|
-
log(chalk4.red(` Coverage: ✗ ${coverageScore}%`) + chalk4.dim(` (min ${minCoverage}%)`));
|
|
1489
|
-
} else {
|
|
1490
|
-
log(chalk4.green(` Coverage: ✓ ${coverageScore}%`) + chalk4.dim(` (min ${minCoverage}%)`));
|
|
1491
|
-
}
|
|
1492
|
-
if (maxDrift !== undefined) {
|
|
1493
|
-
if (driftFailed) {
|
|
1494
|
-
log(chalk4.red(` Drift: ✗ ${driftScore}%`) + chalk4.dim(` (max ${maxDrift}%)`));
|
|
1495
|
-
} else {
|
|
1496
|
-
log(chalk4.green(` Drift: ✓ ${driftScore}%`) + chalk4.dim(` (max ${maxDrift}%)`));
|
|
1497
|
-
}
|
|
1498
|
-
} else {
|
|
1499
|
-
log(` Drift: ${driftScore}%`);
|
|
1500
|
-
}
|
|
1501
|
-
if (exampleResult) {
|
|
1502
|
-
const typecheckCount = exampleResult.typecheck?.errors.length ?? 0;
|
|
1503
|
-
if (typecheckCount > 0) {
|
|
1504
|
-
log(chalk4.yellow(` Examples: ${typecheckCount} type error(s)`));
|
|
1505
|
-
for (const err of typecheckErrors.slice(0, 5)) {
|
|
1506
|
-
const loc = `example[${err.error.exampleIndex}]:${err.error.line}:${err.error.column}`;
|
|
1507
|
-
log(chalk4.dim(` ${err.exportName} ${loc}`));
|
|
1508
|
-
log(chalk4.red(` ${err.error.message}`));
|
|
1509
|
-
}
|
|
1510
|
-
if (typecheckErrors.length > 5) {
|
|
1511
|
-
log(chalk4.dim(` ... and ${typecheckErrors.length - 5} more`));
|
|
1512
|
-
}
|
|
1513
|
-
} else {
|
|
1514
|
-
log(chalk4.green(` Examples: ✓ validated`));
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
const hasStaleRefs = staleRefs.length > 0;
|
|
1518
|
-
if (hasStaleRefs) {
|
|
1519
|
-
log(chalk4.yellow(` Docs: ${staleRefs.length} stale ref(s)`));
|
|
1520
|
-
for (const ref of staleRefs.slice(0, 5)) {
|
|
1521
|
-
log(chalk4.dim(` ${ref.file}:${ref.line} - "${ref.exportName}"`));
|
|
1522
|
-
}
|
|
1523
|
-
if (staleRefs.length > 5) {
|
|
1524
|
-
log(chalk4.dim(` ... and ${staleRefs.length - 5} more`));
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
log("");
|
|
1528
|
-
const failed = coverageFailed || driftFailed || hasTypecheckErrors || hasStaleRefs;
|
|
1529
|
-
if (!failed) {
|
|
1530
|
-
const thresholdParts = [];
|
|
1531
|
-
thresholdParts.push(`coverage ${coverageScore}% ≥ ${minCoverage}%`);
|
|
1532
|
-
if (maxDrift !== undefined) {
|
|
1533
|
-
thresholdParts.push(`drift ${driftScore}% ≤ ${maxDrift}%`);
|
|
1534
|
-
}
|
|
1535
|
-
log(chalk4.green(`✓ Check passed (${thresholdParts.join(", ")})`));
|
|
1536
|
-
return;
|
|
1537
|
-
}
|
|
1538
|
-
if (hasTypecheckErrors) {
|
|
1539
|
-
log(chalk4.red(`✗ ${typecheckErrors.length} example type errors`));
|
|
1540
|
-
}
|
|
1541
|
-
if (hasStaleRefs) {
|
|
1542
|
-
log(chalk4.red(`✗ ${staleRefs.length} stale references in docs`));
|
|
1726
|
+
const passed = displayTextOutput({
|
|
1727
|
+
spec,
|
|
1728
|
+
coverageScore,
|
|
1729
|
+
minCoverage,
|
|
1730
|
+
maxDrift,
|
|
1731
|
+
driftExports,
|
|
1732
|
+
typecheckErrors,
|
|
1733
|
+
staleRefs,
|
|
1734
|
+
exampleResult,
|
|
1735
|
+
specWarnings,
|
|
1736
|
+
specInfos
|
|
1737
|
+
}, { log });
|
|
1738
|
+
if (!passed) {
|
|
1739
|
+
process.exit(1);
|
|
1543
1740
|
}
|
|
1544
|
-
log("");
|
|
1545
|
-
log(chalk4.dim("Use --format json or --format markdown for detailed reports"));
|
|
1546
|
-
process.exit(1);
|
|
1547
1741
|
} catch (commandError) {
|
|
1548
|
-
error(
|
|
1742
|
+
error(chalk6.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
|
|
1549
1743
|
process.exit(1);
|
|
1550
1744
|
}
|
|
1551
1745
|
});
|
|
1552
1746
|
}
|
|
1553
|
-
function collectDrift(exportsList) {
|
|
1554
|
-
const drifts = [];
|
|
1555
|
-
for (const entry of exportsList) {
|
|
1556
|
-
const drift = entry.docs?.drift;
|
|
1557
|
-
if (!drift || drift.length === 0) {
|
|
1558
|
-
continue;
|
|
1559
|
-
}
|
|
1560
|
-
for (const d of drift) {
|
|
1561
|
-
drifts.push({
|
|
1562
|
-
name: entry.name,
|
|
1563
|
-
type: d.type,
|
|
1564
|
-
issue: d.issue ?? "Documentation drift detected.",
|
|
1565
|
-
suggestion: d.suggestion,
|
|
1566
|
-
category: DRIFT_CATEGORIES2[d.type]
|
|
1567
|
-
});
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
return drifts;
|
|
1571
|
-
}
|
|
1572
|
-
function collect(value, previous) {
|
|
1573
|
-
return previous.concat([value]);
|
|
1574
|
-
}
|
|
1575
|
-
async function loadMarkdownFiles(patterns, cwd) {
|
|
1576
|
-
const files = [];
|
|
1577
|
-
for (const pattern of patterns) {
|
|
1578
|
-
const matches = await glob(pattern, { nodir: true, cwd });
|
|
1579
|
-
for (const filePath of matches) {
|
|
1580
|
-
try {
|
|
1581
|
-
const fullPath = path4.resolve(cwd, filePath);
|
|
1582
|
-
const content = fs2.readFileSync(fullPath, "utf-8");
|
|
1583
|
-
files.push({ path: filePath, content });
|
|
1584
|
-
} catch {}
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
return parseMarkdownFiles(files);
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
1747
|
// src/commands/diff.ts
|
|
1591
|
-
import * as
|
|
1592
|
-
import * as
|
|
1748
|
+
import * as fs4 from "node:fs";
|
|
1749
|
+
import * as path6 from "node:path";
|
|
1593
1750
|
import {
|
|
1594
1751
|
diffSpecWithDocs,
|
|
1595
1752
|
ensureSpecCoverage,
|
|
@@ -1600,10 +1757,10 @@ import {
|
|
|
1600
1757
|
parseMarkdownFiles as parseMarkdownFiles2
|
|
1601
1758
|
} from "@doccov/sdk";
|
|
1602
1759
|
import { calculateNextVersion, recommendSemverBump } from "@openpkg-ts/spec";
|
|
1603
|
-
import
|
|
1760
|
+
import chalk7 from "chalk";
|
|
1604
1761
|
import { glob as glob2 } from "glob";
|
|
1605
1762
|
var defaultDependencies2 = {
|
|
1606
|
-
readFileSync:
|
|
1763
|
+
readFileSync: fs4.readFileSync,
|
|
1607
1764
|
log: console.log,
|
|
1608
1765
|
error: console.error
|
|
1609
1766
|
};
|
|
@@ -1641,12 +1798,12 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
1641
1798
|
const baseHash = hashString(JSON.stringify(baseSpec));
|
|
1642
1799
|
const headHash = hashString(JSON.stringify(headSpec));
|
|
1643
1800
|
const cacheEnabled = options.cache !== false;
|
|
1644
|
-
const cachedReportPath =
|
|
1801
|
+
const cachedReportPath = path6.resolve(options.cwd, getDiffReportPath(baseHash, headHash, "json"));
|
|
1645
1802
|
let diff;
|
|
1646
1803
|
let fromCache = false;
|
|
1647
|
-
if (cacheEnabled &&
|
|
1804
|
+
if (cacheEnabled && fs4.existsSync(cachedReportPath)) {
|
|
1648
1805
|
try {
|
|
1649
|
-
const cached = JSON.parse(
|
|
1806
|
+
const cached = JSON.parse(fs4.readFileSync(cachedReportPath, "utf-8"));
|
|
1650
1807
|
diff = cached;
|
|
1651
1808
|
fromCache = true;
|
|
1652
1809
|
} catch {
|
|
@@ -1673,9 +1830,9 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
1673
1830
|
}, null, 2));
|
|
1674
1831
|
} else {
|
|
1675
1832
|
log("");
|
|
1676
|
-
log(
|
|
1833
|
+
log(chalk7.bold("Semver Recommendation"));
|
|
1677
1834
|
log(` Current version: ${currentVersion}`);
|
|
1678
|
-
log(` Recommended: ${
|
|
1835
|
+
log(` Recommended: ${chalk7.cyan(nextVersion)} (${chalk7.yellow(recommendation.bump.toUpperCase())})`);
|
|
1679
1836
|
log(` Reason: ${recommendation.reason}`);
|
|
1680
1837
|
}
|
|
1681
1838
|
return;
|
|
@@ -1683,8 +1840,8 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
1683
1840
|
const format = options.format ?? "text";
|
|
1684
1841
|
const limit = parseInt(options.limit, 10) || 10;
|
|
1685
1842
|
const checks = getStrictChecks(options.strict);
|
|
1686
|
-
const baseName =
|
|
1687
|
-
const headName =
|
|
1843
|
+
const baseName = path6.basename(baseFile);
|
|
1844
|
+
const headName = path6.basename(headFile);
|
|
1688
1845
|
const reportData = {
|
|
1689
1846
|
baseName,
|
|
1690
1847
|
headName,
|
|
@@ -1704,8 +1861,8 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
1704
1861
|
silent: true
|
|
1705
1862
|
});
|
|
1706
1863
|
}
|
|
1707
|
-
const cacheNote = fromCache ?
|
|
1708
|
-
log(
|
|
1864
|
+
const cacheNote = fromCache ? chalk7.cyan(" (cached)") : "";
|
|
1865
|
+
log(chalk7.dim(`Report: ${jsonPath}`) + cacheNote);
|
|
1709
1866
|
}
|
|
1710
1867
|
break;
|
|
1711
1868
|
case "json": {
|
|
@@ -1763,7 +1920,10 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
1763
1920
|
sha: options.sha,
|
|
1764
1921
|
minCoverage,
|
|
1765
1922
|
limit,
|
|
1766
|
-
semverBump: {
|
|
1923
|
+
semverBump: {
|
|
1924
|
+
bump: semverRecommendation.bump,
|
|
1925
|
+
reason: semverRecommendation.reason
|
|
1926
|
+
}
|
|
1767
1927
|
});
|
|
1768
1928
|
log(content);
|
|
1769
1929
|
break;
|
|
@@ -1797,18 +1957,18 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
1797
1957
|
checks
|
|
1798
1958
|
});
|
|
1799
1959
|
if (failures.length > 0) {
|
|
1800
|
-
log(
|
|
1960
|
+
log(chalk7.red(`
|
|
1801
1961
|
✗ Check failed`));
|
|
1802
1962
|
for (const f of failures) {
|
|
1803
|
-
log(
|
|
1963
|
+
log(chalk7.red(` - ${f}`));
|
|
1804
1964
|
}
|
|
1805
1965
|
process.exitCode = 1;
|
|
1806
1966
|
} else if (options.strict || minCoverage !== undefined || maxDrift !== undefined) {
|
|
1807
|
-
log(
|
|
1967
|
+
log(chalk7.green(`
|
|
1808
1968
|
✓ All checks passed`));
|
|
1809
1969
|
}
|
|
1810
1970
|
} catch (commandError) {
|
|
1811
|
-
error(
|
|
1971
|
+
error(chalk7.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
|
|
1812
1972
|
process.exitCode = 1;
|
|
1813
1973
|
}
|
|
1814
1974
|
});
|
|
@@ -1822,7 +1982,7 @@ async function loadMarkdownFiles2(patterns) {
|
|
|
1822
1982
|
const matches = await glob2(pattern, { nodir: true });
|
|
1823
1983
|
for (const filePath of matches) {
|
|
1824
1984
|
try {
|
|
1825
|
-
const content =
|
|
1985
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
1826
1986
|
files.push({ path: filePath, content });
|
|
1827
1987
|
} catch {}
|
|
1828
1988
|
}
|
|
@@ -1835,7 +1995,7 @@ async function generateDiff(baseSpec, headSpec, options, config, log) {
|
|
|
1835
1995
|
if (!docsPatterns || docsPatterns.length === 0) {
|
|
1836
1996
|
if (config?.docs?.include) {
|
|
1837
1997
|
docsPatterns = config.docs.include;
|
|
1838
|
-
log(
|
|
1998
|
+
log(chalk7.gray(`Using docs patterns from config: ${docsPatterns.join(", ")}`));
|
|
1839
1999
|
}
|
|
1840
2000
|
}
|
|
1841
2001
|
if (docsPatterns && docsPatterns.length > 0) {
|
|
@@ -1844,8 +2004,8 @@ async function generateDiff(baseSpec, headSpec, options, config, log) {
|
|
|
1844
2004
|
return diffSpecWithDocs(baseSpec, headSpec, { markdownFiles });
|
|
1845
2005
|
}
|
|
1846
2006
|
function loadSpec(filePath, readFileSync3) {
|
|
1847
|
-
const resolvedPath =
|
|
1848
|
-
if (!
|
|
2007
|
+
const resolvedPath = path6.resolve(filePath);
|
|
2008
|
+
if (!fs4.existsSync(resolvedPath)) {
|
|
1849
2009
|
throw new Error(`File not found: ${filePath}`);
|
|
1850
2010
|
}
|
|
1851
2011
|
try {
|
|
@@ -1858,37 +2018,37 @@ function loadSpec(filePath, readFileSync3) {
|
|
|
1858
2018
|
}
|
|
1859
2019
|
function printSummary(diff, baseName, headName, fromCache, log) {
|
|
1860
2020
|
log("");
|
|
1861
|
-
const cacheIndicator = fromCache ?
|
|
1862
|
-
log(
|
|
2021
|
+
const cacheIndicator = fromCache ? chalk7.cyan(" (cached)") : "";
|
|
2022
|
+
log(chalk7.bold(`Comparing: ${baseName} → ${headName}`) + cacheIndicator);
|
|
1863
2023
|
log("─".repeat(40));
|
|
1864
2024
|
log("");
|
|
1865
|
-
const coverageColor = diff.coverageDelta > 0 ?
|
|
2025
|
+
const coverageColor = diff.coverageDelta > 0 ? chalk7.green : diff.coverageDelta < 0 ? chalk7.red : chalk7.gray;
|
|
1866
2026
|
const coverageSign = diff.coverageDelta > 0 ? "+" : "";
|
|
1867
2027
|
log(` Coverage: ${diff.oldCoverage}% → ${diff.newCoverage}% ${coverageColor(`(${coverageSign}${diff.coverageDelta}%)`)}`);
|
|
1868
2028
|
const breakingCount = diff.breaking.length;
|
|
1869
2029
|
const highSeverity = diff.categorizedBreaking?.filter((c) => c.severity === "high").length ?? 0;
|
|
1870
2030
|
if (breakingCount > 0) {
|
|
1871
|
-
const severityNote = highSeverity > 0 ?
|
|
1872
|
-
log(` Breaking: ${
|
|
2031
|
+
const severityNote = highSeverity > 0 ? chalk7.red(` (${highSeverity} high severity)`) : "";
|
|
2032
|
+
log(` Breaking: ${chalk7.red(breakingCount)} changes${severityNote}`);
|
|
1873
2033
|
} else {
|
|
1874
|
-
log(` Breaking: ${
|
|
2034
|
+
log(` Breaking: ${chalk7.green("0")} changes`);
|
|
1875
2035
|
}
|
|
1876
2036
|
const newCount = diff.nonBreaking.length;
|
|
1877
2037
|
const undocCount = diff.newUndocumented.length;
|
|
1878
2038
|
if (newCount > 0) {
|
|
1879
|
-
const undocNote = undocCount > 0 ?
|
|
1880
|
-
log(` New: ${
|
|
2039
|
+
const undocNote = undocCount > 0 ? chalk7.yellow(` (${undocCount} undocumented)`) : "";
|
|
2040
|
+
log(` New: ${chalk7.green(newCount)} exports${undocNote}`);
|
|
1881
2041
|
}
|
|
1882
2042
|
if (diff.driftIntroduced > 0 || diff.driftResolved > 0) {
|
|
1883
2043
|
const parts = [];
|
|
1884
2044
|
if (diff.driftIntroduced > 0)
|
|
1885
|
-
parts.push(
|
|
2045
|
+
parts.push(chalk7.red(`+${diff.driftIntroduced}`));
|
|
1886
2046
|
if (diff.driftResolved > 0)
|
|
1887
|
-
parts.push(
|
|
2047
|
+
parts.push(chalk7.green(`-${diff.driftResolved}`));
|
|
1888
2048
|
log(` Drift: ${parts.join(", ")}`);
|
|
1889
2049
|
}
|
|
1890
2050
|
const recommendation = recommendSemverBump(diff);
|
|
1891
|
-
const bumpColor = recommendation.bump === "major" ?
|
|
2051
|
+
const bumpColor = recommendation.bump === "major" ? chalk7.red : recommendation.bump === "minor" ? chalk7.yellow : chalk7.green;
|
|
1892
2052
|
log(` Semver: ${bumpColor(recommendation.bump.toUpperCase())} (${recommendation.reason})`);
|
|
1893
2053
|
log("");
|
|
1894
2054
|
}
|
|
@@ -1970,7 +2130,7 @@ function printGitHubAnnotations(diff, log) {
|
|
|
1970
2130
|
|
|
1971
2131
|
// src/commands/info.ts
|
|
1972
2132
|
import { DocCov as DocCov2, enrichSpec as enrichSpec2, NodeFileSystem as NodeFileSystem2, resolveTarget as resolveTarget2 } from "@doccov/sdk";
|
|
1973
|
-
import
|
|
2133
|
+
import chalk8 from "chalk";
|
|
1974
2134
|
function registerInfoCommand(program) {
|
|
1975
2135
|
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) => {
|
|
1976
2136
|
try {
|
|
@@ -1992,28 +2152,28 @@ function registerInfoCommand(program) {
|
|
|
1992
2152
|
const spec = enrichSpec2(specResult.spec);
|
|
1993
2153
|
const stats = computeStats(spec);
|
|
1994
2154
|
console.log("");
|
|
1995
|
-
console.log(
|
|
2155
|
+
console.log(chalk8.bold(`${stats.packageName}@${stats.version}`));
|
|
1996
2156
|
console.log("");
|
|
1997
|
-
console.log(` Exports: ${
|
|
1998
|
-
console.log(` Coverage: ${
|
|
1999
|
-
console.log(` Drift: ${
|
|
2157
|
+
console.log(` Exports: ${chalk8.bold(stats.totalExports.toString())}`);
|
|
2158
|
+
console.log(` Coverage: ${chalk8.bold(`${stats.coverageScore}%`)}`);
|
|
2159
|
+
console.log(` Drift: ${chalk8.bold(`${stats.driftScore}%`)}`);
|
|
2000
2160
|
console.log("");
|
|
2001
2161
|
} catch (err) {
|
|
2002
|
-
console.error(
|
|
2162
|
+
console.error(chalk8.red("Error:"), err instanceof Error ? err.message : err);
|
|
2003
2163
|
process.exit(1);
|
|
2004
2164
|
}
|
|
2005
2165
|
});
|
|
2006
2166
|
}
|
|
2007
2167
|
|
|
2008
2168
|
// src/commands/init.ts
|
|
2009
|
-
import * as
|
|
2010
|
-
import * as
|
|
2011
|
-
import
|
|
2169
|
+
import * as fs5 from "node:fs";
|
|
2170
|
+
import * as path7 from "node:path";
|
|
2171
|
+
import chalk9 from "chalk";
|
|
2012
2172
|
var defaultDependencies3 = {
|
|
2013
|
-
fileExists:
|
|
2014
|
-
writeFileSync:
|
|
2015
|
-
readFileSync:
|
|
2016
|
-
mkdirSync:
|
|
2173
|
+
fileExists: fs5.existsSync,
|
|
2174
|
+
writeFileSync: fs5.writeFileSync,
|
|
2175
|
+
readFileSync: fs5.readFileSync,
|
|
2176
|
+
mkdirSync: fs5.mkdirSync,
|
|
2017
2177
|
log: console.log,
|
|
2018
2178
|
error: console.error
|
|
2019
2179
|
};
|
|
@@ -2023,56 +2183,56 @@ function registerInitCommand(program, dependencies = {}) {
|
|
|
2023
2183
|
...dependencies
|
|
2024
2184
|
};
|
|
2025
2185
|
program.command("init").description("Initialize DocCov: config, GitHub Action, and badge").option("--cwd <dir>", "Working directory", process.cwd()).option("--skip-action", "Skip GitHub Action workflow creation").action((options) => {
|
|
2026
|
-
const cwd =
|
|
2186
|
+
const cwd = path7.resolve(options.cwd);
|
|
2027
2187
|
const existing = findExistingConfig(cwd, fileExists2);
|
|
2028
2188
|
if (existing) {
|
|
2029
|
-
error(
|
|
2189
|
+
error(chalk9.red(`A DocCov config already exists at ${path7.relative(cwd, existing) || "./doccov.config.*"}.`));
|
|
2030
2190
|
process.exitCode = 1;
|
|
2031
2191
|
return;
|
|
2032
2192
|
}
|
|
2033
2193
|
const packageType = detectPackageType(cwd, fileExists2, readFileSync4);
|
|
2034
2194
|
const targetFormat = packageType === "module" ? "ts" : "mts";
|
|
2035
2195
|
const fileName = `doccov.config.${targetFormat}`;
|
|
2036
|
-
const outputPath =
|
|
2196
|
+
const outputPath = path7.join(cwd, fileName);
|
|
2037
2197
|
if (fileExists2(outputPath)) {
|
|
2038
|
-
error(
|
|
2198
|
+
error(chalk9.red(`Cannot create ${fileName}; file already exists.`));
|
|
2039
2199
|
process.exitCode = 1;
|
|
2040
2200
|
return;
|
|
2041
2201
|
}
|
|
2042
2202
|
const template = buildConfigTemplate();
|
|
2043
2203
|
writeFileSync3(outputPath, template, { encoding: "utf8" });
|
|
2044
|
-
log(
|
|
2204
|
+
log(chalk9.green(`✓ Created ${fileName}`));
|
|
2045
2205
|
if (!options.skipAction) {
|
|
2046
|
-
const workflowDir =
|
|
2047
|
-
const workflowPath =
|
|
2206
|
+
const workflowDir = path7.join(cwd, ".github", "workflows");
|
|
2207
|
+
const workflowPath = path7.join(workflowDir, "doccov.yml");
|
|
2048
2208
|
if (!fileExists2(workflowPath)) {
|
|
2049
2209
|
mkdirSync3(workflowDir, { recursive: true });
|
|
2050
2210
|
writeFileSync3(workflowPath, buildWorkflowTemplate(), { encoding: "utf8" });
|
|
2051
|
-
log(
|
|
2211
|
+
log(chalk9.green(`✓ Created .github/workflows/doccov.yml`));
|
|
2052
2212
|
} else {
|
|
2053
|
-
log(
|
|
2213
|
+
log(chalk9.yellow(` Skipped .github/workflows/doccov.yml (already exists)`));
|
|
2054
2214
|
}
|
|
2055
2215
|
}
|
|
2056
2216
|
const repoInfo = detectRepoInfo(cwd, fileExists2, readFileSync4);
|
|
2057
2217
|
log("");
|
|
2058
|
-
log(
|
|
2218
|
+
log(chalk9.bold("Add this badge to your README:"));
|
|
2059
2219
|
log("");
|
|
2060
2220
|
if (repoInfo) {
|
|
2061
|
-
log(
|
|
2221
|
+
log(chalk9.cyan(`[](https://doccov.dev/${repoInfo.owner}/${repoInfo.repo})`));
|
|
2062
2222
|
} else {
|
|
2063
|
-
log(
|
|
2064
|
-
log(
|
|
2223
|
+
log(chalk9.cyan(`[](https://doccov.dev/OWNER/REPO)`));
|
|
2224
|
+
log(chalk9.dim(" Replace OWNER/REPO with your GitHub repo"));
|
|
2065
2225
|
}
|
|
2066
2226
|
log("");
|
|
2067
|
-
log(
|
|
2227
|
+
log(chalk9.dim("Run `doccov check` to verify your documentation coverage"));
|
|
2068
2228
|
});
|
|
2069
2229
|
}
|
|
2070
2230
|
var findExistingConfig = (cwd, fileExists2) => {
|
|
2071
|
-
let current =
|
|
2072
|
-
const { root } =
|
|
2231
|
+
let current = path7.resolve(cwd);
|
|
2232
|
+
const { root } = path7.parse(current);
|
|
2073
2233
|
while (true) {
|
|
2074
2234
|
for (const candidate of DOCCOV_CONFIG_FILENAMES) {
|
|
2075
|
-
const candidatePath =
|
|
2235
|
+
const candidatePath = path7.join(current, candidate);
|
|
2076
2236
|
if (fileExists2(candidatePath)) {
|
|
2077
2237
|
return candidatePath;
|
|
2078
2238
|
}
|
|
@@ -2080,7 +2240,7 @@ var findExistingConfig = (cwd, fileExists2) => {
|
|
|
2080
2240
|
if (current === root) {
|
|
2081
2241
|
break;
|
|
2082
2242
|
}
|
|
2083
|
-
current =
|
|
2243
|
+
current = path7.dirname(current);
|
|
2084
2244
|
}
|
|
2085
2245
|
return null;
|
|
2086
2246
|
};
|
|
@@ -2102,17 +2262,17 @@ var detectPackageType = (cwd, fileExists2, readFileSync4) => {
|
|
|
2102
2262
|
return;
|
|
2103
2263
|
};
|
|
2104
2264
|
var findNearestPackageJson = (cwd, fileExists2) => {
|
|
2105
|
-
let current =
|
|
2106
|
-
const { root } =
|
|
2265
|
+
let current = path7.resolve(cwd);
|
|
2266
|
+
const { root } = path7.parse(current);
|
|
2107
2267
|
while (true) {
|
|
2108
|
-
const candidate =
|
|
2268
|
+
const candidate = path7.join(current, "package.json");
|
|
2109
2269
|
if (fileExists2(candidate)) {
|
|
2110
2270
|
return candidate;
|
|
2111
2271
|
}
|
|
2112
2272
|
if (current === root) {
|
|
2113
2273
|
break;
|
|
2114
2274
|
}
|
|
2115
|
-
current =
|
|
2275
|
+
current = path7.dirname(current);
|
|
2116
2276
|
}
|
|
2117
2277
|
return null;
|
|
2118
2278
|
};
|
|
@@ -2174,7 +2334,7 @@ var detectRepoInfo = (cwd, fileExists2, readFileSync4) => {
|
|
|
2174
2334
|
}
|
|
2175
2335
|
} catch {}
|
|
2176
2336
|
}
|
|
2177
|
-
const gitConfigPath =
|
|
2337
|
+
const gitConfigPath = path7.join(cwd, ".git", "config");
|
|
2178
2338
|
if (fileExists2(gitConfigPath)) {
|
|
2179
2339
|
try {
|
|
2180
2340
|
const config = readFileSync4(gitConfigPath, "utf8");
|
|
@@ -2188,18 +2348,24 @@ var detectRepoInfo = (cwd, fileExists2, readFileSync4) => {
|
|
|
2188
2348
|
};
|
|
2189
2349
|
|
|
2190
2350
|
// src/commands/spec.ts
|
|
2191
|
-
import * as
|
|
2192
|
-
import * as
|
|
2193
|
-
import {
|
|
2351
|
+
import * as fs6 from "node:fs";
|
|
2352
|
+
import * as path8 from "node:path";
|
|
2353
|
+
import {
|
|
2354
|
+
DocCov as DocCov3,
|
|
2355
|
+
detectPackageManager,
|
|
2356
|
+
NodeFileSystem as NodeFileSystem3,
|
|
2357
|
+
renderApiSurface,
|
|
2358
|
+
resolveTarget as resolveTarget3
|
|
2359
|
+
} from "@doccov/sdk";
|
|
2194
2360
|
import { normalize, validateSpec } from "@openpkg-ts/spec";
|
|
2195
|
-
import
|
|
2361
|
+
import chalk10 from "chalk";
|
|
2196
2362
|
// package.json
|
|
2197
|
-
var version = "0.
|
|
2363
|
+
var version = "0.22.0";
|
|
2198
2364
|
|
|
2199
2365
|
// src/commands/spec.ts
|
|
2200
2366
|
var defaultDependencies4 = {
|
|
2201
2367
|
createDocCov: (options) => new DocCov3(options),
|
|
2202
|
-
writeFileSync:
|
|
2368
|
+
writeFileSync: fs6.writeFileSync,
|
|
2203
2369
|
log: console.log,
|
|
2204
2370
|
error: console.error
|
|
2205
2371
|
};
|
|
@@ -2208,8 +2374,8 @@ function getArrayLength(value) {
|
|
|
2208
2374
|
}
|
|
2209
2375
|
function formatDiagnosticOutput(prefix, diagnostic, baseDir) {
|
|
2210
2376
|
const location = diagnostic.location;
|
|
2211
|
-
const relativePath = location?.file ?
|
|
2212
|
-
const locationText = location && relativePath ?
|
|
2377
|
+
const relativePath = location?.file ? path8.relative(baseDir, location.file) || location.file : undefined;
|
|
2378
|
+
const locationText = location && relativePath ? chalk10.gray(`${relativePath}:${location.line ?? 1}:${location.column ?? 1}`) : null;
|
|
2213
2379
|
const locationPrefix = locationText ? `${locationText} ` : "";
|
|
2214
2380
|
return `${prefix} ${locationPrefix}${diagnostic.message}`;
|
|
2215
2381
|
}
|
|
@@ -2240,7 +2406,7 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
2240
2406
|
try {
|
|
2241
2407
|
config = await loadDocCovConfig(targetDir);
|
|
2242
2408
|
} catch (configError) {
|
|
2243
|
-
error(
|
|
2409
|
+
error(chalk10.red("Failed to load DocCov config:"), configError instanceof Error ? configError.message : configError);
|
|
2244
2410
|
process.exit(1);
|
|
2245
2411
|
}
|
|
2246
2412
|
steps.next();
|
|
@@ -2259,7 +2425,7 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
2259
2425
|
schemaExtraction: options.runtime ? "hybrid" : "static"
|
|
2260
2426
|
});
|
|
2261
2427
|
const generationInput = {
|
|
2262
|
-
entryPoint:
|
|
2428
|
+
entryPoint: path8.relative(targetDir, entryFile),
|
|
2263
2429
|
entryPointSource: entryPointInfo.source,
|
|
2264
2430
|
isDeclarationOnly: entryPointInfo.isDeclarationOnly ?? false,
|
|
2265
2431
|
generatorName: "@doccov/cli",
|
|
@@ -2284,15 +2450,15 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
2284
2450
|
const normalized = normalize(result.spec);
|
|
2285
2451
|
const validation = validateSpec(normalized);
|
|
2286
2452
|
if (!validation.ok) {
|
|
2287
|
-
error(
|
|
2453
|
+
error(chalk10.red("Spec failed schema validation"));
|
|
2288
2454
|
for (const err of validation.errors) {
|
|
2289
|
-
error(
|
|
2455
|
+
error(chalk10.red(`schema: ${err.instancePath || "/"} ${err.message}`));
|
|
2290
2456
|
}
|
|
2291
2457
|
process.exit(1);
|
|
2292
2458
|
}
|
|
2293
2459
|
steps.next();
|
|
2294
2460
|
const format = options.format ?? "json";
|
|
2295
|
-
const outputPath =
|
|
2461
|
+
const outputPath = path8.resolve(options.cwd, options.output);
|
|
2296
2462
|
if (format === "api-surface") {
|
|
2297
2463
|
const apiSurface = renderApiSurface(normalized);
|
|
2298
2464
|
writeFileSync4(outputPath, apiSurface);
|
|
@@ -2301,70 +2467,78 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
2301
2467
|
writeFileSync4(outputPath, JSON.stringify(normalized, null, 2));
|
|
2302
2468
|
steps.complete(`Generated ${options.output}`);
|
|
2303
2469
|
}
|
|
2304
|
-
log(
|
|
2305
|
-
log(
|
|
2470
|
+
log(chalk10.gray(` ${getArrayLength(normalized.exports)} exports`));
|
|
2471
|
+
log(chalk10.gray(` ${getArrayLength(normalized.types)} types`));
|
|
2472
|
+
const schemaExtraction = normalized.generation?.analysis?.schemaExtraction;
|
|
2473
|
+
if (options.runtime && (!schemaExtraction?.runtimeCount || schemaExtraction.runtimeCount === 0)) {
|
|
2474
|
+
const pm = await detectPackageManager(fileSystem);
|
|
2475
|
+
const buildCmd = pm.name === "npm" ? "npm run build" : `${pm.name} run build`;
|
|
2476
|
+
log("");
|
|
2477
|
+
log(chalk10.yellow("⚠ Runtime extraction requested but no schemas extracted."));
|
|
2478
|
+
log(chalk10.yellow(` Ensure project is built (${buildCmd}) and dist/ exists.`));
|
|
2479
|
+
}
|
|
2306
2480
|
if (options.verbose && normalized.generation) {
|
|
2307
2481
|
const gen = normalized.generation;
|
|
2308
2482
|
log("");
|
|
2309
|
-
log(
|
|
2310
|
-
log(
|
|
2311
|
-
log(
|
|
2312
|
-
log(
|
|
2313
|
-
log(
|
|
2314
|
-
log(
|
|
2315
|
-
log(
|
|
2483
|
+
log(chalk10.bold("Generation Info"));
|
|
2484
|
+
log(chalk10.gray(` Timestamp: ${gen.timestamp}`));
|
|
2485
|
+
log(chalk10.gray(` Generator: ${gen.generator.name}@${gen.generator.version}`));
|
|
2486
|
+
log(chalk10.gray(` Entry point: ${gen.analysis.entryPoint}`));
|
|
2487
|
+
log(chalk10.gray(` Detected via: ${gen.analysis.entryPointSource}`));
|
|
2488
|
+
log(chalk10.gray(` Declaration only: ${gen.analysis.isDeclarationOnly ? "yes" : "no"}`));
|
|
2489
|
+
log(chalk10.gray(` External types: ${gen.analysis.resolvedExternalTypes ? "resolved" : "skipped"}`));
|
|
2316
2490
|
if (gen.analysis.maxTypeDepth) {
|
|
2317
|
-
log(
|
|
2491
|
+
log(chalk10.gray(` Max type depth: ${gen.analysis.maxTypeDepth}`));
|
|
2318
2492
|
}
|
|
2319
2493
|
if (gen.analysis.schemaExtraction) {
|
|
2320
2494
|
const se = gen.analysis.schemaExtraction;
|
|
2321
|
-
log(
|
|
2495
|
+
log(chalk10.gray(` Schema extraction: ${se.method}`));
|
|
2322
2496
|
if (se.runtimeCount) {
|
|
2323
|
-
log(
|
|
2497
|
+
log(chalk10.gray(` Runtime schemas: ${se.runtimeCount} (${se.vendors?.join(", ")})`));
|
|
2324
2498
|
}
|
|
2325
2499
|
}
|
|
2326
2500
|
log("");
|
|
2327
|
-
log(
|
|
2328
|
-
log(
|
|
2501
|
+
log(chalk10.bold("Environment"));
|
|
2502
|
+
log(chalk10.gray(` node_modules: ${gen.environment.hasNodeModules ? "found" : "not found"}`));
|
|
2329
2503
|
if (gen.environment.packageManager) {
|
|
2330
|
-
log(
|
|
2504
|
+
log(chalk10.gray(` Package manager: ${gen.environment.packageManager}`));
|
|
2331
2505
|
}
|
|
2332
2506
|
if (gen.environment.isMonorepo) {
|
|
2333
|
-
log(
|
|
2507
|
+
log(chalk10.gray(` Monorepo: yes`));
|
|
2334
2508
|
}
|
|
2335
2509
|
if (gen.environment.targetPackage) {
|
|
2336
|
-
log(
|
|
2510
|
+
log(chalk10.gray(` Target package: ${gen.environment.targetPackage}`));
|
|
2337
2511
|
}
|
|
2338
2512
|
if (gen.issues.length > 0) {
|
|
2339
2513
|
log("");
|
|
2340
|
-
log(
|
|
2514
|
+
log(chalk10.bold("Issues"));
|
|
2341
2515
|
for (const issue of gen.issues) {
|
|
2342
|
-
const prefix = issue.severity === "error" ?
|
|
2516
|
+
const prefix = issue.severity === "error" ? chalk10.red(">") : issue.severity === "warning" ? chalk10.yellow(">") : chalk10.cyan(">");
|
|
2343
2517
|
log(`${prefix} [${issue.code}] ${issue.message}`);
|
|
2344
2518
|
if (issue.suggestion) {
|
|
2345
|
-
log(
|
|
2519
|
+
log(chalk10.gray(` ${issue.suggestion}`));
|
|
2346
2520
|
}
|
|
2347
2521
|
}
|
|
2348
2522
|
}
|
|
2349
2523
|
}
|
|
2350
2524
|
if (options.showDiagnostics && result.diagnostics.length > 0) {
|
|
2351
2525
|
log("");
|
|
2352
|
-
log(
|
|
2526
|
+
log(chalk10.bold("Diagnostics"));
|
|
2353
2527
|
for (const diagnostic of result.diagnostics) {
|
|
2354
|
-
const prefix = diagnostic.severity === "error" ?
|
|
2528
|
+
const prefix = diagnostic.severity === "error" ? chalk10.red(">") : diagnostic.severity === "warning" ? chalk10.yellow(">") : chalk10.cyan(">");
|
|
2355
2529
|
log(formatDiagnosticOutput(prefix, diagnostic, targetDir));
|
|
2356
2530
|
}
|
|
2357
2531
|
}
|
|
2358
2532
|
} catch (commandError) {
|
|
2359
|
-
error(
|
|
2533
|
+
error(chalk10.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
|
|
2360
2534
|
process.exit(1);
|
|
2361
2535
|
}
|
|
2362
2536
|
});
|
|
2363
2537
|
}
|
|
2364
2538
|
|
|
2365
2539
|
// src/commands/trends.ts
|
|
2366
|
-
import * as
|
|
2367
|
-
import * as
|
|
2540
|
+
import * as fs7 from "node:fs";
|
|
2541
|
+
import * as path9 from "node:path";
|
|
2368
2542
|
import {
|
|
2369
2543
|
formatDelta as formatDelta2,
|
|
2370
2544
|
getExtendedTrend,
|
|
@@ -2372,11 +2546,11 @@ import {
|
|
|
2372
2546
|
loadSnapshots,
|
|
2373
2547
|
pruneByTier,
|
|
2374
2548
|
pruneHistory,
|
|
2375
|
-
renderSparkline,
|
|
2376
2549
|
RETENTION_DAYS,
|
|
2550
|
+
renderSparkline,
|
|
2377
2551
|
saveSnapshot
|
|
2378
2552
|
} from "@doccov/sdk";
|
|
2379
|
-
import
|
|
2553
|
+
import chalk11 from "chalk";
|
|
2380
2554
|
function formatDate(timestamp) {
|
|
2381
2555
|
const date = new Date(timestamp);
|
|
2382
2556
|
return date.toLocaleDateString("en-US", {
|
|
@@ -2389,19 +2563,19 @@ function formatDate(timestamp) {
|
|
|
2389
2563
|
}
|
|
2390
2564
|
function getColorForScore(score) {
|
|
2391
2565
|
if (score >= 90)
|
|
2392
|
-
return
|
|
2566
|
+
return chalk11.green;
|
|
2393
2567
|
if (score >= 70)
|
|
2394
|
-
return
|
|
2568
|
+
return chalk11.yellow;
|
|
2395
2569
|
if (score >= 50)
|
|
2396
|
-
return
|
|
2397
|
-
return
|
|
2570
|
+
return chalk11.hex("#FFA500");
|
|
2571
|
+
return chalk11.red;
|
|
2398
2572
|
}
|
|
2399
2573
|
function formatSnapshot(snapshot) {
|
|
2400
2574
|
const color = getColorForScore(snapshot.coverageScore);
|
|
2401
2575
|
const date = formatDate(snapshot.timestamp);
|
|
2402
2576
|
const version2 = snapshot.version ? ` v${snapshot.version}` : "";
|
|
2403
|
-
const commit = snapshot.commit ?
|
|
2404
|
-
return `${
|
|
2577
|
+
const commit = snapshot.commit ? chalk11.gray(` (${snapshot.commit.slice(0, 7)})`) : "";
|
|
2578
|
+
return `${chalk11.gray(date)} ${color(`${snapshot.coverageScore}%`)} ${snapshot.documentedExports}/${snapshot.totalExports} exports${version2}${commit}`;
|
|
2405
2579
|
}
|
|
2406
2580
|
function formatWeekDate(timestamp) {
|
|
2407
2581
|
const date = new Date(timestamp);
|
|
@@ -2409,55 +2583,55 @@ function formatWeekDate(timestamp) {
|
|
|
2409
2583
|
}
|
|
2410
2584
|
function formatVelocity(velocity) {
|
|
2411
2585
|
if (velocity > 0)
|
|
2412
|
-
return
|
|
2586
|
+
return chalk11.green(`+${velocity}%/day`);
|
|
2413
2587
|
if (velocity < 0)
|
|
2414
|
-
return
|
|
2415
|
-
return
|
|
2588
|
+
return chalk11.red(`${velocity}%/day`);
|
|
2589
|
+
return chalk11.gray("0%/day");
|
|
2416
2590
|
}
|
|
2417
2591
|
function registerTrendsCommand(program) {
|
|
2418
2592
|
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) => {
|
|
2419
|
-
const cwd =
|
|
2593
|
+
const cwd = path9.resolve(options.cwd);
|
|
2420
2594
|
const tier = options.tier ?? "pro";
|
|
2421
2595
|
if (options.prune) {
|
|
2422
2596
|
const keepCount = parseInt(options.prune, 10);
|
|
2423
|
-
if (!isNaN(keepCount)) {
|
|
2597
|
+
if (!Number.isNaN(keepCount)) {
|
|
2424
2598
|
const deleted = pruneHistory(cwd, keepCount);
|
|
2425
|
-
console.log(
|
|
2599
|
+
console.log(chalk11.green(`Pruned ${deleted} old snapshots, kept ${keepCount} most recent`));
|
|
2426
2600
|
} else {
|
|
2427
2601
|
const deleted = pruneByTier(cwd, tier);
|
|
2428
|
-
console.log(
|
|
2602
|
+
console.log(chalk11.green(`Pruned ${deleted} snapshots older than ${RETENTION_DAYS[tier]} days`));
|
|
2429
2603
|
}
|
|
2430
2604
|
return;
|
|
2431
2605
|
}
|
|
2432
2606
|
if (options.record) {
|
|
2433
|
-
const specPath =
|
|
2434
|
-
if (!
|
|
2435
|
-
console.error(
|
|
2607
|
+
const specPath = path9.resolve(cwd, "openpkg.json");
|
|
2608
|
+
if (!fs7.existsSync(specPath)) {
|
|
2609
|
+
console.error(chalk11.red("No openpkg.json found. Run `doccov spec` first to generate a spec."));
|
|
2436
2610
|
process.exit(1);
|
|
2437
2611
|
}
|
|
2438
2612
|
try {
|
|
2439
|
-
const specContent =
|
|
2613
|
+
const specContent = fs7.readFileSync(specPath, "utf-8");
|
|
2440
2614
|
const spec = JSON.parse(specContent);
|
|
2441
2615
|
const trend = getTrend(spec, cwd);
|
|
2442
2616
|
saveSnapshot(trend.current, cwd);
|
|
2443
|
-
console.log(
|
|
2617
|
+
console.log(chalk11.green("Recorded coverage snapshot:"));
|
|
2444
2618
|
console.log(formatSnapshot(trend.current));
|
|
2445
2619
|
if (trend.delta !== undefined) {
|
|
2446
2620
|
const deltaStr = formatDelta2(trend.delta);
|
|
2447
|
-
const deltaColor = trend.delta > 0 ?
|
|
2448
|
-
console.log(
|
|
2621
|
+
const deltaColor = trend.delta > 0 ? chalk11.green : trend.delta < 0 ? chalk11.red : chalk11.gray;
|
|
2622
|
+
console.log(chalk11.gray("Change from previous:"), deltaColor(deltaStr));
|
|
2449
2623
|
}
|
|
2450
2624
|
return;
|
|
2451
2625
|
} catch (error) {
|
|
2452
|
-
console.error(
|
|
2626
|
+
console.error(chalk11.red("Failed to read openpkg.json:"), error instanceof Error ? error.message : error);
|
|
2453
2627
|
process.exit(1);
|
|
2454
2628
|
}
|
|
2455
2629
|
}
|
|
2456
2630
|
const snapshots = loadSnapshots(cwd);
|
|
2457
2631
|
const limit = parseInt(options.limit ?? "10", 10);
|
|
2458
2632
|
if (snapshots.length === 0) {
|
|
2459
|
-
console.log(
|
|
2460
|
-
console.log(
|
|
2633
|
+
console.log(chalk11.yellow("No coverage history found."));
|
|
2634
|
+
console.log(chalk11.gray("Run `doccov trends --record` to save the current coverage."));
|
|
2461
2635
|
return;
|
|
2462
2636
|
}
|
|
2463
2637
|
if (options.json) {
|
|
@@ -2472,28 +2646,28 @@ function registerTrendsCommand(program) {
|
|
|
2472
2646
|
}
|
|
2473
2647
|
const sparklineData = snapshots.slice(0, 10).map((s) => s.coverageScore).reverse();
|
|
2474
2648
|
const sparkline = renderSparkline(sparklineData);
|
|
2475
|
-
console.log(
|
|
2476
|
-
console.log(
|
|
2477
|
-
console.log(
|
|
2649
|
+
console.log(chalk11.bold("Coverage Trends"));
|
|
2650
|
+
console.log(chalk11.gray(`Package: ${snapshots[0].package}`));
|
|
2651
|
+
console.log(chalk11.gray(`Sparkline: ${sparkline}`));
|
|
2478
2652
|
console.log("");
|
|
2479
2653
|
if (snapshots.length >= 2) {
|
|
2480
2654
|
const oldest = snapshots[snapshots.length - 1];
|
|
2481
2655
|
const newest = snapshots[0];
|
|
2482
2656
|
const overallDelta = newest.coverageScore - oldest.coverageScore;
|
|
2483
2657
|
const deltaStr = formatDelta2(overallDelta);
|
|
2484
|
-
const deltaColor = overallDelta > 0 ?
|
|
2485
|
-
console.log(
|
|
2658
|
+
const deltaColor = overallDelta > 0 ? chalk11.green : overallDelta < 0 ? chalk11.red : chalk11.gray;
|
|
2659
|
+
console.log(chalk11.gray("Overall trend:"), deltaColor(deltaStr), chalk11.gray(`(${snapshots.length} snapshots)`));
|
|
2486
2660
|
console.log("");
|
|
2487
2661
|
}
|
|
2488
2662
|
if (options.extended) {
|
|
2489
|
-
const specPath =
|
|
2490
|
-
if (
|
|
2663
|
+
const specPath = path9.resolve(cwd, "openpkg.json");
|
|
2664
|
+
if (fs7.existsSync(specPath)) {
|
|
2491
2665
|
try {
|
|
2492
|
-
const specContent =
|
|
2666
|
+
const specContent = fs7.readFileSync(specPath, "utf-8");
|
|
2493
2667
|
const spec = JSON.parse(specContent);
|
|
2494
2668
|
const extended = getExtendedTrend(spec, cwd, { tier });
|
|
2495
|
-
console.log(
|
|
2496
|
-
console.log(
|
|
2669
|
+
console.log(chalk11.bold("Extended Analysis"));
|
|
2670
|
+
console.log(chalk11.gray(`Tier: ${tier} (${RETENTION_DAYS[tier]}-day retention)`));
|
|
2497
2671
|
console.log("");
|
|
2498
2672
|
console.log(" Velocity:");
|
|
2499
2673
|
console.log(` 7-day: ${formatVelocity(extended.velocity7d)}`);
|
|
@@ -2502,55 +2676,55 @@ function registerTrendsCommand(program) {
|
|
|
2502
2676
|
console.log(` 90-day: ${formatVelocity(extended.velocity90d)}`);
|
|
2503
2677
|
}
|
|
2504
2678
|
console.log("");
|
|
2505
|
-
const projColor = extended.projected30d >= extended.trend.current.coverageScore ?
|
|
2679
|
+
const projColor = extended.projected30d >= extended.trend.current.coverageScore ? chalk11.green : chalk11.red;
|
|
2506
2680
|
console.log(` Projected (30d): ${projColor(`${extended.projected30d}%`)}`);
|
|
2507
|
-
console.log(` All-time high: ${
|
|
2508
|
-
console.log(` All-time low: ${
|
|
2681
|
+
console.log(` All-time high: ${chalk11.green(`${extended.allTimeHigh}%`)}`);
|
|
2682
|
+
console.log(` All-time low: ${chalk11.red(`${extended.allTimeLow}%`)}`);
|
|
2509
2683
|
if (extended.dataRange) {
|
|
2510
2684
|
const startDate = formatWeekDate(extended.dataRange.start);
|
|
2511
2685
|
const endDate = formatWeekDate(extended.dataRange.end);
|
|
2512
|
-
console.log(
|
|
2686
|
+
console.log(chalk11.gray(` Data range: ${startDate} - ${endDate}`));
|
|
2513
2687
|
}
|
|
2514
2688
|
console.log("");
|
|
2515
2689
|
if (options.weekly && extended.weeklySummaries.length > 0) {
|
|
2516
|
-
console.log(
|
|
2690
|
+
console.log(chalk11.bold("Weekly Summary"));
|
|
2517
2691
|
const weekLimit = Math.min(extended.weeklySummaries.length, 8);
|
|
2518
2692
|
for (let i = 0;i < weekLimit; i++) {
|
|
2519
2693
|
const week = extended.weeklySummaries[i];
|
|
2520
2694
|
const weekStart = formatWeekDate(week.weekStart);
|
|
2521
2695
|
const weekEnd = formatWeekDate(week.weekEnd);
|
|
2522
|
-
const deltaColor = week.delta > 0 ?
|
|
2696
|
+
const deltaColor = week.delta > 0 ? chalk11.green : week.delta < 0 ? chalk11.red : chalk11.gray;
|
|
2523
2697
|
const deltaStr = week.delta > 0 ? `+${week.delta}%` : `${week.delta}%`;
|
|
2524
2698
|
console.log(` ${weekStart} - ${weekEnd}: ${week.avgCoverage}% avg ${deltaColor(deltaStr)} (${week.snapshotCount} snapshots)`);
|
|
2525
2699
|
}
|
|
2526
2700
|
if (extended.weeklySummaries.length > weekLimit) {
|
|
2527
|
-
console.log(
|
|
2701
|
+
console.log(chalk11.gray(` ... and ${extended.weeklySummaries.length - weekLimit} more weeks`));
|
|
2528
2702
|
}
|
|
2529
2703
|
console.log("");
|
|
2530
2704
|
}
|
|
2531
2705
|
} catch {
|
|
2532
|
-
console.log(
|
|
2706
|
+
console.log(chalk11.yellow("Could not load openpkg.json for extended analysis"));
|
|
2533
2707
|
console.log("");
|
|
2534
2708
|
}
|
|
2535
2709
|
}
|
|
2536
2710
|
}
|
|
2537
|
-
console.log(
|
|
2711
|
+
console.log(chalk11.bold("History"));
|
|
2538
2712
|
const displaySnapshots = snapshots.slice(0, limit);
|
|
2539
2713
|
for (let i = 0;i < displaySnapshots.length; i++) {
|
|
2540
2714
|
const snapshot = displaySnapshots[i];
|
|
2541
|
-
const prefix = i === 0 ?
|
|
2715
|
+
const prefix = i === 0 ? chalk11.cyan("→") : " ";
|
|
2542
2716
|
console.log(`${prefix} ${formatSnapshot(snapshot)}`);
|
|
2543
2717
|
}
|
|
2544
2718
|
if (snapshots.length > limit) {
|
|
2545
|
-
console.log(
|
|
2719
|
+
console.log(chalk11.gray(` ... and ${snapshots.length - limit} more`));
|
|
2546
2720
|
}
|
|
2547
2721
|
});
|
|
2548
2722
|
}
|
|
2549
2723
|
|
|
2550
2724
|
// src/cli.ts
|
|
2551
2725
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
2552
|
-
var __dirname2 =
|
|
2553
|
-
var packageJson = JSON.parse(readFileSync5(
|
|
2726
|
+
var __dirname2 = path10.dirname(__filename2);
|
|
2727
|
+
var packageJson = JSON.parse(readFileSync5(path10.join(__dirname2, "../package.json"), "utf-8"));
|
|
2554
2728
|
var program = new Command;
|
|
2555
2729
|
program.name("doccov").description("DocCov - Documentation coverage and drift detection for TypeScript").version(packageJson.version);
|
|
2556
2730
|
registerCheckCommand(program);
|