@doccov/cli 0.12.0 → 0.14.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 +229 -651
- package/dist/config/index.js +0 -19
- package/package.json +3 -4
package/dist/cli.js
CHANGED
|
@@ -1,22 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
-
var __defProp = Object.defineProperty;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
-
for (let key of __getOwnPropNames(mod))
|
|
12
|
-
if (!__hasOwnProp.call(to, key))
|
|
13
|
-
__defProp(to, key, {
|
|
14
|
-
get: () => mod[key],
|
|
15
|
-
enumerable: true
|
|
16
|
-
});
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
20
2
|
|
|
21
3
|
// src/config/doccov-config.ts
|
|
22
4
|
import { access } from "node:fs/promises";
|
|
@@ -178,8 +160,8 @@ ${formatIssues(issues)}`);
|
|
|
178
160
|
// src/config/index.ts
|
|
179
161
|
var defineConfig = (config) => config;
|
|
180
162
|
// src/cli.ts
|
|
181
|
-
import { readFileSync as
|
|
182
|
-
import * as
|
|
163
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
164
|
+
import * as path8 from "node:path";
|
|
183
165
|
import { fileURLToPath } from "node:url";
|
|
184
166
|
import { Command } from "commander";
|
|
185
167
|
|
|
@@ -206,7 +188,7 @@ import {
|
|
|
206
188
|
import {
|
|
207
189
|
DRIFT_CATEGORIES as DRIFT_CATEGORIES2
|
|
208
190
|
} from "@openpkg-ts/spec";
|
|
209
|
-
import
|
|
191
|
+
import chalk3 from "chalk";
|
|
210
192
|
|
|
211
193
|
// src/reports/diff-markdown.ts
|
|
212
194
|
import * as path2 from "node:path";
|
|
@@ -788,6 +770,57 @@ async function parseAssertionsWithLLM(code) {
|
|
|
788
770
|
}
|
|
789
771
|
}
|
|
790
772
|
|
|
773
|
+
// src/utils/progress.ts
|
|
774
|
+
import chalk2 from "chalk";
|
|
775
|
+
class StepProgress {
|
|
776
|
+
steps;
|
|
777
|
+
currentStep = 0;
|
|
778
|
+
startTime;
|
|
779
|
+
stepStartTime;
|
|
780
|
+
constructor(steps) {
|
|
781
|
+
this.steps = steps;
|
|
782
|
+
this.startTime = Date.now();
|
|
783
|
+
this.stepStartTime = Date.now();
|
|
784
|
+
}
|
|
785
|
+
start(stepIndex) {
|
|
786
|
+
this.currentStep = stepIndex ?? 0;
|
|
787
|
+
this.stepStartTime = Date.now();
|
|
788
|
+
this.render();
|
|
789
|
+
}
|
|
790
|
+
next() {
|
|
791
|
+
this.completeCurrentStep();
|
|
792
|
+
this.currentStep++;
|
|
793
|
+
if (this.currentStep < this.steps.length) {
|
|
794
|
+
this.stepStartTime = Date.now();
|
|
795
|
+
this.render();
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
complete(message) {
|
|
799
|
+
this.completeCurrentStep();
|
|
800
|
+
if (message) {
|
|
801
|
+
const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
|
|
802
|
+
console.log(`${chalk2.green("✓")} ${message} ${chalk2.dim(`(${elapsed}s)`)}`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
render() {
|
|
806
|
+
const step = this.steps[this.currentStep];
|
|
807
|
+
if (!step)
|
|
808
|
+
return;
|
|
809
|
+
const label = step.activeLabel ?? step.label;
|
|
810
|
+
const prefix = chalk2.dim(`[${this.currentStep + 1}/${this.steps.length}]`);
|
|
811
|
+
process.stdout.write(`\r${prefix} ${chalk2.cyan(label)}...`);
|
|
812
|
+
}
|
|
813
|
+
completeCurrentStep() {
|
|
814
|
+
const step = this.steps[this.currentStep];
|
|
815
|
+
if (!step)
|
|
816
|
+
return;
|
|
817
|
+
const elapsed = ((Date.now() - this.stepStartTime) / 1000).toFixed(1);
|
|
818
|
+
const prefix = chalk2.dim(`[${this.currentStep + 1}/${this.steps.length}]`);
|
|
819
|
+
process.stdout.write(`\r${" ".repeat(80)}\r`);
|
|
820
|
+
console.log(`${prefix} ${step.label} ${chalk2.green("✓")} ${chalk2.dim(`(${elapsed}s)`)}`);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
791
824
|
// src/utils/validation.ts
|
|
792
825
|
function clampPercentage(value, fallback = 80) {
|
|
793
826
|
if (Number.isNaN(value))
|
|
@@ -830,24 +863,32 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
830
863
|
};
|
|
831
864
|
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("--fix", "Auto-fix drift issues").option("--write", "Alias for --fix").option("--dry-run", "Preview fixes without writing (requires --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)").option("--no-cache", "Bypass spec cache and force regeneration").action(async (entry, options) => {
|
|
832
865
|
try {
|
|
866
|
+
const validations = parseExamplesFlag(options.examples);
|
|
867
|
+
const hasExamples = validations.length > 0;
|
|
868
|
+
const stepList = [
|
|
869
|
+
{ label: "Resolved target", activeLabel: "Resolving target" },
|
|
870
|
+
{ label: "Loaded config", activeLabel: "Loading config" },
|
|
871
|
+
{ label: "Generated spec", activeLabel: "Generating spec" },
|
|
872
|
+
{ label: "Enriched spec", activeLabel: "Enriching spec" },
|
|
873
|
+
...hasExamples ? [{ label: "Validated examples", activeLabel: "Validating examples" }] : [],
|
|
874
|
+
{ label: "Processed results", activeLabel: "Processing results" }
|
|
875
|
+
];
|
|
876
|
+
const steps = new StepProgress(stepList);
|
|
877
|
+
steps.start();
|
|
833
878
|
const fileSystem = new NodeFileSystem(options.cwd);
|
|
834
879
|
const resolved = await resolveTarget(fileSystem, {
|
|
835
880
|
cwd: options.cwd,
|
|
836
881
|
package: options.package,
|
|
837
882
|
entry
|
|
838
883
|
});
|
|
839
|
-
const { targetDir, entryFile
|
|
840
|
-
|
|
841
|
-
log(chalk2.gray(`Found package at ${packageInfo.path}`));
|
|
842
|
-
}
|
|
843
|
-
if (!entry) {
|
|
844
|
-
log(chalk2.gray(`Auto-detected entry point: ${entryPointInfo.path} (from ${entryPointInfo.source})`));
|
|
845
|
-
}
|
|
884
|
+
const { targetDir, entryFile } = resolved;
|
|
885
|
+
steps.next();
|
|
846
886
|
const config = await loadDocCovConfig(targetDir);
|
|
847
887
|
const minCoverageRaw = options.minCoverage ?? config?.check?.minCoverage;
|
|
848
888
|
const minCoverage = minCoverageRaw !== undefined ? clampPercentage(minCoverageRaw) : undefined;
|
|
849
889
|
const maxDriftRaw = options.maxDrift ?? config?.check?.maxDrift;
|
|
850
890
|
const maxDrift = maxDriftRaw !== undefined ? clampPercentage(maxDriftRaw) : undefined;
|
|
891
|
+
steps.next();
|
|
851
892
|
const resolveExternalTypes = !options.skipResolve;
|
|
852
893
|
let specResult;
|
|
853
894
|
const doccov = createDocCov({
|
|
@@ -857,14 +898,13 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
857
898
|
cwd: options.cwd
|
|
858
899
|
});
|
|
859
900
|
specResult = await doccov.analyzeFileWithDiagnostics(entryFile);
|
|
860
|
-
if (specResult.fromCache) {
|
|
861
|
-
log(chalk2.gray("Using cached spec"));
|
|
862
|
-
}
|
|
863
901
|
if (!specResult) {
|
|
864
902
|
throw new Error("Failed to analyze documentation coverage.");
|
|
865
903
|
}
|
|
904
|
+
steps.next();
|
|
866
905
|
const spec = enrichSpec(specResult.spec);
|
|
867
906
|
const format = options.format ?? "text";
|
|
907
|
+
steps.next();
|
|
868
908
|
const specWarnings = specResult.diagnostics.filter((d) => d.severity === "warning");
|
|
869
909
|
const specInfos = specResult.diagnostics.filter((d) => d.severity === "info");
|
|
870
910
|
const shouldFix = options.fix || options.write;
|
|
@@ -874,11 +914,10 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
874
914
|
violations.push({ exportName: exp.name, violation: v });
|
|
875
915
|
}
|
|
876
916
|
}
|
|
877
|
-
const validations = parseExamplesFlag(options.examples);
|
|
878
917
|
let exampleResult;
|
|
879
918
|
const typecheckErrors = [];
|
|
880
919
|
const runtimeDrifts = [];
|
|
881
|
-
if (
|
|
920
|
+
if (hasExamples) {
|
|
882
921
|
exampleResult = await validateExamples(spec.exports ?? [], {
|
|
883
922
|
validations,
|
|
884
923
|
packagePath: targetDir,
|
|
@@ -909,22 +948,23 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
909
948
|
});
|
|
910
949
|
}
|
|
911
950
|
}
|
|
951
|
+
steps.next();
|
|
912
952
|
}
|
|
913
953
|
const coverageScore = spec.docs?.coverageScore ?? 0;
|
|
914
954
|
const allDriftExports = [...collectDrift(spec.exports ?? []), ...runtimeDrifts];
|
|
915
|
-
let driftExports =
|
|
955
|
+
let driftExports = hasExamples ? allDriftExports : allDriftExports.filter((d) => d.category !== "example");
|
|
916
956
|
const fixedDriftKeys = new Set;
|
|
917
957
|
if (shouldFix && driftExports.length > 0) {
|
|
918
958
|
const allDrifts = collectDriftsFromExports(spec.exports ?? []);
|
|
919
959
|
if (allDrifts.length > 0) {
|
|
920
960
|
const { fixable, nonFixable } = categorizeDrifts(allDrifts.map((d) => d.drift));
|
|
921
961
|
if (fixable.length === 0) {
|
|
922
|
-
log(
|
|
962
|
+
log(chalk3.yellow(`Found ${nonFixable.length} drift issue(s), but none are auto-fixable.`));
|
|
923
963
|
} else {
|
|
924
964
|
log("");
|
|
925
|
-
log(
|
|
965
|
+
log(chalk3.bold(`Found ${fixable.length} fixable issue(s)`));
|
|
926
966
|
if (nonFixable.length > 0) {
|
|
927
|
-
log(
|
|
967
|
+
log(chalk3.gray(`(${nonFixable.length} non-fixable issue(s) skipped)`));
|
|
928
968
|
}
|
|
929
969
|
log("");
|
|
930
970
|
const groupedDrifts = groupByExport(allDrifts.filter((d) => fixable.includes(d.drift)));
|
|
@@ -932,22 +972,22 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
932
972
|
const editsByFile = new Map;
|
|
933
973
|
for (const [exp, drifts] of groupedDrifts) {
|
|
934
974
|
if (!exp.source?.file) {
|
|
935
|
-
log(
|
|
975
|
+
log(chalk3.gray(` Skipping ${exp.name}: no source location`));
|
|
936
976
|
continue;
|
|
937
977
|
}
|
|
938
978
|
if (exp.source.file.endsWith(".d.ts")) {
|
|
939
|
-
log(
|
|
979
|
+
log(chalk3.gray(` Skipping ${exp.name}: declaration file`));
|
|
940
980
|
continue;
|
|
941
981
|
}
|
|
942
982
|
const filePath = path4.resolve(targetDir, exp.source.file);
|
|
943
983
|
if (!fs2.existsSync(filePath)) {
|
|
944
|
-
log(
|
|
984
|
+
log(chalk3.gray(` Skipping ${exp.name}: file not found`));
|
|
945
985
|
continue;
|
|
946
986
|
}
|
|
947
987
|
const sourceFile = createSourceFile(filePath);
|
|
948
988
|
const location = findJSDocLocation(sourceFile, exp.name, exp.source.line);
|
|
949
989
|
if (!location) {
|
|
950
|
-
log(
|
|
990
|
+
log(chalk3.gray(` Skipping ${exp.name}: could not find declaration`));
|
|
951
991
|
continue;
|
|
952
992
|
}
|
|
953
993
|
let existingPatch = {};
|
|
@@ -980,26 +1020,26 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
980
1020
|
}
|
|
981
1021
|
if (edits.length > 0) {
|
|
982
1022
|
if (options.dryRun) {
|
|
983
|
-
log(
|
|
1023
|
+
log(chalk3.bold("Dry run - changes that would be made:"));
|
|
984
1024
|
log("");
|
|
985
1025
|
for (const [filePath, fileEdits] of editsByFile) {
|
|
986
1026
|
const relativePath = path4.relative(targetDir, filePath);
|
|
987
|
-
log(
|
|
1027
|
+
log(chalk3.cyan(` ${relativePath}:`));
|
|
988
1028
|
for (const { export: exp, edit, fixes } of fileEdits) {
|
|
989
1029
|
const lineInfo = edit.hasExisting ? `lines ${edit.startLine + 1}-${edit.endLine + 1}` : `line ${edit.startLine + 1}`;
|
|
990
|
-
log(` ${
|
|
1030
|
+
log(` ${chalk3.bold(exp.name)} [${lineInfo}]`);
|
|
991
1031
|
for (const fix of fixes) {
|
|
992
|
-
log(
|
|
1032
|
+
log(chalk3.green(` + ${fix.description}`));
|
|
993
1033
|
}
|
|
994
1034
|
}
|
|
995
1035
|
log("");
|
|
996
1036
|
}
|
|
997
|
-
log(
|
|
1037
|
+
log(chalk3.gray("Run without --dry-run to apply these changes."));
|
|
998
1038
|
} else {
|
|
999
1039
|
const applyResult = await applyEdits(edits);
|
|
1000
1040
|
if (applyResult.errors.length > 0) {
|
|
1001
1041
|
for (const err of applyResult.errors) {
|
|
1002
|
-
error(
|
|
1042
|
+
error(chalk3.red(` ${err.file}: ${err.error}`));
|
|
1003
1043
|
}
|
|
1004
1044
|
}
|
|
1005
1045
|
}
|
|
@@ -1010,6 +1050,7 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
1010
1050
|
driftExports = driftExports.filter((d) => !fixedDriftKeys.has(`${d.name}:${d.issue}`));
|
|
1011
1051
|
}
|
|
1012
1052
|
}
|
|
1053
|
+
steps.complete("Check complete");
|
|
1013
1054
|
if (format !== "text") {
|
|
1014
1055
|
const limit = parseInt(options.limit, 10) || 20;
|
|
1015
1056
|
const stats = computeStats(spec);
|
|
@@ -1069,15 +1110,15 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
1069
1110
|
if (specWarnings.length > 0 || specInfos.length > 0) {
|
|
1070
1111
|
log("");
|
|
1071
1112
|
for (const diag of specWarnings) {
|
|
1072
|
-
log(
|
|
1113
|
+
log(chalk3.yellow(`⚠ ${diag.message}`));
|
|
1073
1114
|
if (diag.suggestion) {
|
|
1074
|
-
log(
|
|
1115
|
+
log(chalk3.gray(` ${diag.suggestion}`));
|
|
1075
1116
|
}
|
|
1076
1117
|
}
|
|
1077
1118
|
for (const diag of specInfos) {
|
|
1078
|
-
log(
|
|
1119
|
+
log(chalk3.cyan(`ℹ ${diag.message}`));
|
|
1079
1120
|
if (diag.suggestion) {
|
|
1080
|
-
log(
|
|
1121
|
+
log(chalk3.gray(` ${diag.suggestion}`));
|
|
1081
1122
|
}
|
|
1082
1123
|
}
|
|
1083
1124
|
}
|
|
@@ -1087,23 +1128,23 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
1087
1128
|
const errorCount = violations.filter((v) => v.violation.severity === "error").length;
|
|
1088
1129
|
const warnCount = violations.filter((v) => v.violation.severity === "warn").length;
|
|
1089
1130
|
log("");
|
|
1090
|
-
log(
|
|
1131
|
+
log(chalk3.bold(`${pkgName}${pkgVersion ? `@${pkgVersion}` : ""}`));
|
|
1091
1132
|
log("");
|
|
1092
1133
|
log(` Exports: ${totalExports}`);
|
|
1093
1134
|
if (minCoverage !== undefined) {
|
|
1094
1135
|
if (coverageFailed) {
|
|
1095
|
-
log(
|
|
1136
|
+
log(chalk3.red(` Coverage: ✗ ${coverageScore}%`) + chalk3.dim(` (min ${minCoverage}%)`));
|
|
1096
1137
|
} else {
|
|
1097
|
-
log(
|
|
1138
|
+
log(chalk3.green(` Coverage: ✓ ${coverageScore}%`) + chalk3.dim(` (min ${minCoverage}%)`));
|
|
1098
1139
|
}
|
|
1099
1140
|
} else {
|
|
1100
1141
|
log(` Coverage: ${coverageScore}%`);
|
|
1101
1142
|
}
|
|
1102
1143
|
if (maxDrift !== undefined) {
|
|
1103
1144
|
if (driftFailed) {
|
|
1104
|
-
log(
|
|
1145
|
+
log(chalk3.red(` Drift: ✗ ${driftScore}%`) + chalk3.dim(` (max ${maxDrift}%)`));
|
|
1105
1146
|
} else {
|
|
1106
|
-
log(
|
|
1147
|
+
log(chalk3.green(` Drift: ✓ ${driftScore}%`) + chalk3.dim(` (max ${maxDrift}%)`));
|
|
1107
1148
|
}
|
|
1108
1149
|
} else {
|
|
1109
1150
|
log(` Drift: ${driftScore}%`);
|
|
@@ -1113,7 +1154,7 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
1113
1154
|
if (typecheckCount > 0) {
|
|
1114
1155
|
log(` Examples: ${typecheckCount} type errors`);
|
|
1115
1156
|
} else {
|
|
1116
|
-
log(
|
|
1157
|
+
log(chalk3.green(` Examples: ✓ validated`));
|
|
1117
1158
|
}
|
|
1118
1159
|
}
|
|
1119
1160
|
if (errorCount > 0 || warnCount > 0) {
|
|
@@ -1135,24 +1176,24 @@ function registerCheckCommand(program, dependencies = {}) {
|
|
|
1135
1176
|
thresholdParts.push(`drift ${driftScore}% ≤ ${maxDrift}%`);
|
|
1136
1177
|
}
|
|
1137
1178
|
if (thresholdParts.length > 0) {
|
|
1138
|
-
log(
|
|
1179
|
+
log(chalk3.green(`✓ Check passed (${thresholdParts.join(", ")})`));
|
|
1139
1180
|
} else {
|
|
1140
|
-
log(
|
|
1141
|
-
log(
|
|
1181
|
+
log(chalk3.green("✓ Check passed"));
|
|
1182
|
+
log(chalk3.dim(" No thresholds configured. Use --min-coverage or --max-drift to enforce."));
|
|
1142
1183
|
}
|
|
1143
1184
|
return;
|
|
1144
1185
|
}
|
|
1145
1186
|
if (hasQualityErrors) {
|
|
1146
|
-
log(
|
|
1187
|
+
log(chalk3.red(`✗ ${errorCount} quality errors`));
|
|
1147
1188
|
}
|
|
1148
1189
|
if (hasTypecheckErrors) {
|
|
1149
|
-
log(
|
|
1190
|
+
log(chalk3.red(`✗ ${typecheckErrors.length} example type errors`));
|
|
1150
1191
|
}
|
|
1151
1192
|
log("");
|
|
1152
|
-
log(
|
|
1193
|
+
log(chalk3.dim("Use --format json or --format markdown for detailed reports"));
|
|
1153
1194
|
process.exit(1);
|
|
1154
1195
|
} catch (commandError) {
|
|
1155
|
-
error(
|
|
1196
|
+
error(chalk3.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
|
|
1156
1197
|
process.exit(1);
|
|
1157
1198
|
}
|
|
1158
1199
|
});
|
|
@@ -1189,7 +1230,7 @@ import {
|
|
|
1189
1230
|
hashString,
|
|
1190
1231
|
parseMarkdownFiles
|
|
1191
1232
|
} from "@doccov/sdk";
|
|
1192
|
-
import
|
|
1233
|
+
import chalk4 from "chalk";
|
|
1193
1234
|
import { glob } from "glob";
|
|
1194
1235
|
|
|
1195
1236
|
// src/utils/docs-impact-ai.ts
|
|
@@ -1335,8 +1376,8 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
1335
1376
|
silent: true
|
|
1336
1377
|
});
|
|
1337
1378
|
}
|
|
1338
|
-
const cacheNote = fromCache ?
|
|
1339
|
-
log(
|
|
1379
|
+
const cacheNote = fromCache ? chalk4.cyan(" (cached)") : "";
|
|
1380
|
+
log(chalk4.dim(`Report: ${jsonPath}`) + cacheNote);
|
|
1340
1381
|
}
|
|
1341
1382
|
break;
|
|
1342
1383
|
case "json": {
|
|
@@ -1394,18 +1435,18 @@ function registerDiffCommand(program, dependencies = {}) {
|
|
|
1394
1435
|
checks
|
|
1395
1436
|
});
|
|
1396
1437
|
if (failures.length > 0) {
|
|
1397
|
-
log(
|
|
1438
|
+
log(chalk4.red(`
|
|
1398
1439
|
✗ Check failed`));
|
|
1399
1440
|
for (const f of failures) {
|
|
1400
|
-
log(
|
|
1441
|
+
log(chalk4.red(` - ${f}`));
|
|
1401
1442
|
}
|
|
1402
1443
|
process.exitCode = 1;
|
|
1403
1444
|
} else if (options.strict || minCoverage !== undefined || maxDrift !== undefined) {
|
|
1404
|
-
log(
|
|
1445
|
+
log(chalk4.green(`
|
|
1405
1446
|
✓ All checks passed`));
|
|
1406
1447
|
}
|
|
1407
1448
|
} catch (commandError) {
|
|
1408
|
-
error(
|
|
1449
|
+
error(chalk4.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
|
|
1409
1450
|
process.exitCode = 1;
|
|
1410
1451
|
}
|
|
1411
1452
|
});
|
|
@@ -1432,7 +1473,7 @@ async function generateDiff(baseSpec, headSpec, options, config, log) {
|
|
|
1432
1473
|
if (!docsPatterns || docsPatterns.length === 0) {
|
|
1433
1474
|
if (config?.docs?.include) {
|
|
1434
1475
|
docsPatterns = config.docs.include;
|
|
1435
|
-
log(
|
|
1476
|
+
log(chalk4.gray(`Using docs patterns from config: ${docsPatterns.join(", ")}`));
|
|
1436
1477
|
}
|
|
1437
1478
|
}
|
|
1438
1479
|
if (docsPatterns && docsPatterns.length > 0) {
|
|
@@ -1455,46 +1496,46 @@ function loadSpec(filePath, readFileSync2) {
|
|
|
1455
1496
|
}
|
|
1456
1497
|
function printSummary(diff, baseName, headName, fromCache, log) {
|
|
1457
1498
|
log("");
|
|
1458
|
-
const cacheIndicator = fromCache ?
|
|
1459
|
-
log(
|
|
1499
|
+
const cacheIndicator = fromCache ? chalk4.cyan(" (cached)") : "";
|
|
1500
|
+
log(chalk4.bold(`Comparing: ${baseName} → ${headName}`) + cacheIndicator);
|
|
1460
1501
|
log("─".repeat(40));
|
|
1461
1502
|
log("");
|
|
1462
|
-
const coverageColor = diff.coverageDelta > 0 ?
|
|
1503
|
+
const coverageColor = diff.coverageDelta > 0 ? chalk4.green : diff.coverageDelta < 0 ? chalk4.red : chalk4.gray;
|
|
1463
1504
|
const coverageSign = diff.coverageDelta > 0 ? "+" : "";
|
|
1464
1505
|
log(` Coverage: ${diff.oldCoverage}% → ${diff.newCoverage}% ${coverageColor(`(${coverageSign}${diff.coverageDelta}%)`)}`);
|
|
1465
1506
|
const breakingCount = diff.breaking.length;
|
|
1466
1507
|
const highSeverity = diff.categorizedBreaking?.filter((c) => c.severity === "high").length ?? 0;
|
|
1467
1508
|
if (breakingCount > 0) {
|
|
1468
|
-
const severityNote = highSeverity > 0 ?
|
|
1469
|
-
log(` Breaking: ${
|
|
1509
|
+
const severityNote = highSeverity > 0 ? chalk4.red(` (${highSeverity} high severity)`) : "";
|
|
1510
|
+
log(` Breaking: ${chalk4.red(breakingCount)} changes${severityNote}`);
|
|
1470
1511
|
} else {
|
|
1471
|
-
log(` Breaking: ${
|
|
1512
|
+
log(` Breaking: ${chalk4.green("0")} changes`);
|
|
1472
1513
|
}
|
|
1473
1514
|
const newCount = diff.nonBreaking.length;
|
|
1474
1515
|
const undocCount = diff.newUndocumented.length;
|
|
1475
1516
|
if (newCount > 0) {
|
|
1476
|
-
const undocNote = undocCount > 0 ?
|
|
1477
|
-
log(` New: ${
|
|
1517
|
+
const undocNote = undocCount > 0 ? chalk4.yellow(` (${undocCount} undocumented)`) : "";
|
|
1518
|
+
log(` New: ${chalk4.green(newCount)} exports${undocNote}`);
|
|
1478
1519
|
}
|
|
1479
1520
|
if (diff.driftIntroduced > 0 || diff.driftResolved > 0) {
|
|
1480
1521
|
const parts = [];
|
|
1481
1522
|
if (diff.driftIntroduced > 0)
|
|
1482
|
-
parts.push(
|
|
1523
|
+
parts.push(chalk4.red(`+${diff.driftIntroduced}`));
|
|
1483
1524
|
if (diff.driftResolved > 0)
|
|
1484
|
-
parts.push(
|
|
1525
|
+
parts.push(chalk4.green(`-${diff.driftResolved}`));
|
|
1485
1526
|
log(` Drift: ${parts.join(", ")}`);
|
|
1486
1527
|
}
|
|
1487
1528
|
log("");
|
|
1488
1529
|
}
|
|
1489
1530
|
async function printAISummary(diff, log) {
|
|
1490
1531
|
if (!isAIDocsAnalysisAvailable()) {
|
|
1491
|
-
log(
|
|
1532
|
+
log(chalk4.yellow(`
|
|
1492
1533
|
⚠ AI analysis unavailable (set OPENAI_API_KEY or ANTHROPIC_API_KEY)`));
|
|
1493
1534
|
return;
|
|
1494
1535
|
}
|
|
1495
1536
|
if (!diff.docsImpact)
|
|
1496
1537
|
return;
|
|
1497
|
-
log(
|
|
1538
|
+
log(chalk4.gray(`
|
|
1498
1539
|
Generating AI summary...`));
|
|
1499
1540
|
const impacts = diff.docsImpact.impactedFiles.flatMap((f) => f.references.map((r) => ({
|
|
1500
1541
|
file: f.file,
|
|
@@ -1505,8 +1546,8 @@ Generating AI summary...`));
|
|
|
1505
1546
|
const summary = await generateImpactSummary(impacts);
|
|
1506
1547
|
if (summary) {
|
|
1507
1548
|
log("");
|
|
1508
|
-
log(
|
|
1509
|
-
log(
|
|
1549
|
+
log(chalk4.bold("AI Summary"));
|
|
1550
|
+
log(chalk4.cyan(` ${summary}`));
|
|
1510
1551
|
}
|
|
1511
1552
|
}
|
|
1512
1553
|
function validateDiff(diff, headSpec, options) {
|
|
@@ -1587,7 +1628,7 @@ function printGitHubAnnotations(diff, log) {
|
|
|
1587
1628
|
|
|
1588
1629
|
// src/commands/info.ts
|
|
1589
1630
|
import { DocCov as DocCov2, enrichSpec as enrichSpec2, NodeFileSystem as NodeFileSystem2, resolveTarget as resolveTarget2 } from "@doccov/sdk";
|
|
1590
|
-
import
|
|
1631
|
+
import chalk5 from "chalk";
|
|
1591
1632
|
function registerInfoCommand(program) {
|
|
1592
1633
|
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) => {
|
|
1593
1634
|
try {
|
|
@@ -1609,14 +1650,14 @@ function registerInfoCommand(program) {
|
|
|
1609
1650
|
const spec = enrichSpec2(specResult.spec);
|
|
1610
1651
|
const stats = computeStats(spec);
|
|
1611
1652
|
console.log("");
|
|
1612
|
-
console.log(
|
|
1653
|
+
console.log(chalk5.bold(`${stats.packageName}@${stats.version}`));
|
|
1613
1654
|
console.log("");
|
|
1614
|
-
console.log(` Exports: ${
|
|
1615
|
-
console.log(` Coverage: ${
|
|
1616
|
-
console.log(` Drift: ${
|
|
1655
|
+
console.log(` Exports: ${chalk5.bold(stats.totalExports.toString())}`);
|
|
1656
|
+
console.log(` Coverage: ${chalk5.bold(`${stats.coverageScore}%`)}`);
|
|
1657
|
+
console.log(` Drift: ${chalk5.bold(`${stats.driftScore}%`)}`);
|
|
1617
1658
|
console.log("");
|
|
1618
1659
|
} catch (err) {
|
|
1619
|
-
console.error(
|
|
1660
|
+
console.error(chalk5.red("Error:"), err instanceof Error ? err.message : err);
|
|
1620
1661
|
process.exit(1);
|
|
1621
1662
|
}
|
|
1622
1663
|
});
|
|
@@ -1625,7 +1666,7 @@ function registerInfoCommand(program) {
|
|
|
1625
1666
|
// src/commands/init.ts
|
|
1626
1667
|
import * as fs4 from "node:fs";
|
|
1627
1668
|
import * as path6 from "node:path";
|
|
1628
|
-
import
|
|
1669
|
+
import chalk6 from "chalk";
|
|
1629
1670
|
var defaultDependencies3 = {
|
|
1630
1671
|
fileExists: fs4.existsSync,
|
|
1631
1672
|
writeFileSync: fs4.writeFileSync,
|
|
@@ -1642,31 +1683,31 @@ function registerInitCommand(program, dependencies = {}) {
|
|
|
1642
1683
|
const cwd = path6.resolve(options.cwd);
|
|
1643
1684
|
const formatOption = String(options.format ?? "auto").toLowerCase();
|
|
1644
1685
|
if (!isValidFormat(formatOption)) {
|
|
1645
|
-
error(
|
|
1686
|
+
error(chalk6.red(`Invalid format "${formatOption}". Use auto, mjs, js, or cjs.`));
|
|
1646
1687
|
process.exitCode = 1;
|
|
1647
1688
|
return;
|
|
1648
1689
|
}
|
|
1649
1690
|
const existing = findExistingConfig(cwd, fileExists2);
|
|
1650
1691
|
if (existing) {
|
|
1651
|
-
error(
|
|
1692
|
+
error(chalk6.red(`A DocCov config already exists at ${path6.relative(cwd, existing) || "./doccov.config.*"}.`));
|
|
1652
1693
|
process.exitCode = 1;
|
|
1653
1694
|
return;
|
|
1654
1695
|
}
|
|
1655
1696
|
const packageType = detectPackageType(cwd, fileExists2, readFileSync3);
|
|
1656
1697
|
const targetFormat = resolveFormat(formatOption, packageType);
|
|
1657
1698
|
if (targetFormat === "js" && packageType !== "module") {
|
|
1658
|
-
log(
|
|
1699
|
+
log(chalk6.yellow('Package is not marked as "type": "module"; creating doccov.config.js may require enabling ESM.'));
|
|
1659
1700
|
}
|
|
1660
1701
|
const fileName = `doccov.config.${targetFormat}`;
|
|
1661
1702
|
const outputPath = path6.join(cwd, fileName);
|
|
1662
1703
|
if (fileExists2(outputPath)) {
|
|
1663
|
-
error(
|
|
1704
|
+
error(chalk6.red(`Cannot create ${fileName}; file already exists.`));
|
|
1664
1705
|
process.exitCode = 1;
|
|
1665
1706
|
return;
|
|
1666
1707
|
}
|
|
1667
1708
|
const template = buildTemplate(targetFormat);
|
|
1668
1709
|
writeFileSync3(outputPath, template, { encoding: "utf8" });
|
|
1669
|
-
log(
|
|
1710
|
+
log(chalk6.green(`✓ Created ${path6.relative(process.cwd(), outputPath)}`));
|
|
1670
1711
|
});
|
|
1671
1712
|
}
|
|
1672
1713
|
var isValidFormat = (value) => {
|
|
@@ -1775,508 +1816,14 @@ var buildTemplate = (format) => {
|
|
|
1775
1816
|
`);
|
|
1776
1817
|
};
|
|
1777
1818
|
|
|
1778
|
-
// src/commands/
|
|
1779
|
-
import * as fs6 from "node:fs";
|
|
1780
|
-
import * as fsPromises from "node:fs/promises";
|
|
1781
|
-
import * as os from "node:os";
|
|
1782
|
-
import * as path8 from "node:path";
|
|
1783
|
-
import {
|
|
1784
|
-
buildCloneUrl,
|
|
1785
|
-
buildDisplayUrl,
|
|
1786
|
-
DocCov as DocCov3,
|
|
1787
|
-
detectBuildInfo,
|
|
1788
|
-
detectEntryPoint,
|
|
1789
|
-
detectMonorepo,
|
|
1790
|
-
detectPackageManager,
|
|
1791
|
-
extractSpecSummary,
|
|
1792
|
-
findPackageByName,
|
|
1793
|
-
formatPackageList,
|
|
1794
|
-
getInstallCommand,
|
|
1795
|
-
NodeFileSystem as NodeFileSystem3,
|
|
1796
|
-
parseGitHubUrl
|
|
1797
|
-
} from "@doccov/sdk";
|
|
1798
|
-
import {
|
|
1799
|
-
DRIFT_CATEGORIES as DRIFT_CATEGORIES3,
|
|
1800
|
-
DRIFT_CATEGORY_LABELS as DRIFT_CATEGORY_LABELS2
|
|
1801
|
-
} from "@openpkg-ts/spec";
|
|
1802
|
-
import chalk6 from "chalk";
|
|
1803
|
-
import { simpleGit } from "simple-git";
|
|
1804
|
-
|
|
1805
|
-
// src/utils/llm-build-plan.ts
|
|
1819
|
+
// src/commands/spec.ts
|
|
1806
1820
|
import * as fs5 from "node:fs";
|
|
1807
1821
|
import * as path7 from "node:path";
|
|
1808
|
-
import {
|
|
1809
|
-
import { createOpenAI as createOpenAI3 } from "@ai-sdk/openai";
|
|
1810
|
-
import { generateObject as generateObject3 } from "ai";
|
|
1811
|
-
import { z as z4 } from "zod";
|
|
1812
|
-
var BuildPlanSchema = z4.object({
|
|
1813
|
-
installCommand: z4.string().optional().describe("Additional install command if needed"),
|
|
1814
|
-
buildCommands: z4.array(z4.string()).describe('Build steps to run, e.g. ["npm run build:wasm"]'),
|
|
1815
|
-
entryPoint: z4.string().describe("Path to TS/TSX entry file after build"),
|
|
1816
|
-
notes: z4.string().optional().describe("Caveats or warnings")
|
|
1817
|
-
});
|
|
1818
|
-
var CONTEXT_FILES = [
|
|
1819
|
-
"package.json",
|
|
1820
|
-
"README.md",
|
|
1821
|
-
"README",
|
|
1822
|
-
"tsconfig.json",
|
|
1823
|
-
"Cargo.toml",
|
|
1824
|
-
".nvmrc",
|
|
1825
|
-
".node-version",
|
|
1826
|
-
"pnpm-workspace.yaml",
|
|
1827
|
-
"lerna.json",
|
|
1828
|
-
"wasm-pack.json"
|
|
1829
|
-
];
|
|
1830
|
-
var MAX_FILE_CHARS = 2000;
|
|
1831
|
-
function getModel3() {
|
|
1832
|
-
const provider = process.env.DOCCOV_LLM_PROVIDER?.toLowerCase();
|
|
1833
|
-
if (provider === "anthropic" || process.env.ANTHROPIC_API_KEY) {
|
|
1834
|
-
const anthropic = createAnthropic3();
|
|
1835
|
-
return anthropic("claude-sonnet-4-20250514");
|
|
1836
|
-
}
|
|
1837
|
-
const openai = createOpenAI3();
|
|
1838
|
-
return openai("gpt-4o-mini");
|
|
1839
|
-
}
|
|
1840
|
-
async function gatherContextFiles(repoDir) {
|
|
1841
|
-
const sections = [];
|
|
1842
|
-
for (const fileName of CONTEXT_FILES) {
|
|
1843
|
-
const filePath = path7.join(repoDir, fileName);
|
|
1844
|
-
if (fs5.existsSync(filePath)) {
|
|
1845
|
-
try {
|
|
1846
|
-
let content = fs5.readFileSync(filePath, "utf-8");
|
|
1847
|
-
if (content.length > MAX_FILE_CHARS) {
|
|
1848
|
-
content = `${content.slice(0, MAX_FILE_CHARS)}
|
|
1849
|
-
... (truncated)`;
|
|
1850
|
-
}
|
|
1851
|
-
sections.push(`--- ${fileName} ---
|
|
1852
|
-
${content}`);
|
|
1853
|
-
} catch {}
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
return sections.join(`
|
|
1857
|
-
|
|
1858
|
-
`);
|
|
1859
|
-
}
|
|
1860
|
-
var BUILD_PLAN_PROMPT = (context) => `Analyze this project to determine how to build it for TypeScript API analysis.
|
|
1861
|
-
|
|
1862
|
-
The standard entry detection failed. This might be a WASM project, unusual monorepo, or require a build step before the TypeScript entry point exists.
|
|
1863
|
-
|
|
1864
|
-
<files>
|
|
1865
|
-
${context}
|
|
1866
|
-
</files>
|
|
1867
|
-
|
|
1868
|
-
Return:
|
|
1869
|
-
- buildCommands: Commands to run in order (e.g., ["npm run build:wasm", "npm run build"]). Empty array if no build needed.
|
|
1870
|
-
- entryPoint: Path to the TypeScript entry file AFTER build completes (e.g., "src/index.ts" or "pkg/index.d.ts")
|
|
1871
|
-
- installCommand: Additional install command if needed beyond what was already run
|
|
1872
|
-
- notes: Any caveats (e.g., "requires Rust/wasm-pack installed")
|
|
1873
|
-
|
|
1874
|
-
Important:
|
|
1875
|
-
- Look for build scripts in package.json that might generate TypeScript bindings
|
|
1876
|
-
- Check README for build instructions
|
|
1877
|
-
- For WASM projects, look for wasm-pack or similar tooling
|
|
1878
|
-
- The entry point should be a .ts, .tsx, or .d.ts file`;
|
|
1879
|
-
async function generateBuildPlan(repoDir) {
|
|
1880
|
-
const hasApiKey = process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY;
|
|
1881
|
-
if (!hasApiKey) {
|
|
1882
|
-
return null;
|
|
1883
|
-
}
|
|
1884
|
-
const context = await gatherContextFiles(repoDir);
|
|
1885
|
-
if (!context.trim()) {
|
|
1886
|
-
return null;
|
|
1887
|
-
}
|
|
1888
|
-
const model = getModel3();
|
|
1889
|
-
const { object } = await generateObject3({
|
|
1890
|
-
model,
|
|
1891
|
-
schema: BuildPlanSchema,
|
|
1892
|
-
prompt: BUILD_PLAN_PROMPT(context)
|
|
1893
|
-
});
|
|
1894
|
-
return object;
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
// src/commands/scan.ts
|
|
1898
|
-
var defaultDependencies4 = {
|
|
1899
|
-
createDocCov: (options) => new DocCov3(options),
|
|
1900
|
-
log: console.log,
|
|
1901
|
-
error: console.error
|
|
1902
|
-
};
|
|
1903
|
-
function registerScanCommand(program, dependencies = {}) {
|
|
1904
|
-
const { createDocCov, log, error } = {
|
|
1905
|
-
...defaultDependencies4,
|
|
1906
|
-
...dependencies
|
|
1907
|
-
};
|
|
1908
|
-
program.command("scan <url>").description("Analyze docs coverage for any public GitHub repository").option("--ref <branch>", "Branch or tag to analyze").option("--package <name>", "Target package in monorepo").option("--output <format>", "Output format: text or json", "text").option("--no-cleanup", "Keep cloned repo (for debugging)").option("--skip-install", "Skip dependency installation (faster, but may limit type resolution)").option("--skip-resolve", "Skip external type resolution from node_modules").option("--save-spec <path>", "Save full OpenPkg spec to file").action(async (url, options) => {
|
|
1909
|
-
let tempDir;
|
|
1910
|
-
try {
|
|
1911
|
-
const parsed = parseGitHubUrl(url, options.ref ?? "main");
|
|
1912
|
-
const cloneUrl = buildCloneUrl(parsed);
|
|
1913
|
-
const displayUrl = buildDisplayUrl(parsed);
|
|
1914
|
-
log("");
|
|
1915
|
-
log(chalk6.bold(`Scanning ${displayUrl}`));
|
|
1916
|
-
log(chalk6.gray(`Branch/tag: ${parsed.ref}`));
|
|
1917
|
-
log("");
|
|
1918
|
-
tempDir = path8.join(os.tmpdir(), `doccov-scan-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
1919
|
-
fs6.mkdirSync(tempDir, { recursive: true });
|
|
1920
|
-
process.stdout.write(chalk6.cyan(`> Cloning ${parsed.owner}/${parsed.repo}...
|
|
1921
|
-
`));
|
|
1922
|
-
try {
|
|
1923
|
-
const git = simpleGit({
|
|
1924
|
-
timeout: {
|
|
1925
|
-
block: 30000
|
|
1926
|
-
}
|
|
1927
|
-
});
|
|
1928
|
-
const originalEnv = { ...process.env };
|
|
1929
|
-
process.env.GIT_TERMINAL_PROMPT = "0";
|
|
1930
|
-
process.env.GIT_ASKPASS = "echo";
|
|
1931
|
-
try {
|
|
1932
|
-
await git.clone(cloneUrl, tempDir, [
|
|
1933
|
-
"--depth",
|
|
1934
|
-
"1",
|
|
1935
|
-
"--branch",
|
|
1936
|
-
parsed.ref,
|
|
1937
|
-
"--single-branch"
|
|
1938
|
-
]);
|
|
1939
|
-
} finally {
|
|
1940
|
-
process.env = originalEnv;
|
|
1941
|
-
}
|
|
1942
|
-
process.stdout.write(chalk6.green(`✓ Cloned ${parsed.owner}/${parsed.repo}
|
|
1943
|
-
`));
|
|
1944
|
-
} catch (cloneError) {
|
|
1945
|
-
process.stdout.write(chalk6.red(`✗ Failed to clone repository
|
|
1946
|
-
`));
|
|
1947
|
-
const message = cloneError instanceof Error ? cloneError.message : String(cloneError);
|
|
1948
|
-
if (message.includes("Authentication failed") || message.includes("could not read Username") || message.includes("terminal prompts disabled") || message.includes("Invalid username or password") || message.includes("Permission denied")) {
|
|
1949
|
-
throw new Error(`Authentication required: This repository appears to be private. ` + `Public repositories only are currently supported.
|
|
1950
|
-
` + `Repository: ${displayUrl}`);
|
|
1951
|
-
}
|
|
1952
|
-
if (message.includes("not found") || message.includes("404")) {
|
|
1953
|
-
throw new Error(`Repository not accessible or does not exist: ${displayUrl}
|
|
1954
|
-
` + `Note: Private repositories are not currently supported.`);
|
|
1955
|
-
}
|
|
1956
|
-
if (message.includes("Could not find remote branch")) {
|
|
1957
|
-
throw new Error(`Branch or tag not found: ${parsed.ref}`);
|
|
1958
|
-
}
|
|
1959
|
-
throw new Error(`Clone failed: ${message}`);
|
|
1960
|
-
}
|
|
1961
|
-
const fileSystem = new NodeFileSystem3(tempDir);
|
|
1962
|
-
if (options.skipInstall) {
|
|
1963
|
-
log(chalk6.gray("Skipping dependency installation (--skip-install)"));
|
|
1964
|
-
} else {
|
|
1965
|
-
process.stdout.write(chalk6.cyan(`> Installing dependencies...
|
|
1966
|
-
`));
|
|
1967
|
-
const installErrors = [];
|
|
1968
|
-
try {
|
|
1969
|
-
const { execSync } = await import("node:child_process");
|
|
1970
|
-
const pmInfo = await detectPackageManager(fileSystem);
|
|
1971
|
-
const installCmd = getInstallCommand(pmInfo);
|
|
1972
|
-
const cmdString = installCmd.join(" ");
|
|
1973
|
-
let installed = false;
|
|
1974
|
-
if (pmInfo.lockfile) {
|
|
1975
|
-
try {
|
|
1976
|
-
execSync(cmdString, {
|
|
1977
|
-
cwd: tempDir,
|
|
1978
|
-
stdio: "pipe",
|
|
1979
|
-
timeout: 180000
|
|
1980
|
-
});
|
|
1981
|
-
installed = true;
|
|
1982
|
-
} catch (cmdError) {
|
|
1983
|
-
const stderr = cmdError?.stderr?.toString() ?? "";
|
|
1984
|
-
const msg = cmdError instanceof Error ? cmdError.message : String(cmdError);
|
|
1985
|
-
installErrors.push(`[${cmdString}] ${stderr.slice(0, 150) || msg.slice(0, 150)}`);
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
if (!installed) {
|
|
1989
|
-
try {
|
|
1990
|
-
execSync("bun install", {
|
|
1991
|
-
cwd: tempDir,
|
|
1992
|
-
stdio: "pipe",
|
|
1993
|
-
timeout: 120000
|
|
1994
|
-
});
|
|
1995
|
-
installed = true;
|
|
1996
|
-
} catch (bunError) {
|
|
1997
|
-
const stderr = bunError?.stderr?.toString() ?? "";
|
|
1998
|
-
const msg = bunError instanceof Error ? bunError.message : String(bunError);
|
|
1999
|
-
installErrors.push(`[bun install] ${stderr.slice(0, 150) || msg.slice(0, 150)}`);
|
|
2000
|
-
try {
|
|
2001
|
-
execSync("npm install --legacy-peer-deps --ignore-scripts", {
|
|
2002
|
-
cwd: tempDir,
|
|
2003
|
-
stdio: "pipe",
|
|
2004
|
-
timeout: 180000
|
|
2005
|
-
});
|
|
2006
|
-
installed = true;
|
|
2007
|
-
} catch (npmError) {
|
|
2008
|
-
const npmStderr = npmError?.stderr?.toString() ?? "";
|
|
2009
|
-
const npmMsg = npmError instanceof Error ? npmError.message : String(npmError);
|
|
2010
|
-
installErrors.push(`[npm install] ${npmStderr.slice(0, 150) || npmMsg.slice(0, 150)}`);
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
}
|
|
2014
|
-
if (installed) {
|
|
2015
|
-
process.stdout.write(chalk6.green(`✓ Dependencies installed
|
|
2016
|
-
`));
|
|
2017
|
-
} else {
|
|
2018
|
-
process.stdout.write(chalk6.yellow(`⚠ Could not install dependencies (analysis may be limited)
|
|
2019
|
-
`));
|
|
2020
|
-
for (const err of installErrors) {
|
|
2021
|
-
log(chalk6.gray(` ${err}`));
|
|
2022
|
-
}
|
|
2023
|
-
}
|
|
2024
|
-
} catch (outerError) {
|
|
2025
|
-
const msg = outerError instanceof Error ? outerError.message : String(outerError);
|
|
2026
|
-
process.stdout.write(chalk6.yellow(`⚠ Could not install dependencies: ${msg.slice(0, 100)}
|
|
2027
|
-
`));
|
|
2028
|
-
for (const err of installErrors) {
|
|
2029
|
-
log(chalk6.gray(` ${err}`));
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
}
|
|
2033
|
-
let targetDir = tempDir;
|
|
2034
|
-
let packageName;
|
|
2035
|
-
const mono = await detectMonorepo(fileSystem);
|
|
2036
|
-
if (mono.isMonorepo) {
|
|
2037
|
-
if (!options.package) {
|
|
2038
|
-
error("");
|
|
2039
|
-
error(chalk6.red(`Monorepo detected with ${mono.packages.length} packages. Specify target with --package:`));
|
|
2040
|
-
error("");
|
|
2041
|
-
error(formatPackageList(mono.packages));
|
|
2042
|
-
error("");
|
|
2043
|
-
throw new Error("Monorepo requires --package flag");
|
|
2044
|
-
}
|
|
2045
|
-
const pkg = findPackageByName(mono.packages, options.package);
|
|
2046
|
-
if (!pkg) {
|
|
2047
|
-
error("");
|
|
2048
|
-
error(chalk6.red(`Package "${options.package}" not found. Available packages:`));
|
|
2049
|
-
error("");
|
|
2050
|
-
error(formatPackageList(mono.packages));
|
|
2051
|
-
error("");
|
|
2052
|
-
throw new Error(`Package not found: ${options.package}`);
|
|
2053
|
-
}
|
|
2054
|
-
targetDir = path8.join(tempDir, pkg.path);
|
|
2055
|
-
packageName = pkg.name;
|
|
2056
|
-
log(chalk6.gray(`Analyzing package: ${packageName}`));
|
|
2057
|
-
}
|
|
2058
|
-
process.stdout.write(chalk6.cyan(`> Detecting entry point...
|
|
2059
|
-
`));
|
|
2060
|
-
let entryPath;
|
|
2061
|
-
const targetFs = mono.isMonorepo ? new NodeFileSystem3(targetDir) : fileSystem;
|
|
2062
|
-
let buildFailed = false;
|
|
2063
|
-
const runLlmFallback = async (reason) => {
|
|
2064
|
-
process.stdout.write(chalk6.cyan(`> ${reason}, trying LLM fallback...
|
|
2065
|
-
`));
|
|
2066
|
-
const plan = await generateBuildPlan(targetDir);
|
|
2067
|
-
if (!plan) {
|
|
2068
|
-
return null;
|
|
2069
|
-
}
|
|
2070
|
-
if (plan.buildCommands.length > 0) {
|
|
2071
|
-
const { execSync } = await import("node:child_process");
|
|
2072
|
-
for (const cmd of plan.buildCommands) {
|
|
2073
|
-
log(chalk6.gray(` Running: ${cmd}`));
|
|
2074
|
-
try {
|
|
2075
|
-
execSync(cmd, { cwd: targetDir, stdio: "pipe", timeout: 300000 });
|
|
2076
|
-
} catch (buildError) {
|
|
2077
|
-
buildFailed = true;
|
|
2078
|
-
const msg = buildError instanceof Error ? buildError.message : String(buildError);
|
|
2079
|
-
if (msg.includes("rustc") || msg.includes("cargo") || msg.includes("wasm-pack")) {
|
|
2080
|
-
log(chalk6.yellow(` ⚠ Build requires Rust toolchain (not available)`));
|
|
2081
|
-
} else if (msg.includes("rimraf") || msg.includes("command not found")) {
|
|
2082
|
-
log(chalk6.yellow(` ⚠ Build failed: missing dependencies`));
|
|
2083
|
-
} else {
|
|
2084
|
-
log(chalk6.yellow(` ⚠ Build failed: ${msg.slice(0, 80)}`));
|
|
2085
|
-
}
|
|
2086
|
-
}
|
|
2087
|
-
}
|
|
2088
|
-
}
|
|
2089
|
-
if (plan.notes) {
|
|
2090
|
-
log(chalk6.gray(` Note: ${plan.notes}`));
|
|
2091
|
-
}
|
|
2092
|
-
return plan.entryPoint;
|
|
2093
|
-
};
|
|
2094
|
-
try {
|
|
2095
|
-
const entry = await detectEntryPoint(targetFs);
|
|
2096
|
-
const buildInfo = await detectBuildInfo(targetFs);
|
|
2097
|
-
const needsBuildStep = entry.isDeclarationOnly && buildInfo.exoticIndicators.wasm;
|
|
2098
|
-
if (needsBuildStep) {
|
|
2099
|
-
process.stdout.write(chalk6.cyan(`> Detected .d.ts entry with WASM indicators...
|
|
2100
|
-
`));
|
|
2101
|
-
const llmEntry = await runLlmFallback("WASM project detected");
|
|
2102
|
-
if (llmEntry) {
|
|
2103
|
-
entryPath = path8.join(targetDir, llmEntry);
|
|
2104
|
-
if (buildFailed) {
|
|
2105
|
-
process.stdout.write(chalk6.green(`✓ Entry point: ${llmEntry} (using pre-committed declarations)
|
|
2106
|
-
`));
|
|
2107
|
-
log(chalk6.gray(" Coverage may be limited - generated .d.ts files typically lack JSDoc"));
|
|
2108
|
-
} else {
|
|
2109
|
-
process.stdout.write(chalk6.green(`✓ Entry point: ${llmEntry} (from LLM fallback - WASM project)
|
|
2110
|
-
`));
|
|
2111
|
-
}
|
|
2112
|
-
} else {
|
|
2113
|
-
entryPath = path8.join(targetDir, entry.path);
|
|
2114
|
-
process.stdout.write(chalk6.green(`✓ Entry point: ${entry.path} (from ${entry.source})
|
|
2115
|
-
`));
|
|
2116
|
-
log(chalk6.yellow(" ⚠ WASM project detected but no API key - analysis may be limited"));
|
|
2117
|
-
}
|
|
2118
|
-
} else {
|
|
2119
|
-
entryPath = path8.join(targetDir, entry.path);
|
|
2120
|
-
process.stdout.write(chalk6.green(`✓ Entry point: ${entry.path} (from ${entry.source})
|
|
2121
|
-
`));
|
|
2122
|
-
}
|
|
2123
|
-
} catch (entryError) {
|
|
2124
|
-
const llmEntry = await runLlmFallback("Heuristics failed");
|
|
2125
|
-
if (llmEntry) {
|
|
2126
|
-
entryPath = path8.join(targetDir, llmEntry);
|
|
2127
|
-
process.stdout.write(chalk6.green(`✓ Entry point: ${llmEntry} (from LLM fallback)
|
|
2128
|
-
`));
|
|
2129
|
-
} else {
|
|
2130
|
-
process.stdout.write(chalk6.red(`✗ Could not detect entry point (set OPENAI_API_KEY for smart fallback)
|
|
2131
|
-
`));
|
|
2132
|
-
throw entryError;
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
process.stdout.write(chalk6.cyan(`> Analyzing documentation coverage...
|
|
2136
|
-
`));
|
|
2137
|
-
let result;
|
|
2138
|
-
try {
|
|
2139
|
-
const resolveExternalTypes = !options.skipResolve;
|
|
2140
|
-
const doccov = createDocCov({ resolveExternalTypes });
|
|
2141
|
-
result = await doccov.analyzeFileWithDiagnostics(entryPath);
|
|
2142
|
-
process.stdout.write(chalk6.green(`✓ Analysis complete
|
|
2143
|
-
`));
|
|
2144
|
-
} catch (analysisError) {
|
|
2145
|
-
process.stdout.write(chalk6.red(`✗ Analysis failed
|
|
2146
|
-
`));
|
|
2147
|
-
throw analysisError;
|
|
2148
|
-
}
|
|
2149
|
-
const spec = result.spec;
|
|
2150
|
-
if (options.saveSpec) {
|
|
2151
|
-
const specPath = path8.resolve(process.cwd(), options.saveSpec);
|
|
2152
|
-
fs6.writeFileSync(specPath, JSON.stringify(spec, null, 2));
|
|
2153
|
-
log(chalk6.green(`✓ Saved spec to ${options.saveSpec}`));
|
|
2154
|
-
}
|
|
2155
|
-
const summary = extractSpecSummary(spec);
|
|
2156
|
-
const scanResult = {
|
|
2157
|
-
owner: parsed.owner,
|
|
2158
|
-
repo: parsed.repo,
|
|
2159
|
-
ref: parsed.ref,
|
|
2160
|
-
packageName,
|
|
2161
|
-
coverage: summary.coverage,
|
|
2162
|
-
exportCount: summary.exportCount,
|
|
2163
|
-
typeCount: summary.typeCount,
|
|
2164
|
-
driftCount: summary.driftCount,
|
|
2165
|
-
undocumented: summary.undocumented,
|
|
2166
|
-
drift: summary.drift
|
|
2167
|
-
};
|
|
2168
|
-
if (options.output === "json") {
|
|
2169
|
-
log(JSON.stringify(scanResult, null, 2));
|
|
2170
|
-
} else {
|
|
2171
|
-
printTextResult(scanResult, log);
|
|
2172
|
-
}
|
|
2173
|
-
} catch (commandError) {
|
|
2174
|
-
error(chalk6.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
|
|
2175
|
-
process.exitCode = 1;
|
|
2176
|
-
} finally {
|
|
2177
|
-
if (tempDir && options.cleanup !== false) {
|
|
2178
|
-
fsPromises.rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
|
2179
|
-
} else if (tempDir) {
|
|
2180
|
-
log(chalk6.gray(`Repo preserved at: ${tempDir}`));
|
|
2181
|
-
}
|
|
2182
|
-
}
|
|
2183
|
-
});
|
|
2184
|
-
}
|
|
2185
|
-
function categorizeDriftIssues(drift) {
|
|
2186
|
-
const byCategory = {
|
|
2187
|
-
structural: [],
|
|
2188
|
-
semantic: [],
|
|
2189
|
-
example: []
|
|
2190
|
-
};
|
|
2191
|
-
for (const d of drift) {
|
|
2192
|
-
const category = DRIFT_CATEGORIES3[d.type] ?? "semantic";
|
|
2193
|
-
byCategory[category].push(d);
|
|
2194
|
-
}
|
|
2195
|
-
return {
|
|
2196
|
-
summary: {
|
|
2197
|
-
total: drift.length,
|
|
2198
|
-
byCategory: {
|
|
2199
|
-
structural: byCategory.structural.length,
|
|
2200
|
-
semantic: byCategory.semantic.length,
|
|
2201
|
-
example: byCategory.example.length
|
|
2202
|
-
}
|
|
2203
|
-
},
|
|
2204
|
-
byCategory
|
|
2205
|
-
};
|
|
2206
|
-
}
|
|
2207
|
-
function formatDriftSummary(summary) {
|
|
2208
|
-
if (summary.total === 0) {
|
|
2209
|
-
return "No drift detected";
|
|
2210
|
-
}
|
|
2211
|
-
const parts = [];
|
|
2212
|
-
if (summary.byCategory.structural > 0) {
|
|
2213
|
-
parts.push(`${summary.byCategory.structural} structural`);
|
|
2214
|
-
}
|
|
2215
|
-
if (summary.byCategory.semantic > 0) {
|
|
2216
|
-
parts.push(`${summary.byCategory.semantic} semantic`);
|
|
2217
|
-
}
|
|
2218
|
-
if (summary.byCategory.example > 0) {
|
|
2219
|
-
parts.push(`${summary.byCategory.example} example`);
|
|
2220
|
-
}
|
|
2221
|
-
return `${summary.total} issues (${parts.join(", ")})`;
|
|
2222
|
-
}
|
|
2223
|
-
function printTextResult(result, log) {
|
|
2224
|
-
log("");
|
|
2225
|
-
log(chalk6.bold("DocCov Scan Results"));
|
|
2226
|
-
log("─".repeat(40));
|
|
2227
|
-
const repoName = result.packageName ? `${result.owner}/${result.repo} (${result.packageName})` : `${result.owner}/${result.repo}`;
|
|
2228
|
-
log(`Repository: ${chalk6.cyan(repoName)}`);
|
|
2229
|
-
log(`Branch: ${chalk6.gray(result.ref)}`);
|
|
2230
|
-
log("");
|
|
2231
|
-
const coverageColor = result.coverage >= 80 ? chalk6.green : result.coverage >= 50 ? chalk6.yellow : chalk6.red;
|
|
2232
|
-
log(chalk6.bold("Coverage"));
|
|
2233
|
-
log(` ${coverageColor(`${result.coverage}%`)}`);
|
|
2234
|
-
log("");
|
|
2235
|
-
log(chalk6.bold("Stats"));
|
|
2236
|
-
log(` ${result.exportCount} exports`);
|
|
2237
|
-
log(` ${result.typeCount} types`);
|
|
2238
|
-
log(` ${result.undocumented.length} undocumented`);
|
|
2239
|
-
const categorized = categorizeDriftIssues(result.drift);
|
|
2240
|
-
const driftColor = result.driftCount > 0 ? chalk6.yellow : chalk6.green;
|
|
2241
|
-
log(` ${driftColor(formatDriftSummary(categorized.summary))}`);
|
|
2242
|
-
if (result.undocumented.length > 0) {
|
|
2243
|
-
log("");
|
|
2244
|
-
log(chalk6.bold("Undocumented Exports"));
|
|
2245
|
-
for (const name of result.undocumented.slice(0, 10)) {
|
|
2246
|
-
log(chalk6.yellow(` ! ${name}`));
|
|
2247
|
-
}
|
|
2248
|
-
if (result.undocumented.length > 10) {
|
|
2249
|
-
log(chalk6.gray(` ... and ${result.undocumented.length - 10} more`));
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
if (result.drift.length > 0) {
|
|
2253
|
-
log("");
|
|
2254
|
-
log(chalk6.bold("Drift Issues"));
|
|
2255
|
-
const categories = ["structural", "semantic", "example"];
|
|
2256
|
-
for (const category of categories) {
|
|
2257
|
-
const issues = categorized.byCategory[category];
|
|
2258
|
-
if (issues.length === 0)
|
|
2259
|
-
continue;
|
|
2260
|
-
const label = DRIFT_CATEGORY_LABELS2[category];
|
|
2261
|
-
log("");
|
|
2262
|
-
log(chalk6.dim(` ${label} (${issues.length})`));
|
|
2263
|
-
for (const d of issues.slice(0, 3)) {
|
|
2264
|
-
log(chalk6.red(` • ${d.export}: ${d.issue}`));
|
|
2265
|
-
}
|
|
2266
|
-
if (issues.length > 3) {
|
|
2267
|
-
log(chalk6.gray(` ... and ${issues.length - 3} more`));
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2270
|
-
}
|
|
2271
|
-
log("");
|
|
2272
|
-
}
|
|
2273
|
-
|
|
2274
|
-
// src/commands/spec.ts
|
|
2275
|
-
import * as fs7 from "node:fs";
|
|
2276
|
-
import * as path9 from "node:path";
|
|
2277
|
-
import { DocCov as DocCov4, NodeFileSystem as NodeFileSystem4, resolveTarget as resolveTarget3 } from "@doccov/sdk";
|
|
1822
|
+
import { DocCov as DocCov3, NodeFileSystem as NodeFileSystem3, resolveTarget as resolveTarget3 } from "@doccov/sdk";
|
|
2278
1823
|
import { normalize, validateSpec } from "@openpkg-ts/spec";
|
|
2279
1824
|
import chalk8 from "chalk";
|
|
1825
|
+
// package.json
|
|
1826
|
+
var version = "0.13.0";
|
|
2280
1827
|
|
|
2281
1828
|
// src/utils/filter-options.ts
|
|
2282
1829
|
import { mergeFilters, parseListFlag } from "@doccov/sdk";
|
|
@@ -2310,9 +1857,9 @@ var mergeFilterOptions = (config, cliOptions) => {
|
|
|
2310
1857
|
};
|
|
2311
1858
|
|
|
2312
1859
|
// src/commands/spec.ts
|
|
2313
|
-
var
|
|
2314
|
-
createDocCov: (options) => new
|
|
2315
|
-
writeFileSync:
|
|
1860
|
+
var defaultDependencies4 = {
|
|
1861
|
+
createDocCov: (options) => new DocCov3(options),
|
|
1862
|
+
writeFileSync: fs5.writeFileSync,
|
|
2316
1863
|
log: console.log,
|
|
2317
1864
|
error: console.error
|
|
2318
1865
|
};
|
|
@@ -2321,82 +1868,76 @@ function getArrayLength(value) {
|
|
|
2321
1868
|
}
|
|
2322
1869
|
function formatDiagnosticOutput(prefix, diagnostic, baseDir) {
|
|
2323
1870
|
const location = diagnostic.location;
|
|
2324
|
-
const relativePath = location?.file ?
|
|
1871
|
+
const relativePath = location?.file ? path7.relative(baseDir, location.file) || location.file : undefined;
|
|
2325
1872
|
const locationText = location && relativePath ? chalk8.gray(`${relativePath}:${location.line ?? 1}:${location.column ?? 1}`) : null;
|
|
2326
1873
|
const locationPrefix = locationText ? `${locationText} ` : "";
|
|
2327
1874
|
return `${prefix} ${locationPrefix}${diagnostic.message}`;
|
|
2328
1875
|
}
|
|
2329
1876
|
function registerSpecCommand(program, dependencies = {}) {
|
|
2330
|
-
const { createDocCov, writeFileSync:
|
|
2331
|
-
...
|
|
1877
|
+
const { createDocCov, writeFileSync: writeFileSync4, log, error } = {
|
|
1878
|
+
...defaultDependencies4,
|
|
2332
1879
|
...dependencies
|
|
2333
1880
|
};
|
|
2334
|
-
program.command("spec [entry]").description("Generate OpenPkg specification (JSON)").option("--cwd <dir>", "Working directory", process.cwd()).option("-p, --package <name>", "Target package name (for monorepos)").option("-o, --output <file>", "Output file path", "openpkg.json").option("--include <patterns>", "Include exports matching pattern (comma-separated)").option("--exclude <patterns>", "Exclude exports matching pattern (comma-separated)").option("--skip-resolve", "Skip external type resolution from node_modules").option("--max-type-depth <n>", "Maximum depth for type conversion", "20").option("--no-cache", "Bypass spec cache and force regeneration").option("--show-diagnostics", "Show TypeScript compiler diagnostics").action(async (entry, options) => {
|
|
1881
|
+
program.command("spec [entry]").description("Generate OpenPkg specification (JSON)").option("--cwd <dir>", "Working directory", process.cwd()).option("-p, --package <name>", "Target package name (for monorepos)").option("-o, --output <file>", "Output file path", "openpkg.json").option("--include <patterns>", "Include exports matching pattern (comma-separated)").option("--exclude <patterns>", "Exclude exports matching pattern (comma-separated)").option("--skip-resolve", "Skip external type resolution from node_modules").option("--max-type-depth <n>", "Maximum depth for type conversion", "20").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) => {
|
|
2335
1882
|
try {
|
|
2336
|
-
const
|
|
1883
|
+
const steps = new StepProgress([
|
|
1884
|
+
{ label: "Resolved target", activeLabel: "Resolving target" },
|
|
1885
|
+
{ label: "Loaded config", activeLabel: "Loading config" },
|
|
1886
|
+
{ label: "Generated spec", activeLabel: "Generating spec" },
|
|
1887
|
+
{ label: "Validated schema", activeLabel: "Validating schema" },
|
|
1888
|
+
{ label: "Wrote output", activeLabel: "Writing output" }
|
|
1889
|
+
]);
|
|
1890
|
+
steps.start();
|
|
1891
|
+
const fileSystem = new NodeFileSystem3(options.cwd);
|
|
2337
1892
|
const resolved = await resolveTarget3(fileSystem, {
|
|
2338
1893
|
cwd: options.cwd,
|
|
2339
1894
|
package: options.package,
|
|
2340
1895
|
entry
|
|
2341
1896
|
});
|
|
2342
1897
|
const { targetDir, entryFile, packageInfo, entryPointInfo } = resolved;
|
|
2343
|
-
|
|
2344
|
-
log(chalk8.gray(`Found package at ${packageInfo.path}`));
|
|
2345
|
-
}
|
|
2346
|
-
if (!entry) {
|
|
2347
|
-
log(chalk8.gray(`Auto-detected entry point: ${entryPointInfo.path} (from ${entryPointInfo.source})`));
|
|
2348
|
-
}
|
|
1898
|
+
steps.next();
|
|
2349
1899
|
let config = null;
|
|
2350
1900
|
try {
|
|
2351
1901
|
config = await loadDocCovConfig(targetDir);
|
|
2352
|
-
if (config?.filePath) {
|
|
2353
|
-
log(chalk8.gray(`Loaded configuration from ${path9.relative(targetDir, config.filePath)}`));
|
|
2354
|
-
}
|
|
2355
1902
|
} catch (configError) {
|
|
2356
1903
|
error(chalk8.red("Failed to load DocCov config:"), configError instanceof Error ? configError.message : configError);
|
|
2357
1904
|
process.exit(1);
|
|
2358
1905
|
}
|
|
1906
|
+
steps.next();
|
|
2359
1907
|
const cliFilters = {
|
|
2360
1908
|
include: parseListFlag(options.include),
|
|
2361
1909
|
exclude: parseListFlag(options.exclude)
|
|
2362
1910
|
};
|
|
2363
1911
|
const resolvedFilters = mergeFilterOptions(config, cliFilters);
|
|
2364
|
-
for (const message of resolvedFilters.messages) {
|
|
2365
|
-
log(chalk8.gray(`${message}`));
|
|
2366
|
-
}
|
|
2367
1912
|
const resolveExternalTypes = !options.skipResolve;
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
}
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
} catch (generationError) {
|
|
2393
|
-
process.stdout.write(chalk8.red(`> Failed to generate spec
|
|
2394
|
-
`));
|
|
2395
|
-
throw generationError;
|
|
2396
|
-
}
|
|
1913
|
+
const doccov = createDocCov({
|
|
1914
|
+
resolveExternalTypes,
|
|
1915
|
+
maxDepth: options.maxTypeDepth ? parseInt(options.maxTypeDepth, 10) : undefined,
|
|
1916
|
+
useCache: options.cache !== false,
|
|
1917
|
+
cwd: options.cwd
|
|
1918
|
+
});
|
|
1919
|
+
const generationInput = {
|
|
1920
|
+
entryPoint: path7.relative(targetDir, entryFile),
|
|
1921
|
+
entryPointSource: entryPointInfo.source,
|
|
1922
|
+
isDeclarationOnly: entryPointInfo.isDeclarationOnly ?? false,
|
|
1923
|
+
generatorName: "@doccov/cli",
|
|
1924
|
+
generatorVersion: version,
|
|
1925
|
+
packageManager: packageInfo?.packageManager,
|
|
1926
|
+
isMonorepo: resolved.isMonorepo,
|
|
1927
|
+
targetPackage: packageInfo?.name
|
|
1928
|
+
};
|
|
1929
|
+
const analyzeOptions = resolvedFilters.include || resolvedFilters.exclude ? {
|
|
1930
|
+
filters: {
|
|
1931
|
+
include: resolvedFilters.include,
|
|
1932
|
+
exclude: resolvedFilters.exclude
|
|
1933
|
+
},
|
|
1934
|
+
generationInput
|
|
1935
|
+
} : { generationInput };
|
|
1936
|
+
const result = await doccov.analyzeFileWithDiagnostics(entryFile, analyzeOptions);
|
|
2397
1937
|
if (!result) {
|
|
2398
1938
|
throw new Error("Failed to produce an OpenPkg spec.");
|
|
2399
1939
|
}
|
|
1940
|
+
steps.next();
|
|
2400
1941
|
const normalized = normalize(result.spec);
|
|
2401
1942
|
const validation = validateSpec(normalized);
|
|
2402
1943
|
if (!validation.ok) {
|
|
@@ -2406,11 +1947,49 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
2406
1947
|
}
|
|
2407
1948
|
process.exit(1);
|
|
2408
1949
|
}
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
1950
|
+
steps.next();
|
|
1951
|
+
const outputPath = path7.resolve(process.cwd(), options.output);
|
|
1952
|
+
writeFileSync4(outputPath, JSON.stringify(normalized, null, 2));
|
|
1953
|
+
steps.complete(`Generated ${options.output}`);
|
|
2412
1954
|
log(chalk8.gray(` ${getArrayLength(normalized.exports)} exports`));
|
|
2413
1955
|
log(chalk8.gray(` ${getArrayLength(normalized.types)} types`));
|
|
1956
|
+
if (options.verbose && normalized.generation) {
|
|
1957
|
+
const gen = normalized.generation;
|
|
1958
|
+
log("");
|
|
1959
|
+
log(chalk8.bold("Generation Info"));
|
|
1960
|
+
log(chalk8.gray(` Timestamp: ${gen.timestamp}`));
|
|
1961
|
+
log(chalk8.gray(` Generator: ${gen.generator.name}@${gen.generator.version}`));
|
|
1962
|
+
log(chalk8.gray(` Entry point: ${gen.analysis.entryPoint}`));
|
|
1963
|
+
log(chalk8.gray(` Detected via: ${gen.analysis.entryPointSource}`));
|
|
1964
|
+
log(chalk8.gray(` Declaration only: ${gen.analysis.isDeclarationOnly ? "yes" : "no"}`));
|
|
1965
|
+
log(chalk8.gray(` External types: ${gen.analysis.resolvedExternalTypes ? "resolved" : "skipped"}`));
|
|
1966
|
+
if (gen.analysis.maxTypeDepth) {
|
|
1967
|
+
log(chalk8.gray(` Max type depth: ${gen.analysis.maxTypeDepth}`));
|
|
1968
|
+
}
|
|
1969
|
+
log("");
|
|
1970
|
+
log(chalk8.bold("Environment"));
|
|
1971
|
+
log(chalk8.gray(` node_modules: ${gen.environment.hasNodeModules ? "found" : "not found"}`));
|
|
1972
|
+
if (gen.environment.packageManager) {
|
|
1973
|
+
log(chalk8.gray(` Package manager: ${gen.environment.packageManager}`));
|
|
1974
|
+
}
|
|
1975
|
+
if (gen.environment.isMonorepo) {
|
|
1976
|
+
log(chalk8.gray(` Monorepo: yes`));
|
|
1977
|
+
}
|
|
1978
|
+
if (gen.environment.targetPackage) {
|
|
1979
|
+
log(chalk8.gray(` Target package: ${gen.environment.targetPackage}`));
|
|
1980
|
+
}
|
|
1981
|
+
if (gen.issues.length > 0) {
|
|
1982
|
+
log("");
|
|
1983
|
+
log(chalk8.bold("Issues"));
|
|
1984
|
+
for (const issue of gen.issues) {
|
|
1985
|
+
const prefix = issue.severity === "error" ? chalk8.red(">") : issue.severity === "warning" ? chalk8.yellow(">") : chalk8.cyan(">");
|
|
1986
|
+
log(`${prefix} [${issue.code}] ${issue.message}`);
|
|
1987
|
+
if (issue.suggestion) {
|
|
1988
|
+
log(chalk8.gray(` ${issue.suggestion}`));
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
2414
1993
|
if (options.showDiagnostics && result.diagnostics.length > 0) {
|
|
2415
1994
|
log("");
|
|
2416
1995
|
log(chalk8.bold("Diagnostics"));
|
|
@@ -2428,8 +2007,8 @@ function registerSpecCommand(program, dependencies = {}) {
|
|
|
2428
2007
|
|
|
2429
2008
|
// src/cli.ts
|
|
2430
2009
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
2431
|
-
var __dirname2 =
|
|
2432
|
-
var packageJson = JSON.parse(
|
|
2010
|
+
var __dirname2 = path8.dirname(__filename2);
|
|
2011
|
+
var packageJson = JSON.parse(readFileSync3(path8.join(__dirname2, "../package.json"), "utf-8"));
|
|
2433
2012
|
var program = new Command;
|
|
2434
2013
|
program.name("doccov").description("DocCov - Documentation coverage and drift detection for TypeScript").version(packageJson.version);
|
|
2435
2014
|
registerCheckCommand(program);
|
|
@@ -2437,7 +2016,6 @@ registerInfoCommand(program);
|
|
|
2437
2016
|
registerSpecCommand(program);
|
|
2438
2017
|
registerDiffCommand(program);
|
|
2439
2018
|
registerInitCommand(program);
|
|
2440
|
-
registerScanCommand(program);
|
|
2441
2019
|
program.command("*", { hidden: true }).action(() => {
|
|
2442
2020
|
program.outputHelp();
|
|
2443
2021
|
});
|
package/dist/config/index.js
CHANGED
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
-
for (let key of __getOwnPropNames(mod))
|
|
11
|
-
if (!__hasOwnProp.call(to, key))
|
|
12
|
-
__defProp(to, key, {
|
|
13
|
-
get: () => mod[key],
|
|
14
|
-
enumerable: true
|
|
15
|
-
});
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
-
|
|
20
1
|
// src/config/doccov-config.ts
|
|
21
2
|
import { access } from "node:fs/promises";
|
|
22
3
|
import path from "node:path";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doccov/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "DocCov CLI - Documentation coverage and drift detection for TypeScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -48,14 +48,13 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@ai-sdk/anthropic": "^1.0.0",
|
|
50
50
|
"@ai-sdk/openai": "^1.0.0",
|
|
51
|
-
"@doccov/sdk": "^0.
|
|
51
|
+
"@doccov/sdk": "^0.13.0",
|
|
52
52
|
"@inquirer/prompts": "^7.8.0",
|
|
53
|
-
"@openpkg-ts/spec": "^0.
|
|
53
|
+
"@openpkg-ts/spec": "^0.9.0",
|
|
54
54
|
"ai": "^4.0.0",
|
|
55
55
|
"chalk": "^5.4.1",
|
|
56
56
|
"commander": "^14.0.0",
|
|
57
57
|
"glob": "^11.0.0",
|
|
58
|
-
"ora": "^9.0.0",
|
|
59
58
|
"simple-git": "^3.27.0",
|
|
60
59
|
"zod": "^3.25.0"
|
|
61
60
|
},
|