@doccov/sdk 0.24.0 → 0.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/index.d.ts +2 -2
- package/dist/analysis/index.js +2 -1
- package/dist/index.d.ts +888 -888
- package/dist/index.js +443 -447
- package/dist/shared/{chunk-c1f9mytc.js → chunk-p1stkhse.js} +324 -324
- package/dist/types/index.d.ts +136 -136
- package/package.json +1 -1
|
@@ -3,250 +3,6 @@ import {
|
|
|
3
3
|
REPORT_VERSION
|
|
4
4
|
} from "./chunk-esptwrfq.js";
|
|
5
5
|
|
|
6
|
-
// src/extract/schema/standard-schema.ts
|
|
7
|
-
import { spawn } from "node:child_process";
|
|
8
|
-
import * as fs from "node:fs";
|
|
9
|
-
import * as path from "node:path";
|
|
10
|
-
function isStandardJSONSchema(obj) {
|
|
11
|
-
if (typeof obj !== "object" || obj === null)
|
|
12
|
-
return false;
|
|
13
|
-
const std = obj["~standard"];
|
|
14
|
-
if (typeof std !== "object" || std === null)
|
|
15
|
-
return false;
|
|
16
|
-
const stdObj = std;
|
|
17
|
-
if (typeof stdObj.version !== "number")
|
|
18
|
-
return false;
|
|
19
|
-
if (typeof stdObj.vendor !== "string")
|
|
20
|
-
return false;
|
|
21
|
-
const jsonSchema = stdObj.jsonSchema;
|
|
22
|
-
if (typeof jsonSchema !== "object" || jsonSchema === null)
|
|
23
|
-
return false;
|
|
24
|
-
const jsObj = jsonSchema;
|
|
25
|
-
return typeof jsObj.output === "function";
|
|
26
|
-
}
|
|
27
|
-
var WORKER_SCRIPT = `
|
|
28
|
-
const path = require('path');
|
|
29
|
-
const { pathToFileURL } = require('url');
|
|
30
|
-
|
|
31
|
-
// TypeBox detection: schemas have Symbol.for('TypeBox.Kind') and are JSON Schema
|
|
32
|
-
const TYPEBOX_KIND = Symbol.for('TypeBox.Kind');
|
|
33
|
-
|
|
34
|
-
function isTypeBoxSchema(obj) {
|
|
35
|
-
if (!obj || typeof obj !== 'object') return false;
|
|
36
|
-
// TypeBox schemas always have Kind symbol (Union, Object, String, etc.)
|
|
37
|
-
// Also check for common JSON Schema props to avoid false positives
|
|
38
|
-
if (!obj[TYPEBOX_KIND]) return false;
|
|
39
|
-
return typeof obj.type === 'string' || 'anyOf' in obj || 'oneOf' in obj || 'allOf' in obj;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function sanitizeTypeBoxSchema(schema) {
|
|
43
|
-
// JSON.stringify removes symbol keys, keeping only JSON Schema props
|
|
44
|
-
return JSON.parse(JSON.stringify(schema));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function extract() {
|
|
48
|
-
// With node -e, argv is: [node, arg1, arg2, ...]
|
|
49
|
-
// (the -e script is NOT in argv)
|
|
50
|
-
const [modulePath, target] = process.argv.slice(1);
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
// Import the module using dynamic import (works with ESM and CJS)
|
|
54
|
-
const absPath = path.resolve(modulePath);
|
|
55
|
-
const mod = await import(pathToFileURL(absPath).href);
|
|
56
|
-
const results = [];
|
|
57
|
-
|
|
58
|
-
// Build exports map - handle both ESM and CJS (where exports are in mod.default)
|
|
59
|
-
const exports = {};
|
|
60
|
-
for (const [name, value] of Object.entries(mod)) {
|
|
61
|
-
if (name === 'default' && typeof value === 'object' && value !== null) {
|
|
62
|
-
// CJS module: spread default exports
|
|
63
|
-
Object.assign(exports, value);
|
|
64
|
-
} else if (name !== 'default') {
|
|
65
|
-
exports[name] = value;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Check each export
|
|
70
|
-
for (const [name, value] of Object.entries(exports)) {
|
|
71
|
-
if (name.startsWith('_')) continue;
|
|
72
|
-
if (typeof value !== 'object' || value === null) continue;
|
|
73
|
-
|
|
74
|
-
// Priority 1: Standard Schema (Zod 4.2+, ArkType, etc.)
|
|
75
|
-
const std = value['~standard'];
|
|
76
|
-
if (std && typeof std === 'object' && typeof std.version === 'number' && typeof std.vendor === 'string' && std.jsonSchema && typeof std.jsonSchema.output === 'function') {
|
|
77
|
-
try {
|
|
78
|
-
const outputSchema = std.jsonSchema.output(target);
|
|
79
|
-
const inputSchema = std.jsonSchema.input ? std.jsonSchema.input(target) : undefined;
|
|
80
|
-
results.push({
|
|
81
|
-
exportName: name,
|
|
82
|
-
vendor: std.vendor,
|
|
83
|
-
outputSchema,
|
|
84
|
-
inputSchema
|
|
85
|
-
});
|
|
86
|
-
} catch (e) {
|
|
87
|
-
// Skip schemas that fail to extract
|
|
88
|
-
}
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Priority 2: TypeBox (schema IS JSON Schema)
|
|
93
|
-
if (isTypeBoxSchema(value)) {
|
|
94
|
-
try {
|
|
95
|
-
results.push({
|
|
96
|
-
exportName: name,
|
|
97
|
-
vendor: 'typebox',
|
|
98
|
-
outputSchema: sanitizeTypeBoxSchema(value)
|
|
99
|
-
});
|
|
100
|
-
} catch (e) {
|
|
101
|
-
// Skip schemas that fail to extract
|
|
102
|
-
}
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
console.log(JSON.stringify({ success: true, results }));
|
|
108
|
-
} catch (e) {
|
|
109
|
-
console.log(JSON.stringify({ success: false, error: e.message }));
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
extract();
|
|
114
|
-
`;
|
|
115
|
-
function resolveCompiledPath(tsPath, baseDir) {
|
|
116
|
-
const relativePath = path.relative(baseDir, tsPath);
|
|
117
|
-
const withoutExt = relativePath.replace(/\.tsx?$/, "");
|
|
118
|
-
const candidates = [
|
|
119
|
-
path.join(baseDir, `${withoutExt}.js`),
|
|
120
|
-
path.join(baseDir, "dist", `${withoutExt.replace(/^src\//, "")}.js`),
|
|
121
|
-
path.join(baseDir, "build", `${withoutExt.replace(/^src\//, "")}.js`),
|
|
122
|
-
path.join(baseDir, "lib", `${withoutExt.replace(/^src\//, "")}.js`)
|
|
123
|
-
];
|
|
124
|
-
for (const candidate of candidates) {
|
|
125
|
-
if (fs.existsSync(candidate)) {
|
|
126
|
-
return candidate;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
async function extractStandardSchemas(compiledJsPath, options = {}) {
|
|
132
|
-
const { timeout = 1e4, target = "draft-2020-12" } = options;
|
|
133
|
-
const result = {
|
|
134
|
-
schemas: new Map,
|
|
135
|
-
errors: []
|
|
136
|
-
};
|
|
137
|
-
if (!fs.existsSync(compiledJsPath)) {
|
|
138
|
-
result.errors.push(`Compiled JS not found: ${compiledJsPath}`);
|
|
139
|
-
return result;
|
|
140
|
-
}
|
|
141
|
-
return new Promise((resolve) => {
|
|
142
|
-
const child = spawn("node", ["-e", WORKER_SCRIPT, compiledJsPath, target], {
|
|
143
|
-
timeout,
|
|
144
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
145
|
-
});
|
|
146
|
-
let stdout = "";
|
|
147
|
-
let stderr = "";
|
|
148
|
-
child.stdout.on("data", (data) => {
|
|
149
|
-
stdout += data.toString();
|
|
150
|
-
});
|
|
151
|
-
child.stderr.on("data", (data) => {
|
|
152
|
-
stderr += data.toString();
|
|
153
|
-
});
|
|
154
|
-
child.on("close", (code) => {
|
|
155
|
-
if (code !== 0) {
|
|
156
|
-
result.errors.push(`Extraction process failed: ${stderr || `exit code ${code}`}`);
|
|
157
|
-
resolve(result);
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
try {
|
|
161
|
-
const parsed = JSON.parse(stdout);
|
|
162
|
-
if (!parsed.success) {
|
|
163
|
-
result.errors.push(`Extraction failed: ${parsed.error}`);
|
|
164
|
-
resolve(result);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
for (const item of parsed.results) {
|
|
168
|
-
result.schemas.set(item.exportName, {
|
|
169
|
-
exportName: item.exportName,
|
|
170
|
-
vendor: item.vendor,
|
|
171
|
-
outputSchema: item.outputSchema,
|
|
172
|
-
inputSchema: item.inputSchema
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
} catch (e) {
|
|
176
|
-
result.errors.push(`Failed to parse extraction output: ${e}`);
|
|
177
|
-
}
|
|
178
|
-
resolve(result);
|
|
179
|
-
});
|
|
180
|
-
child.on("error", (err) => {
|
|
181
|
-
result.errors.push(`Subprocess error: ${err.message}`);
|
|
182
|
-
resolve(result);
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
async function extractStandardSchemasFromProject(entryFile, baseDir, options = {}) {
|
|
187
|
-
const compiledPath = resolveCompiledPath(entryFile, baseDir);
|
|
188
|
-
if (!compiledPath) {
|
|
189
|
-
return {
|
|
190
|
-
schemas: new Map,
|
|
191
|
-
errors: [`Could not find compiled JS for ${entryFile}. Build the project first.`]
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
return extractStandardSchemas(compiledPath, options);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// src/analysis/schema-detection.ts
|
|
198
|
-
async function detectRuntimeSchemas(context) {
|
|
199
|
-
const { baseDir, entryFile } = context;
|
|
200
|
-
const compiledPath = resolveCompiledPath(entryFile, baseDir);
|
|
201
|
-
if (!compiledPath) {
|
|
202
|
-
return {
|
|
203
|
-
schemas: new Map,
|
|
204
|
-
errors: [],
|
|
205
|
-
noCompiledJsWarning: true
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
const extraction = await extractStandardSchemasFromProject(entryFile, baseDir);
|
|
209
|
-
const schemas = new Map;
|
|
210
|
-
for (const [name, result] of extraction.schemas) {
|
|
211
|
-
schemas.set(name, {
|
|
212
|
-
schema: result.outputSchema,
|
|
213
|
-
vendor: result.vendor
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
return {
|
|
217
|
-
schemas,
|
|
218
|
-
errors: extraction.errors
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// src/analysis/drift/types.ts
|
|
223
|
-
var DRIFT_CATEGORIES = {
|
|
224
|
-
"param-mismatch": "structural",
|
|
225
|
-
"param-type-mismatch": "structural",
|
|
226
|
-
"return-type-mismatch": "structural",
|
|
227
|
-
"optionality-mismatch": "structural",
|
|
228
|
-
"generic-constraint-mismatch": "structural",
|
|
229
|
-
"property-type-drift": "structural",
|
|
230
|
-
"async-mismatch": "structural",
|
|
231
|
-
"deprecated-mismatch": "semantic",
|
|
232
|
-
"visibility-mismatch": "semantic",
|
|
233
|
-
"broken-link": "semantic",
|
|
234
|
-
"example-drift": "example",
|
|
235
|
-
"example-syntax-error": "example",
|
|
236
|
-
"example-runtime-error": "example",
|
|
237
|
-
"example-assertion-failed": "example"
|
|
238
|
-
};
|
|
239
|
-
var DRIFT_CATEGORY_LABELS = {
|
|
240
|
-
structural: "Signature mismatches",
|
|
241
|
-
semantic: "Metadata issues",
|
|
242
|
-
example: "Example problems"
|
|
243
|
-
};
|
|
244
|
-
var DRIFT_CATEGORY_DESCRIPTIONS = {
|
|
245
|
-
structural: "JSDoc types or parameters don't match the actual code signature",
|
|
246
|
-
semantic: "Deprecation, visibility, or reference issues",
|
|
247
|
-
example: "@example code has errors or doesn't work correctly"
|
|
248
|
-
};
|
|
249
|
-
|
|
250
6
|
// src/fix/deterministic-fixes.ts
|
|
251
7
|
var FIXABLE_DRIFT_TYPES = new Set([
|
|
252
8
|
"param-mismatch",
|
|
@@ -671,8 +427,8 @@ function categorizeDrifts(drifts) {
|
|
|
671
427
|
}
|
|
672
428
|
|
|
673
429
|
// src/fix/jsdoc-writer.ts
|
|
674
|
-
import * as
|
|
675
|
-
import * as
|
|
430
|
+
import * as fs from "node:fs";
|
|
431
|
+
import * as path from "node:path";
|
|
676
432
|
|
|
677
433
|
// src/ts-module.ts
|
|
678
434
|
import * as tsNamespace from "typescript";
|
|
@@ -1011,7 +767,7 @@ async function applyEdits(edits) {
|
|
|
1011
767
|
}
|
|
1012
768
|
for (const [filePath, fileEdits] of editsByFile) {
|
|
1013
769
|
try {
|
|
1014
|
-
const content =
|
|
770
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
1015
771
|
const lines = content.split(`
|
|
1016
772
|
`);
|
|
1017
773
|
const sortedEdits = [...fileEdits].sort((a, b) => b.startLine - a.startLine);
|
|
@@ -1025,7 +781,7 @@ async function applyEdits(edits) {
|
|
|
1025
781
|
}
|
|
1026
782
|
result.editsApplied++;
|
|
1027
783
|
}
|
|
1028
|
-
|
|
784
|
+
fs.writeFileSync(filePath, lines.join(`
|
|
1029
785
|
`));
|
|
1030
786
|
result.filesModified++;
|
|
1031
787
|
} catch (error) {
|
|
@@ -1038,71 +794,21 @@ async function applyEdits(edits) {
|
|
|
1038
794
|
return result;
|
|
1039
795
|
}
|
|
1040
796
|
function createSourceFile(filePath) {
|
|
1041
|
-
const content =
|
|
1042
|
-
return ts.createSourceFile(
|
|
797
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
798
|
+
return ts.createSourceFile(path.basename(filePath), content, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
1043
799
|
}
|
|
1044
|
-
// src/analysis/drift/
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
example: []
|
|
1057
|
-
};
|
|
1058
|
-
for (const drift of drifts) {
|
|
1059
|
-
const categorized = categorizeDrift(drift);
|
|
1060
|
-
grouped[categorized.category].push(categorized);
|
|
1061
|
-
}
|
|
1062
|
-
return grouped;
|
|
1063
|
-
}
|
|
1064
|
-
function getDriftSummary(drifts) {
|
|
1065
|
-
const grouped = groupDriftsByCategory(drifts);
|
|
1066
|
-
return {
|
|
1067
|
-
total: drifts.length,
|
|
1068
|
-
byCategory: {
|
|
1069
|
-
structural: grouped.structural.length,
|
|
1070
|
-
semantic: grouped.semantic.length,
|
|
1071
|
-
example: grouped.example.length
|
|
1072
|
-
},
|
|
1073
|
-
fixable: drifts.filter((d) => isFixableDrift(d)).length
|
|
1074
|
-
};
|
|
1075
|
-
}
|
|
1076
|
-
function formatDriftSummaryLine(summary) {
|
|
1077
|
-
if (summary.total === 0) {
|
|
1078
|
-
return "No drift detected";
|
|
1079
|
-
}
|
|
1080
|
-
const parts = [];
|
|
1081
|
-
if (summary.byCategory.structural > 0) {
|
|
1082
|
-
parts.push(`${summary.byCategory.structural} structural`);
|
|
1083
|
-
}
|
|
1084
|
-
if (summary.byCategory.semantic > 0) {
|
|
1085
|
-
parts.push(`${summary.byCategory.semantic} semantic`);
|
|
1086
|
-
}
|
|
1087
|
-
if (summary.byCategory.example > 0) {
|
|
1088
|
-
parts.push(`${summary.byCategory.example} example`);
|
|
1089
|
-
}
|
|
1090
|
-
const fixableNote = summary.fixable > 0 ? ` (${summary.fixable} auto-fixable)` : "";
|
|
1091
|
-
return `${summary.total} issues (${parts.join(", ")})${fixableNote}`;
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
// src/analysis/drift/example-drift.ts
|
|
1095
|
-
import ts2 from "typescript";
|
|
1096
|
-
|
|
1097
|
-
// src/utils/builtin-detection.ts
|
|
1098
|
-
function isBuiltInTypeName(name) {
|
|
1099
|
-
if (name.length === 1 && /^[A-Z]$/.test(name)) {
|
|
1100
|
-
return true;
|
|
1101
|
-
}
|
|
1102
|
-
if (name.startsWith("__")) {
|
|
1103
|
-
return true;
|
|
1104
|
-
}
|
|
1105
|
-
return BUILTIN_TYPE_NAMES.has(name) || LIBRARY_INTERNAL_PATTERNS.some((re) => re.test(name));
|
|
800
|
+
// src/analysis/drift/example-drift.ts
|
|
801
|
+
import ts2 from "typescript";
|
|
802
|
+
|
|
803
|
+
// src/utils/builtin-detection.ts
|
|
804
|
+
function isBuiltInTypeName(name) {
|
|
805
|
+
if (name.length === 1 && /^[A-Z]$/.test(name)) {
|
|
806
|
+
return true;
|
|
807
|
+
}
|
|
808
|
+
if (name.startsWith("__")) {
|
|
809
|
+
return true;
|
|
810
|
+
}
|
|
811
|
+
return BUILTIN_TYPE_NAMES.has(name) || LIBRARY_INTERNAL_PATTERNS.some((re) => re.test(name));
|
|
1106
812
|
}
|
|
1107
813
|
function isBuiltInIdentifier(identifier) {
|
|
1108
814
|
return BUILTIN_GLOBALS.has(identifier);
|
|
@@ -2310,6 +2016,84 @@ function computeExportDrift(entry, registry) {
|
|
|
2310
2016
|
];
|
|
2311
2017
|
}
|
|
2312
2018
|
|
|
2019
|
+
// src/analysis/drift/types.ts
|
|
2020
|
+
var DRIFT_CATEGORIES = {
|
|
2021
|
+
"param-mismatch": "structural",
|
|
2022
|
+
"param-type-mismatch": "structural",
|
|
2023
|
+
"return-type-mismatch": "structural",
|
|
2024
|
+
"optionality-mismatch": "structural",
|
|
2025
|
+
"generic-constraint-mismatch": "structural",
|
|
2026
|
+
"property-type-drift": "structural",
|
|
2027
|
+
"async-mismatch": "structural",
|
|
2028
|
+
"deprecated-mismatch": "semantic",
|
|
2029
|
+
"visibility-mismatch": "semantic",
|
|
2030
|
+
"broken-link": "semantic",
|
|
2031
|
+
"example-drift": "example",
|
|
2032
|
+
"example-syntax-error": "example",
|
|
2033
|
+
"example-runtime-error": "example",
|
|
2034
|
+
"example-assertion-failed": "example"
|
|
2035
|
+
};
|
|
2036
|
+
var DRIFT_CATEGORY_LABELS = {
|
|
2037
|
+
structural: "Signature mismatches",
|
|
2038
|
+
semantic: "Metadata issues",
|
|
2039
|
+
example: "Example problems"
|
|
2040
|
+
};
|
|
2041
|
+
var DRIFT_CATEGORY_DESCRIPTIONS = {
|
|
2042
|
+
structural: "JSDoc types or parameters don't match the actual code signature",
|
|
2043
|
+
semantic: "Deprecation, visibility, or reference issues",
|
|
2044
|
+
example: "@example code has errors or doesn't work correctly"
|
|
2045
|
+
};
|
|
2046
|
+
|
|
2047
|
+
// src/analysis/drift/categorize.ts
|
|
2048
|
+
function categorizeDrift(drift) {
|
|
2049
|
+
return {
|
|
2050
|
+
...drift,
|
|
2051
|
+
category: DRIFT_CATEGORIES[drift.type],
|
|
2052
|
+
fixable: isFixableDrift(drift)
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
function groupDriftsByCategory(drifts) {
|
|
2056
|
+
const grouped = {
|
|
2057
|
+
structural: [],
|
|
2058
|
+
semantic: [],
|
|
2059
|
+
example: []
|
|
2060
|
+
};
|
|
2061
|
+
for (const drift of drifts) {
|
|
2062
|
+
const categorized = categorizeDrift(drift);
|
|
2063
|
+
grouped[categorized.category].push(categorized);
|
|
2064
|
+
}
|
|
2065
|
+
return grouped;
|
|
2066
|
+
}
|
|
2067
|
+
function getDriftSummary(drifts) {
|
|
2068
|
+
const grouped = groupDriftsByCategory(drifts);
|
|
2069
|
+
return {
|
|
2070
|
+
total: drifts.length,
|
|
2071
|
+
byCategory: {
|
|
2072
|
+
structural: grouped.structural.length,
|
|
2073
|
+
semantic: grouped.semantic.length,
|
|
2074
|
+
example: grouped.example.length
|
|
2075
|
+
},
|
|
2076
|
+
fixable: drifts.filter((d) => isFixableDrift(d)).length
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
function formatDriftSummaryLine(summary) {
|
|
2080
|
+
if (summary.total === 0) {
|
|
2081
|
+
return "No drift detected";
|
|
2082
|
+
}
|
|
2083
|
+
const parts = [];
|
|
2084
|
+
if (summary.byCategory.structural > 0) {
|
|
2085
|
+
parts.push(`${summary.byCategory.structural} structural`);
|
|
2086
|
+
}
|
|
2087
|
+
if (summary.byCategory.semantic > 0) {
|
|
2088
|
+
parts.push(`${summary.byCategory.semantic} semantic`);
|
|
2089
|
+
}
|
|
2090
|
+
if (summary.byCategory.example > 0) {
|
|
2091
|
+
parts.push(`${summary.byCategory.example} example`);
|
|
2092
|
+
}
|
|
2093
|
+
const fixableNote = summary.fixable > 0 ? ` (${summary.fixable} auto-fixable)` : "";
|
|
2094
|
+
return `${summary.total} issues (${parts.join(", ")})${fixableNote}`;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2313
2097
|
// src/analysis/drift/coverage.ts
|
|
2314
2098
|
function calculateAggregateCoverage(spec) {
|
|
2315
2099
|
const exports = spec.exports ?? [];
|
|
@@ -2416,8 +2200,8 @@ function enrichSpec(spec, options = {}) {
|
|
|
2416
2200
|
}
|
|
2417
2201
|
|
|
2418
2202
|
// src/analysis/report.ts
|
|
2419
|
-
import * as
|
|
2420
|
-
import * as
|
|
2203
|
+
import * as fs2 from "node:fs";
|
|
2204
|
+
import * as path2 from "node:path";
|
|
2421
2205
|
function generateReport(spec) {
|
|
2422
2206
|
const enriched = enrichSpec(spec);
|
|
2423
2207
|
return generateReportFromEnriched(enriched);
|
|
@@ -2471,23 +2255,23 @@ function generateReportFromEnriched(enriched) {
|
|
|
2471
2255
|
}
|
|
2472
2256
|
function loadCachedReport(reportPath = DEFAULT_REPORT_PATH) {
|
|
2473
2257
|
try {
|
|
2474
|
-
const fullPath =
|
|
2475
|
-
if (!
|
|
2258
|
+
const fullPath = path2.resolve(reportPath);
|
|
2259
|
+
if (!fs2.existsSync(fullPath)) {
|
|
2476
2260
|
return null;
|
|
2477
2261
|
}
|
|
2478
|
-
const content =
|
|
2262
|
+
const content = fs2.readFileSync(fullPath, "utf-8");
|
|
2479
2263
|
return JSON.parse(content);
|
|
2480
2264
|
} catch {
|
|
2481
2265
|
return null;
|
|
2482
2266
|
}
|
|
2483
2267
|
}
|
|
2484
2268
|
function saveReport(report, reportPath = DEFAULT_REPORT_PATH) {
|
|
2485
|
-
const fullPath =
|
|
2486
|
-
const dir =
|
|
2487
|
-
if (!
|
|
2488
|
-
|
|
2269
|
+
const fullPath = path2.resolve(reportPath);
|
|
2270
|
+
const dir = path2.dirname(fullPath);
|
|
2271
|
+
if (!fs2.existsSync(dir)) {
|
|
2272
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
2489
2273
|
}
|
|
2490
|
-
|
|
2274
|
+
fs2.writeFileSync(fullPath, JSON.stringify(report, null, 2));
|
|
2491
2275
|
}
|
|
2492
2276
|
function isCachedReportValid(reportPath = DEFAULT_REPORT_PATH, sourceFiles = []) {
|
|
2493
2277
|
const report = loadCachedReport(reportPath);
|
|
@@ -2497,7 +2281,7 @@ function isCachedReportValid(reportPath = DEFAULT_REPORT_PATH, sourceFiles = [])
|
|
|
2497
2281
|
const reportTime = new Date(report.generatedAt).getTime();
|
|
2498
2282
|
for (const file of sourceFiles) {
|
|
2499
2283
|
try {
|
|
2500
|
-
const stat =
|
|
2284
|
+
const stat = fs2.statSync(file);
|
|
2501
2285
|
if (stat.mtimeMs > reportTime) {
|
|
2502
2286
|
return false;
|
|
2503
2287
|
}
|
|
@@ -2684,6 +2468,222 @@ function renderApiSurface(spec) {
|
|
|
2684
2468
|
`);
|
|
2685
2469
|
}
|
|
2686
2470
|
|
|
2471
|
+
// src/extract/schema/standard-schema.ts
|
|
2472
|
+
import { spawn } from "node:child_process";
|
|
2473
|
+
import * as fs3 from "node:fs";
|
|
2474
|
+
import * as path3 from "node:path";
|
|
2475
|
+
function isStandardJSONSchema(obj) {
|
|
2476
|
+
if (typeof obj !== "object" || obj === null)
|
|
2477
|
+
return false;
|
|
2478
|
+
const std = obj["~standard"];
|
|
2479
|
+
if (typeof std !== "object" || std === null)
|
|
2480
|
+
return false;
|
|
2481
|
+
const stdObj = std;
|
|
2482
|
+
if (typeof stdObj.version !== "number")
|
|
2483
|
+
return false;
|
|
2484
|
+
if (typeof stdObj.vendor !== "string")
|
|
2485
|
+
return false;
|
|
2486
|
+
const jsonSchema = stdObj.jsonSchema;
|
|
2487
|
+
if (typeof jsonSchema !== "object" || jsonSchema === null)
|
|
2488
|
+
return false;
|
|
2489
|
+
const jsObj = jsonSchema;
|
|
2490
|
+
return typeof jsObj.output === "function";
|
|
2491
|
+
}
|
|
2492
|
+
var WORKER_SCRIPT = `
|
|
2493
|
+
const path = require('path');
|
|
2494
|
+
const { pathToFileURL } = require('url');
|
|
2495
|
+
|
|
2496
|
+
// TypeBox detection: schemas have Symbol.for('TypeBox.Kind') and are JSON Schema
|
|
2497
|
+
const TYPEBOX_KIND = Symbol.for('TypeBox.Kind');
|
|
2498
|
+
|
|
2499
|
+
function isTypeBoxSchema(obj) {
|
|
2500
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
2501
|
+
// TypeBox schemas always have Kind symbol (Union, Object, String, etc.)
|
|
2502
|
+
// Also check for common JSON Schema props to avoid false positives
|
|
2503
|
+
if (!obj[TYPEBOX_KIND]) return false;
|
|
2504
|
+
return typeof obj.type === 'string' || 'anyOf' in obj || 'oneOf' in obj || 'allOf' in obj;
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
function sanitizeTypeBoxSchema(schema) {
|
|
2508
|
+
// JSON.stringify removes symbol keys, keeping only JSON Schema props
|
|
2509
|
+
return JSON.parse(JSON.stringify(schema));
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
async function extract() {
|
|
2513
|
+
// With node -e, argv is: [node, arg1, arg2, ...]
|
|
2514
|
+
// (the -e script is NOT in argv)
|
|
2515
|
+
const [modulePath, target] = process.argv.slice(1);
|
|
2516
|
+
|
|
2517
|
+
try {
|
|
2518
|
+
// Import the module using dynamic import (works with ESM and CJS)
|
|
2519
|
+
const absPath = path.resolve(modulePath);
|
|
2520
|
+
const mod = await import(pathToFileURL(absPath).href);
|
|
2521
|
+
const results = [];
|
|
2522
|
+
|
|
2523
|
+
// Build exports map - handle both ESM and CJS (where exports are in mod.default)
|
|
2524
|
+
const exports = {};
|
|
2525
|
+
for (const [name, value] of Object.entries(mod)) {
|
|
2526
|
+
if (name === 'default' && typeof value === 'object' && value !== null) {
|
|
2527
|
+
// CJS module: spread default exports
|
|
2528
|
+
Object.assign(exports, value);
|
|
2529
|
+
} else if (name !== 'default') {
|
|
2530
|
+
exports[name] = value;
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
// Check each export
|
|
2535
|
+
for (const [name, value] of Object.entries(exports)) {
|
|
2536
|
+
if (name.startsWith('_')) continue;
|
|
2537
|
+
if (typeof value !== 'object' || value === null) continue;
|
|
2538
|
+
|
|
2539
|
+
// Priority 1: Standard Schema (Zod 4.2+, ArkType, etc.)
|
|
2540
|
+
const std = value['~standard'];
|
|
2541
|
+
if (std && typeof std === 'object' && typeof std.version === 'number' && typeof std.vendor === 'string' && std.jsonSchema && typeof std.jsonSchema.output === 'function') {
|
|
2542
|
+
try {
|
|
2543
|
+
const outputSchema = std.jsonSchema.output(target);
|
|
2544
|
+
const inputSchema = std.jsonSchema.input ? std.jsonSchema.input(target) : undefined;
|
|
2545
|
+
results.push({
|
|
2546
|
+
exportName: name,
|
|
2547
|
+
vendor: std.vendor,
|
|
2548
|
+
outputSchema,
|
|
2549
|
+
inputSchema
|
|
2550
|
+
});
|
|
2551
|
+
} catch (e) {
|
|
2552
|
+
// Skip schemas that fail to extract
|
|
2553
|
+
}
|
|
2554
|
+
continue;
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
// Priority 2: TypeBox (schema IS JSON Schema)
|
|
2558
|
+
if (isTypeBoxSchema(value)) {
|
|
2559
|
+
try {
|
|
2560
|
+
results.push({
|
|
2561
|
+
exportName: name,
|
|
2562
|
+
vendor: 'typebox',
|
|
2563
|
+
outputSchema: sanitizeTypeBoxSchema(value)
|
|
2564
|
+
});
|
|
2565
|
+
} catch (e) {
|
|
2566
|
+
// Skip schemas that fail to extract
|
|
2567
|
+
}
|
|
2568
|
+
continue;
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
console.log(JSON.stringify({ success: true, results }));
|
|
2573
|
+
} catch (e) {
|
|
2574
|
+
console.log(JSON.stringify({ success: false, error: e.message }));
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
extract();
|
|
2579
|
+
`;
|
|
2580
|
+
function resolveCompiledPath(tsPath, baseDir) {
|
|
2581
|
+
const relativePath = path3.relative(baseDir, tsPath);
|
|
2582
|
+
const withoutExt = relativePath.replace(/\.tsx?$/, "");
|
|
2583
|
+
const candidates = [
|
|
2584
|
+
path3.join(baseDir, `${withoutExt}.js`),
|
|
2585
|
+
path3.join(baseDir, "dist", `${withoutExt.replace(/^src\//, "")}.js`),
|
|
2586
|
+
path3.join(baseDir, "build", `${withoutExt.replace(/^src\//, "")}.js`),
|
|
2587
|
+
path3.join(baseDir, "lib", `${withoutExt.replace(/^src\//, "")}.js`)
|
|
2588
|
+
];
|
|
2589
|
+
for (const candidate of candidates) {
|
|
2590
|
+
if (fs3.existsSync(candidate)) {
|
|
2591
|
+
return candidate;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
return null;
|
|
2595
|
+
}
|
|
2596
|
+
async function extractStandardSchemas(compiledJsPath, options = {}) {
|
|
2597
|
+
const { timeout = 1e4, target = "draft-2020-12" } = options;
|
|
2598
|
+
const result = {
|
|
2599
|
+
schemas: new Map,
|
|
2600
|
+
errors: []
|
|
2601
|
+
};
|
|
2602
|
+
if (!fs3.existsSync(compiledJsPath)) {
|
|
2603
|
+
result.errors.push(`Compiled JS not found: ${compiledJsPath}`);
|
|
2604
|
+
return result;
|
|
2605
|
+
}
|
|
2606
|
+
return new Promise((resolve2) => {
|
|
2607
|
+
const child = spawn("node", ["-e", WORKER_SCRIPT, compiledJsPath, target], {
|
|
2608
|
+
timeout,
|
|
2609
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2610
|
+
});
|
|
2611
|
+
let stdout = "";
|
|
2612
|
+
let stderr = "";
|
|
2613
|
+
child.stdout.on("data", (data) => {
|
|
2614
|
+
stdout += data.toString();
|
|
2615
|
+
});
|
|
2616
|
+
child.stderr.on("data", (data) => {
|
|
2617
|
+
stderr += data.toString();
|
|
2618
|
+
});
|
|
2619
|
+
child.on("close", (code) => {
|
|
2620
|
+
if (code !== 0) {
|
|
2621
|
+
result.errors.push(`Extraction process failed: ${stderr || `exit code ${code}`}`);
|
|
2622
|
+
resolve2(result);
|
|
2623
|
+
return;
|
|
2624
|
+
}
|
|
2625
|
+
try {
|
|
2626
|
+
const parsed = JSON.parse(stdout);
|
|
2627
|
+
if (!parsed.success) {
|
|
2628
|
+
result.errors.push(`Extraction failed: ${parsed.error}`);
|
|
2629
|
+
resolve2(result);
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2632
|
+
for (const item of parsed.results) {
|
|
2633
|
+
result.schemas.set(item.exportName, {
|
|
2634
|
+
exportName: item.exportName,
|
|
2635
|
+
vendor: item.vendor,
|
|
2636
|
+
outputSchema: item.outputSchema,
|
|
2637
|
+
inputSchema: item.inputSchema
|
|
2638
|
+
});
|
|
2639
|
+
}
|
|
2640
|
+
} catch (e) {
|
|
2641
|
+
result.errors.push(`Failed to parse extraction output: ${e}`);
|
|
2642
|
+
}
|
|
2643
|
+
resolve2(result);
|
|
2644
|
+
});
|
|
2645
|
+
child.on("error", (err) => {
|
|
2646
|
+
result.errors.push(`Subprocess error: ${err.message}`);
|
|
2647
|
+
resolve2(result);
|
|
2648
|
+
});
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2651
|
+
async function extractStandardSchemasFromProject(entryFile, baseDir, options = {}) {
|
|
2652
|
+
const compiledPath = resolveCompiledPath(entryFile, baseDir);
|
|
2653
|
+
if (!compiledPath) {
|
|
2654
|
+
return {
|
|
2655
|
+
schemas: new Map,
|
|
2656
|
+
errors: [`Could not find compiled JS for ${entryFile}. Build the project first.`]
|
|
2657
|
+
};
|
|
2658
|
+
}
|
|
2659
|
+
return extractStandardSchemas(compiledPath, options);
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
// src/analysis/schema-detection.ts
|
|
2663
|
+
async function detectRuntimeSchemas(context) {
|
|
2664
|
+
const { baseDir, entryFile } = context;
|
|
2665
|
+
const compiledPath = resolveCompiledPath(entryFile, baseDir);
|
|
2666
|
+
if (!compiledPath) {
|
|
2667
|
+
return {
|
|
2668
|
+
schemas: new Map,
|
|
2669
|
+
errors: [],
|
|
2670
|
+
noCompiledJsWarning: true
|
|
2671
|
+
};
|
|
2672
|
+
}
|
|
2673
|
+
const extraction = await extractStandardSchemasFromProject(entryFile, baseDir);
|
|
2674
|
+
const schemas = new Map;
|
|
2675
|
+
for (const [name, result] of extraction.schemas) {
|
|
2676
|
+
schemas.set(name, {
|
|
2677
|
+
schema: result.outputSchema,
|
|
2678
|
+
vendor: result.vendor
|
|
2679
|
+
});
|
|
2680
|
+
}
|
|
2681
|
+
return {
|
|
2682
|
+
schemas,
|
|
2683
|
+
errors: extraction.errors
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
2687
|
// src/analysis/history.ts
|
|
2688
2688
|
import * as fs4 from "node:fs";
|
|
2689
2689
|
import * as path4 from "node:path";
|
|
@@ -2911,4 +2911,4 @@ function getExtendedTrend(spec, cwd, options) {
|
|
|
2911
2911
|
};
|
|
2912
2912
|
}
|
|
2913
2913
|
|
|
2914
|
-
export {
|
|
2914
|
+
export { isFixableDrift, generateFix, generateFixesForExport, mergeFixes, categorizeDrifts, ts, parseJSDocToPatch, applyPatchToJSDoc, serializeJSDoc, findJSDocLocation, applyEdits, createSourceFile, isBuiltInTypeName, isBuiltInIdentifier, detectExampleRuntimeErrors, parseAssertions, hasNonAssertionComments, detectExampleAssertionFailures, buildExportRegistry, computeDrift, computeExportDrift, DRIFT_CATEGORIES, DRIFT_CATEGORY_LABELS, DRIFT_CATEGORY_DESCRIPTIONS, categorizeDrift, groupDriftsByCategory, getDriftSummary, formatDriftSummaryLine, calculateAggregateCoverage, ensureSpecCoverage, enrichSpec, generateReport, generateReportFromEnriched, loadCachedReport, saveReport, isCachedReportValid, renderApiSurface, isStandardJSONSchema, resolveCompiledPath, extractStandardSchemas, extractStandardSchemasFromProject, detectRuntimeSchemas, HISTORY_DIR, RETENTION_DAYS, computeSnapshot, saveSnapshot, loadSnapshots, getTrend, renderSparkline, formatDelta, pruneHistory, pruneByTier, loadSnapshotsForDays, generateWeeklySummaries, getExtendedTrend };
|