@doccov/sdk 0.15.1 → 0.19.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/LICENSE +1 -1
- package/dist/index.d.ts +785 -551
- package/dist/index.js +1630 -983
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -15,8 +15,349 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
15
15
|
});
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
18
|
-
var __require = createRequire(import.meta.url);
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
|
+
// src/analysis/schema-detection.ts
|
|
21
|
+
async function detectRuntimeSchemas(_context) {
|
|
22
|
+
return {
|
|
23
|
+
schemas: new Map,
|
|
24
|
+
errors: []
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function clearSchemaCache() {}
|
|
28
|
+
// src/extract/schema/types.ts
|
|
29
|
+
function isTypeReference(type) {
|
|
30
|
+
return !!(type.flags & 524288 && type.objectFlags && type.objectFlags & 4);
|
|
31
|
+
}
|
|
32
|
+
function getNonNullableType(type) {
|
|
33
|
+
if (type.isUnion()) {
|
|
34
|
+
const nonNullable = type.types.filter((t) => !(t.flags & 32768) && !(t.flags & 65536));
|
|
35
|
+
if (nonNullable.length === 1) {
|
|
36
|
+
return nonNullable[0];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return type;
|
|
40
|
+
}
|
|
41
|
+
// src/extract/schema/adapters/zod.ts
|
|
42
|
+
var ZOD_TYPE_PATTERN = /^Zod[A-Z]/;
|
|
43
|
+
var zodAdapter = {
|
|
44
|
+
id: "zod",
|
|
45
|
+
packages: ["zod"],
|
|
46
|
+
matches(type, checker) {
|
|
47
|
+
const typeName = checker.typeToString(type);
|
|
48
|
+
return ZOD_TYPE_PATTERN.test(typeName);
|
|
49
|
+
},
|
|
50
|
+
extractOutputType(type, checker) {
|
|
51
|
+
const outputSymbol = type.getProperty("_output");
|
|
52
|
+
if (outputSymbol) {
|
|
53
|
+
return checker.getTypeOfSymbol(outputSymbol);
|
|
54
|
+
}
|
|
55
|
+
const typeSymbol = type.getProperty("_type");
|
|
56
|
+
if (typeSymbol) {
|
|
57
|
+
return checker.getTypeOfSymbol(typeSymbol);
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
},
|
|
61
|
+
extractInputType(type, checker) {
|
|
62
|
+
const inputSymbol = type.getProperty("_input");
|
|
63
|
+
if (inputSymbol) {
|
|
64
|
+
return checker.getTypeOfSymbol(inputSymbol);
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// src/extract/schema/adapters/valibot.ts
|
|
71
|
+
var VALIBOT_TYPE_PATTERN = /Schema(<|$)/;
|
|
72
|
+
var valibotAdapter = {
|
|
73
|
+
id: "valibot",
|
|
74
|
+
packages: ["valibot"],
|
|
75
|
+
matches(type, checker) {
|
|
76
|
+
const typeName = checker.typeToString(type);
|
|
77
|
+
return VALIBOT_TYPE_PATTERN.test(typeName) && !typeName.includes("Zod");
|
|
78
|
+
},
|
|
79
|
+
extractOutputType(type, checker) {
|
|
80
|
+
const typesSymbol = type.getProperty("~types");
|
|
81
|
+
if (!typesSymbol) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
let typesType = checker.getTypeOfSymbol(typesSymbol);
|
|
85
|
+
typesType = getNonNullableType(typesType);
|
|
86
|
+
const outputSymbol = typesType.getProperty("output");
|
|
87
|
+
if (!outputSymbol) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return checker.getTypeOfSymbol(outputSymbol);
|
|
91
|
+
},
|
|
92
|
+
extractInputType(type, checker) {
|
|
93
|
+
const typesSymbol = type.getProperty("~types");
|
|
94
|
+
if (!typesSymbol) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
let typesType = checker.getTypeOfSymbol(typesSymbol);
|
|
98
|
+
typesType = getNonNullableType(typesType);
|
|
99
|
+
const inputSymbol = typesType.getProperty("input");
|
|
100
|
+
if (!inputSymbol) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return checker.getTypeOfSymbol(inputSymbol);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// src/extract/schema/adapters/typebox.ts
|
|
108
|
+
var TYPEBOX_TYPE_PATTERN = /^T[A-Z]/;
|
|
109
|
+
var typeboxAdapter = {
|
|
110
|
+
id: "typebox",
|
|
111
|
+
packages: ["@sinclair/typebox"],
|
|
112
|
+
matches(type, checker) {
|
|
113
|
+
const typeName = checker.typeToString(type);
|
|
114
|
+
if (!TYPEBOX_TYPE_PATTERN.test(typeName)) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
const typeProperty = type.getProperty("type");
|
|
118
|
+
return typeProperty !== undefined;
|
|
119
|
+
},
|
|
120
|
+
extractOutputType(type, checker) {
|
|
121
|
+
const staticSymbol = type.getProperty("static");
|
|
122
|
+
if (staticSymbol) {
|
|
123
|
+
return checker.getTypeOfSymbol(staticSymbol);
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// src/extract/schema/adapters/arktype.ts
|
|
130
|
+
var ARKTYPE_TYPE_PATTERN = /^Type</;
|
|
131
|
+
var arktypeAdapter = {
|
|
132
|
+
id: "arktype",
|
|
133
|
+
packages: ["arktype"],
|
|
134
|
+
matches(type, checker) {
|
|
135
|
+
const typeName = checker.typeToString(type);
|
|
136
|
+
return ARKTYPE_TYPE_PATTERN.test(typeName);
|
|
137
|
+
},
|
|
138
|
+
extractOutputType(type, checker) {
|
|
139
|
+
if (!isTypeReference(type)) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const args = checker.getTypeArguments(type);
|
|
143
|
+
if (args.length < 1) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
return args[0];
|
|
147
|
+
},
|
|
148
|
+
extractInputType(type, checker) {
|
|
149
|
+
if (!isTypeReference(type)) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const args = checker.getTypeArguments(type);
|
|
153
|
+
if (args.length < 2) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
return args[1];
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// src/extract/schema/registry.ts
|
|
161
|
+
var adapters = [
|
|
162
|
+
zodAdapter,
|
|
163
|
+
arktypeAdapter,
|
|
164
|
+
typeboxAdapter,
|
|
165
|
+
valibotAdapter
|
|
166
|
+
];
|
|
167
|
+
function findAdapter(type, checker) {
|
|
168
|
+
for (const adapter of adapters) {
|
|
169
|
+
if (adapter.matches(type, checker)) {
|
|
170
|
+
return adapter;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
function isSchemaType(type, checker) {
|
|
176
|
+
return findAdapter(type, checker) !== null;
|
|
177
|
+
}
|
|
178
|
+
function extractSchemaOutputType(type, checker) {
|
|
179
|
+
const adapter = findAdapter(type, checker);
|
|
180
|
+
if (!adapter) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
return adapter.extractOutputType(type, checker);
|
|
184
|
+
}
|
|
185
|
+
function extractSchemaType(type, checker) {
|
|
186
|
+
const adapter = findAdapter(type, checker);
|
|
187
|
+
if (!adapter) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
const outputType = adapter.extractOutputType(type, checker);
|
|
191
|
+
if (!outputType) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const result = {
|
|
195
|
+
adapter,
|
|
196
|
+
outputType
|
|
197
|
+
};
|
|
198
|
+
if (adapter.extractInputType) {
|
|
199
|
+
const inputType = adapter.extractInputType(type, checker);
|
|
200
|
+
if (inputType) {
|
|
201
|
+
result.inputType = inputType;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
function getRegisteredAdapters() {
|
|
207
|
+
return adapters;
|
|
208
|
+
}
|
|
209
|
+
function getSupportedLibraries() {
|
|
210
|
+
return adapters.flatMap((a) => a.packages);
|
|
211
|
+
}
|
|
212
|
+
// src/extract/schema/standard-schema.ts
|
|
213
|
+
import { spawn } from "node:child_process";
|
|
214
|
+
import * as fs from "node:fs";
|
|
215
|
+
import * as path from "node:path";
|
|
216
|
+
function isStandardJSONSchema(obj) {
|
|
217
|
+
if (typeof obj !== "object" || obj === null)
|
|
218
|
+
return false;
|
|
219
|
+
const std = obj["~standard"];
|
|
220
|
+
if (typeof std !== "object" || std === null)
|
|
221
|
+
return false;
|
|
222
|
+
const stdObj = std;
|
|
223
|
+
if (typeof stdObj.version !== "number")
|
|
224
|
+
return false;
|
|
225
|
+
if (typeof stdObj.vendor !== "string")
|
|
226
|
+
return false;
|
|
227
|
+
const jsonSchema = stdObj.jsonSchema;
|
|
228
|
+
if (typeof jsonSchema !== "object" || jsonSchema === null)
|
|
229
|
+
return false;
|
|
230
|
+
const jsObj = jsonSchema;
|
|
231
|
+
return typeof jsObj.output === "function";
|
|
232
|
+
}
|
|
233
|
+
var WORKER_SCRIPT = `
|
|
234
|
+
const path = require('path');
|
|
235
|
+
|
|
236
|
+
async function extract() {
|
|
237
|
+
// With node -e, argv is: [node, arg1, arg2, ...]
|
|
238
|
+
// (the -e script is NOT in argv)
|
|
239
|
+
const [modulePath, target] = process.argv.slice(1);
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
// Import the module
|
|
243
|
+
const mod = require(path.resolve(modulePath));
|
|
244
|
+
const results = [];
|
|
245
|
+
|
|
246
|
+
// Check each export
|
|
247
|
+
for (const [name, value] of Object.entries(mod)) {
|
|
248
|
+
if (name.startsWith('_')) continue;
|
|
249
|
+
if (typeof value !== 'object' || value === null) continue;
|
|
250
|
+
|
|
251
|
+
const std = value['~standard'];
|
|
252
|
+
if (!std || typeof std !== 'object') continue;
|
|
253
|
+
if (typeof std.version !== 'number') continue;
|
|
254
|
+
if (typeof std.vendor !== 'string') continue;
|
|
255
|
+
if (!std.jsonSchema || typeof std.jsonSchema.output !== 'function') continue;
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const outputSchema = std.jsonSchema.output(target);
|
|
259
|
+
const inputSchema = std.jsonSchema.input ? std.jsonSchema.input(target) : undefined;
|
|
260
|
+
|
|
261
|
+
results.push({
|
|
262
|
+
exportName: name,
|
|
263
|
+
vendor: std.vendor,
|
|
264
|
+
outputSchema,
|
|
265
|
+
inputSchema
|
|
266
|
+
});
|
|
267
|
+
} catch (e) {
|
|
268
|
+
// Skip schemas that fail to extract
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
console.log(JSON.stringify({ success: true, results }));
|
|
273
|
+
} catch (e) {
|
|
274
|
+
console.log(JSON.stringify({ success: false, error: e.message }));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
extract();
|
|
279
|
+
`;
|
|
280
|
+
function resolveCompiledPath(tsPath, baseDir) {
|
|
281
|
+
const relativePath = path.relative(baseDir, tsPath);
|
|
282
|
+
const withoutExt = relativePath.replace(/\.tsx?$/, "");
|
|
283
|
+
const candidates = [
|
|
284
|
+
path.join(baseDir, `${withoutExt}.js`),
|
|
285
|
+
path.join(baseDir, "dist", `${withoutExt.replace(/^src\//, "")}.js`),
|
|
286
|
+
path.join(baseDir, "build", `${withoutExt.replace(/^src\//, "")}.js`),
|
|
287
|
+
path.join(baseDir, "lib", `${withoutExt.replace(/^src\//, "")}.js`)
|
|
288
|
+
];
|
|
289
|
+
for (const candidate of candidates) {
|
|
290
|
+
if (fs.existsSync(candidate)) {
|
|
291
|
+
return candidate;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
async function extractStandardSchemas(compiledJsPath, options = {}) {
|
|
297
|
+
const { timeout = 1e4, target = "draft-2020-12" } = options;
|
|
298
|
+
const result = {
|
|
299
|
+
schemas: new Map,
|
|
300
|
+
errors: []
|
|
301
|
+
};
|
|
302
|
+
if (!fs.existsSync(compiledJsPath)) {
|
|
303
|
+
result.errors.push(`Compiled JS not found: ${compiledJsPath}`);
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
return new Promise((resolve) => {
|
|
307
|
+
const child = spawn("node", ["-e", WORKER_SCRIPT, compiledJsPath, target], {
|
|
308
|
+
timeout,
|
|
309
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
310
|
+
});
|
|
311
|
+
let stdout = "";
|
|
312
|
+
let stderr = "";
|
|
313
|
+
child.stdout.on("data", (data) => {
|
|
314
|
+
stdout += data.toString();
|
|
315
|
+
});
|
|
316
|
+
child.stderr.on("data", (data) => {
|
|
317
|
+
stderr += data.toString();
|
|
318
|
+
});
|
|
319
|
+
child.on("close", (code) => {
|
|
320
|
+
if (code !== 0) {
|
|
321
|
+
result.errors.push(`Extraction process failed: ${stderr || `exit code ${code}`}`);
|
|
322
|
+
resolve(result);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
const parsed = JSON.parse(stdout);
|
|
327
|
+
if (!parsed.success) {
|
|
328
|
+
result.errors.push(`Extraction failed: ${parsed.error}`);
|
|
329
|
+
resolve(result);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
for (const item of parsed.results) {
|
|
333
|
+
result.schemas.set(item.exportName, {
|
|
334
|
+
exportName: item.exportName,
|
|
335
|
+
vendor: item.vendor,
|
|
336
|
+
outputSchema: item.outputSchema,
|
|
337
|
+
inputSchema: item.inputSchema
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
} catch (e) {
|
|
341
|
+
result.errors.push(`Failed to parse extraction output: ${e}`);
|
|
342
|
+
}
|
|
343
|
+
resolve(result);
|
|
344
|
+
});
|
|
345
|
+
child.on("error", (err) => {
|
|
346
|
+
result.errors.push(`Subprocess error: ${err.message}`);
|
|
347
|
+
resolve(result);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
async function extractStandardSchemasFromProject(entryFile, baseDir, options = {}) {
|
|
352
|
+
const compiledPath = resolveCompiledPath(entryFile, baseDir);
|
|
353
|
+
if (!compiledPath) {
|
|
354
|
+
return {
|
|
355
|
+
schemas: new Map,
|
|
356
|
+
errors: [`Could not find compiled JS for ${entryFile}. Build the project first.`]
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
return extractStandardSchemas(compiledPath, options);
|
|
360
|
+
}
|
|
20
361
|
// src/analysis/docs-coverage.ts
|
|
21
362
|
import {
|
|
22
363
|
DRIFT_CATEGORIES
|
|
@@ -446,8 +787,8 @@ function categorizeDrifts(drifts) {
|
|
|
446
787
|
return { fixable, nonFixable };
|
|
447
788
|
}
|
|
448
789
|
// src/fix/jsdoc-writer.ts
|
|
449
|
-
import * as
|
|
450
|
-
import * as
|
|
790
|
+
import * as fs2 from "node:fs";
|
|
791
|
+
import * as path2 from "node:path";
|
|
451
792
|
|
|
452
793
|
// src/ts-module.ts
|
|
453
794
|
import * as tsNamespace from "typescript";
|
|
@@ -786,7 +1127,7 @@ async function applyEdits(edits) {
|
|
|
786
1127
|
}
|
|
787
1128
|
for (const [filePath, fileEdits] of editsByFile) {
|
|
788
1129
|
try {
|
|
789
|
-
const content =
|
|
1130
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
790
1131
|
const lines = content.split(`
|
|
791
1132
|
`);
|
|
792
1133
|
const sortedEdits = [...fileEdits].sort((a, b) => b.startLine - a.startLine);
|
|
@@ -800,7 +1141,7 @@ async function applyEdits(edits) {
|
|
|
800
1141
|
}
|
|
801
1142
|
result.editsApplied++;
|
|
802
1143
|
}
|
|
803
|
-
|
|
1144
|
+
fs2.writeFileSync(filePath, lines.join(`
|
|
804
1145
|
`));
|
|
805
1146
|
result.filesModified++;
|
|
806
1147
|
} catch (error) {
|
|
@@ -813,8 +1154,8 @@ async function applyEdits(edits) {
|
|
|
813
1154
|
return result;
|
|
814
1155
|
}
|
|
815
1156
|
function createSourceFile(filePath) {
|
|
816
|
-
const content =
|
|
817
|
-
return ts.createSourceFile(
|
|
1157
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
1158
|
+
return ts.createSourceFile(path2.basename(filePath), content, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
818
1159
|
}
|
|
819
1160
|
// src/utils/builtin-detection.ts
|
|
820
1161
|
function isBuiltInTypeName(name) {
|
|
@@ -2012,271 +2353,6 @@ function ensureSpecCoverage(spec) {
|
|
|
2012
2353
|
}
|
|
2013
2354
|
};
|
|
2014
2355
|
}
|
|
2015
|
-
// src/quality/rules.ts
|
|
2016
|
-
var CORE_RULES = [
|
|
2017
|
-
{
|
|
2018
|
-
id: "has-description",
|
|
2019
|
-
name: "Has Description",
|
|
2020
|
-
description: "Export has a description comment",
|
|
2021
|
-
affectsCoverage: true,
|
|
2022
|
-
defaultSeverity: "warn",
|
|
2023
|
-
check(ctx) {
|
|
2024
|
-
return Boolean(ctx.export.description?.trim());
|
|
2025
|
-
},
|
|
2026
|
-
getViolation(ctx) {
|
|
2027
|
-
return {
|
|
2028
|
-
ruleId: "has-description",
|
|
2029
|
-
severity: "warn",
|
|
2030
|
-
message: `Export '${ctx.export.name}' is missing a description`,
|
|
2031
|
-
fixable: false
|
|
2032
|
-
};
|
|
2033
|
-
}
|
|
2034
|
-
},
|
|
2035
|
-
{
|
|
2036
|
-
id: "has-params",
|
|
2037
|
-
name: "Has Parameters",
|
|
2038
|
-
description: "All parameters are documented",
|
|
2039
|
-
appliesTo: ["function"],
|
|
2040
|
-
affectsCoverage: true,
|
|
2041
|
-
defaultSeverity: "off",
|
|
2042
|
-
check(ctx) {
|
|
2043
|
-
const parameters = (ctx.export.signatures ?? []).flatMap((sig) => sig.parameters ?? []);
|
|
2044
|
-
if (parameters.length === 0)
|
|
2045
|
-
return true;
|
|
2046
|
-
return parameters.every((p) => Boolean(p.description?.trim()));
|
|
2047
|
-
},
|
|
2048
|
-
getViolation(ctx) {
|
|
2049
|
-
return {
|
|
2050
|
-
ruleId: "has-params",
|
|
2051
|
-
severity: "warn",
|
|
2052
|
-
message: `Function '${ctx.export.name}' has undocumented parameters`,
|
|
2053
|
-
fixable: true
|
|
2054
|
-
};
|
|
2055
|
-
}
|
|
2056
|
-
},
|
|
2057
|
-
{
|
|
2058
|
-
id: "has-returns",
|
|
2059
|
-
name: "Has Returns",
|
|
2060
|
-
description: "Return value is documented",
|
|
2061
|
-
appliesTo: ["function"],
|
|
2062
|
-
affectsCoverage: true,
|
|
2063
|
-
defaultSeverity: "off",
|
|
2064
|
-
check(ctx) {
|
|
2065
|
-
const signatures = ctx.export.signatures ?? [];
|
|
2066
|
-
if (signatures.length === 0)
|
|
2067
|
-
return true;
|
|
2068
|
-
return signatures.every((sig) => {
|
|
2069
|
-
const text = sig.returns?.description;
|
|
2070
|
-
return Boolean(text?.trim());
|
|
2071
|
-
});
|
|
2072
|
-
},
|
|
2073
|
-
getViolation(ctx) {
|
|
2074
|
-
return {
|
|
2075
|
-
ruleId: "has-returns",
|
|
2076
|
-
severity: "warn",
|
|
2077
|
-
message: `Function '${ctx.export.name}' has undocumented return value`,
|
|
2078
|
-
fixable: true
|
|
2079
|
-
};
|
|
2080
|
-
}
|
|
2081
|
-
},
|
|
2082
|
-
{
|
|
2083
|
-
id: "has-examples",
|
|
2084
|
-
name: "Has Examples",
|
|
2085
|
-
description: "Export has at least one @example",
|
|
2086
|
-
appliesTo: ["function", "class"],
|
|
2087
|
-
affectsCoverage: true,
|
|
2088
|
-
defaultSeverity: "off",
|
|
2089
|
-
check(ctx) {
|
|
2090
|
-
return Boolean(ctx.export.examples?.length);
|
|
2091
|
-
},
|
|
2092
|
-
getViolation(ctx) {
|
|
2093
|
-
return {
|
|
2094
|
-
ruleId: "has-examples",
|
|
2095
|
-
severity: "warn",
|
|
2096
|
-
message: `Export '${ctx.export.name}' is missing an @example`,
|
|
2097
|
-
fixable: false
|
|
2098
|
-
};
|
|
2099
|
-
}
|
|
2100
|
-
}
|
|
2101
|
-
];
|
|
2102
|
-
var STYLE_RULES = [
|
|
2103
|
-
{
|
|
2104
|
-
id: "no-empty-returns",
|
|
2105
|
-
name: "No Empty Returns",
|
|
2106
|
-
description: "@returns tag must have a description",
|
|
2107
|
-
appliesTo: ["function"],
|
|
2108
|
-
affectsCoverage: false,
|
|
2109
|
-
defaultSeverity: "warn",
|
|
2110
|
-
check(ctx) {
|
|
2111
|
-
if (!ctx.rawJSDoc)
|
|
2112
|
-
return true;
|
|
2113
|
-
const returnsMatch = ctx.rawJSDoc.match(/@returns?\s*(?:\{[^}]*\})?\s*$/m);
|
|
2114
|
-
if (returnsMatch)
|
|
2115
|
-
return false;
|
|
2116
|
-
const returnsTypeOnly = ctx.rawJSDoc.match(/@returns?\s+\{[^}]+\}\s*$/m);
|
|
2117
|
-
if (returnsTypeOnly)
|
|
2118
|
-
return false;
|
|
2119
|
-
return true;
|
|
2120
|
-
},
|
|
2121
|
-
getViolation(ctx) {
|
|
2122
|
-
return {
|
|
2123
|
-
ruleId: "no-empty-returns",
|
|
2124
|
-
severity: "warn",
|
|
2125
|
-
message: `Export '${ctx.export.name}' has @returns without a description`,
|
|
2126
|
-
fixable: false
|
|
2127
|
-
};
|
|
2128
|
-
}
|
|
2129
|
-
},
|
|
2130
|
-
{
|
|
2131
|
-
id: "consistent-param-style",
|
|
2132
|
-
name: "Consistent Param Style",
|
|
2133
|
-
description: "@param tags use dash separator",
|
|
2134
|
-
appliesTo: ["function"],
|
|
2135
|
-
affectsCoverage: false,
|
|
2136
|
-
defaultSeverity: "off",
|
|
2137
|
-
check(ctx) {
|
|
2138
|
-
if (!ctx.rawJSDoc)
|
|
2139
|
-
return true;
|
|
2140
|
-
const paramRegex = /@param\s+(?:\{[^}]+\}\s+)?(\S+)\s+([^@\n]+)/g;
|
|
2141
|
-
const matches = ctx.rawJSDoc.matchAll(paramRegex);
|
|
2142
|
-
for (const match of matches) {
|
|
2143
|
-
const rest = match[2]?.trim();
|
|
2144
|
-
if (rest && !rest.startsWith("-") && !rest.startsWith("–")) {
|
|
2145
|
-
return false;
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2148
|
-
return true;
|
|
2149
|
-
},
|
|
2150
|
-
getViolation(ctx) {
|
|
2151
|
-
return {
|
|
2152
|
-
ruleId: "consistent-param-style",
|
|
2153
|
-
severity: "warn",
|
|
2154
|
-
message: `Export '${ctx.export.name}' has @param without dash separator`,
|
|
2155
|
-
fixable: true
|
|
2156
|
-
};
|
|
2157
|
-
},
|
|
2158
|
-
fix(ctx) {
|
|
2159
|
-
if (!ctx.rawJSDoc)
|
|
2160
|
-
return null;
|
|
2161
|
-
const patch = parseJSDocToPatch(ctx.rawJSDoc);
|
|
2162
|
-
if (!patch.params || patch.params.length === 0)
|
|
2163
|
-
return null;
|
|
2164
|
-
return patch;
|
|
2165
|
-
}
|
|
2166
|
-
}
|
|
2167
|
-
];
|
|
2168
|
-
var BUILTIN_RULES = [...CORE_RULES, ...STYLE_RULES];
|
|
2169
|
-
function getCoverageRules() {
|
|
2170
|
-
return BUILTIN_RULES.filter((r) => r.affectsCoverage);
|
|
2171
|
-
}
|
|
2172
|
-
function getRulesForKind(kind) {
|
|
2173
|
-
return BUILTIN_RULES.filter((r) => {
|
|
2174
|
-
if (!r.appliesTo)
|
|
2175
|
-
return true;
|
|
2176
|
-
return r.appliesTo.includes(kind);
|
|
2177
|
-
});
|
|
2178
|
-
}
|
|
2179
|
-
function getRule(id) {
|
|
2180
|
-
return BUILTIN_RULES.find((r) => r.id === id);
|
|
2181
|
-
}
|
|
2182
|
-
function getDefaultConfig() {
|
|
2183
|
-
const config = {};
|
|
2184
|
-
for (const rule of BUILTIN_RULES) {
|
|
2185
|
-
config[rule.id] = rule.defaultSeverity;
|
|
2186
|
-
}
|
|
2187
|
-
return config;
|
|
2188
|
-
}
|
|
2189
|
-
|
|
2190
|
-
// src/quality/engine.ts
|
|
2191
|
-
function evaluateExportQuality(exp, rawJSDoc, config = { rules: {} }) {
|
|
2192
|
-
const kind = exp.kind ?? "variable";
|
|
2193
|
-
const applicableRules = getRulesForKind(kind);
|
|
2194
|
-
const defaults = getDefaultConfig();
|
|
2195
|
-
const getSeverity = (ruleId, defaultSev) => config.rules[ruleId] ?? defaults[ruleId] ?? defaultSev;
|
|
2196
|
-
const activeCoverageRules = applicableRules.filter((r) => {
|
|
2197
|
-
if (!r.affectsCoverage)
|
|
2198
|
-
return false;
|
|
2199
|
-
const severity = getSeverity(r.id, r.defaultSeverity);
|
|
2200
|
-
return severity !== "off";
|
|
2201
|
-
});
|
|
2202
|
-
const result = {
|
|
2203
|
-
coverageScore: 0,
|
|
2204
|
-
coverage: {
|
|
2205
|
-
satisfied: [],
|
|
2206
|
-
missing: [],
|
|
2207
|
-
applicable: activeCoverageRules.map((r) => r.id)
|
|
2208
|
-
},
|
|
2209
|
-
violations: [],
|
|
2210
|
-
summary: {
|
|
2211
|
-
errorCount: 0,
|
|
2212
|
-
warningCount: 0,
|
|
2213
|
-
fixableCount: 0
|
|
2214
|
-
}
|
|
2215
|
-
};
|
|
2216
|
-
const context = { export: exp, rawJSDoc };
|
|
2217
|
-
for (const rule of applicableRules) {
|
|
2218
|
-
const passed = rule.check(context);
|
|
2219
|
-
const severity = getSeverity(rule.id, rule.defaultSeverity);
|
|
2220
|
-
if (rule.affectsCoverage && severity !== "off") {
|
|
2221
|
-
if (passed) {
|
|
2222
|
-
result.coverage.satisfied.push(rule.id);
|
|
2223
|
-
} else {
|
|
2224
|
-
result.coverage.missing.push(rule.id);
|
|
2225
|
-
}
|
|
2226
|
-
}
|
|
2227
|
-
if (!passed && severity !== "off" && rule.getViolation) {
|
|
2228
|
-
const violation = {
|
|
2229
|
-
...rule.getViolation(context),
|
|
2230
|
-
severity: severity === "error" ? "error" : "warn"
|
|
2231
|
-
};
|
|
2232
|
-
result.violations.push(violation);
|
|
2233
|
-
if (violation.severity === "error") {
|
|
2234
|
-
result.summary.errorCount++;
|
|
2235
|
-
} else {
|
|
2236
|
-
result.summary.warningCount++;
|
|
2237
|
-
}
|
|
2238
|
-
if (violation.fixable) {
|
|
2239
|
-
result.summary.fixableCount++;
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2243
|
-
const { satisfied, applicable } = result.coverage;
|
|
2244
|
-
result.coverageScore = applicable.length === 0 ? 100 : Math.round(satisfied.length / applicable.length * 100);
|
|
2245
|
-
return result;
|
|
2246
|
-
}
|
|
2247
|
-
function evaluateQuality(exports, config = { rules: {} }) {
|
|
2248
|
-
const byExport = new Map;
|
|
2249
|
-
let totalCoverage = 0;
|
|
2250
|
-
let totalErrors = 0;
|
|
2251
|
-
let totalWarnings = 0;
|
|
2252
|
-
for (const { export: exp, rawJSDoc } of exports) {
|
|
2253
|
-
const result = evaluateExportQuality(exp, rawJSDoc, config);
|
|
2254
|
-
byExport.set(exp.id ?? exp.name, result);
|
|
2255
|
-
totalCoverage += result.coverageScore;
|
|
2256
|
-
totalErrors += result.summary.errorCount;
|
|
2257
|
-
totalWarnings += result.summary.warningCount;
|
|
2258
|
-
}
|
|
2259
|
-
const count = exports.length;
|
|
2260
|
-
return {
|
|
2261
|
-
byExport,
|
|
2262
|
-
overall: {
|
|
2263
|
-
coverageScore: count === 0 ? 100 : Math.round(totalCoverage / count),
|
|
2264
|
-
totalViolations: totalErrors + totalWarnings,
|
|
2265
|
-
errorCount: totalErrors,
|
|
2266
|
-
warningCount: totalWarnings
|
|
2267
|
-
}
|
|
2268
|
-
};
|
|
2269
|
-
}
|
|
2270
|
-
function mergeConfig(userConfig) {
|
|
2271
|
-
const defaults = getDefaultConfig();
|
|
2272
|
-
return {
|
|
2273
|
-
rules: {
|
|
2274
|
-
...defaults,
|
|
2275
|
-
...userConfig.rules
|
|
2276
|
-
}
|
|
2277
|
-
};
|
|
2278
|
-
}
|
|
2279
|
-
|
|
2280
2356
|
// src/analysis/enrich.ts
|
|
2281
2357
|
function collectAllMissing(exports) {
|
|
2282
2358
|
const allMissing = new Set;
|
|
@@ -2298,38 +2374,33 @@ function collectAllDrift(exports) {
|
|
|
2298
2374
|
}
|
|
2299
2375
|
return allDrift;
|
|
2300
2376
|
}
|
|
2301
|
-
function
|
|
2302
|
-
const
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
}
|
|
2377
|
+
function computeExportCoverage(exp) {
|
|
2378
|
+
const missing = [];
|
|
2379
|
+
if (!exp.description) {
|
|
2380
|
+
missing.push("has-description");
|
|
2381
|
+
return { score: 0, missing };
|
|
2307
2382
|
}
|
|
2308
|
-
return
|
|
2383
|
+
return { score: 100, missing: [] };
|
|
2309
2384
|
}
|
|
2310
2385
|
function enrichSpec(spec, options = {}) {
|
|
2311
|
-
const { driftByExport
|
|
2386
|
+
const { driftByExport } = options;
|
|
2312
2387
|
const exportRegistry = buildExportRegistry(spec);
|
|
2313
2388
|
let totalCoverage = 0;
|
|
2314
2389
|
const enrichedExports = spec.exports.map((exp) => {
|
|
2315
|
-
const
|
|
2316
|
-
const quality = evaluateExportQuality(exp, rawJSDoc, qualityConfig);
|
|
2390
|
+
const coverage = computeExportCoverage(exp);
|
|
2317
2391
|
const drift = computeExportDrift(exp, exportRegistry);
|
|
2318
2392
|
const additionalDrift = driftByExport?.get(exp.id);
|
|
2319
2393
|
const allDrift2 = additionalDrift ? [...drift, ...additionalDrift] : drift;
|
|
2320
|
-
totalCoverage +=
|
|
2394
|
+
totalCoverage += coverage.score;
|
|
2321
2395
|
const docs2 = {
|
|
2322
|
-
coverageScore:
|
|
2396
|
+
coverageScore: coverage.score
|
|
2323
2397
|
};
|
|
2324
|
-
if (
|
|
2325
|
-
docs2.missing =
|
|
2398
|
+
if (coverage.missing.length > 0) {
|
|
2399
|
+
docs2.missing = coverage.missing;
|
|
2326
2400
|
}
|
|
2327
2401
|
if (allDrift2.length > 0) {
|
|
2328
2402
|
docs2.drift = allDrift2;
|
|
2329
2403
|
}
|
|
2330
|
-
if (quality.violations.length > 0) {
|
|
2331
|
-
docs2.violations = quality.violations;
|
|
2332
|
-
}
|
|
2333
2404
|
return {
|
|
2334
2405
|
...exp,
|
|
2335
2406
|
docs: docs2
|
|
@@ -2338,7 +2409,6 @@ function enrichSpec(spec, options = {}) {
|
|
|
2338
2409
|
const count = enrichedExports.length;
|
|
2339
2410
|
const allMissing = collectAllMissing(enrichedExports);
|
|
2340
2411
|
const allDrift = collectAllDrift(enrichedExports);
|
|
2341
|
-
const allViolations = collectAllViolations(enrichedExports);
|
|
2342
2412
|
const docs = {
|
|
2343
2413
|
coverageScore: count === 0 ? 100 : Math.round(totalCoverage / count)
|
|
2344
2414
|
};
|
|
@@ -2348,9 +2418,6 @@ function enrichSpec(spec, options = {}) {
|
|
|
2348
2418
|
if (allDrift.length > 0) {
|
|
2349
2419
|
docs.drift = allDrift;
|
|
2350
2420
|
}
|
|
2351
|
-
if (allViolations.length > 0) {
|
|
2352
|
-
docs.violations = allViolations;
|
|
2353
|
-
}
|
|
2354
2421
|
const driftSummary = allDrift.length > 0 ? getDriftSummary(allDrift) : undefined;
|
|
2355
2422
|
return {
|
|
2356
2423
|
...spec,
|
|
@@ -2360,8 +2427,8 @@ function enrichSpec(spec, options = {}) {
|
|
|
2360
2427
|
};
|
|
2361
2428
|
}
|
|
2362
2429
|
// src/analysis/report.ts
|
|
2363
|
-
import * as
|
|
2364
|
-
import * as
|
|
2430
|
+
import * as fs3 from "node:fs";
|
|
2431
|
+
import * as path3 from "node:path";
|
|
2365
2432
|
|
|
2366
2433
|
// src/types/report.ts
|
|
2367
2434
|
var REPORT_VERSION = "1.0.0";
|
|
@@ -2424,7 +2491,7 @@ function generateReportFromEnriched(enriched) {
|
|
|
2424
2491
|
driftSummary
|
|
2425
2492
|
};
|
|
2426
2493
|
return {
|
|
2427
|
-
$schema: "https://doccov.
|
|
2494
|
+
$schema: "https://doccov.com/schemas/v1.0.0/report.schema.json",
|
|
2428
2495
|
version: REPORT_VERSION,
|
|
2429
2496
|
generatedAt: new Date().toISOString(),
|
|
2430
2497
|
spec: {
|
|
@@ -2437,23 +2504,23 @@ function generateReportFromEnriched(enriched) {
|
|
|
2437
2504
|
}
|
|
2438
2505
|
function loadCachedReport(reportPath = DEFAULT_REPORT_PATH) {
|
|
2439
2506
|
try {
|
|
2440
|
-
const fullPath =
|
|
2441
|
-
if (!
|
|
2507
|
+
const fullPath = path3.resolve(reportPath);
|
|
2508
|
+
if (!fs3.existsSync(fullPath)) {
|
|
2442
2509
|
return null;
|
|
2443
2510
|
}
|
|
2444
|
-
const content =
|
|
2511
|
+
const content = fs3.readFileSync(fullPath, "utf-8");
|
|
2445
2512
|
return JSON.parse(content);
|
|
2446
2513
|
} catch {
|
|
2447
2514
|
return null;
|
|
2448
2515
|
}
|
|
2449
2516
|
}
|
|
2450
2517
|
function saveReport(report, reportPath = DEFAULT_REPORT_PATH) {
|
|
2451
|
-
const fullPath =
|
|
2452
|
-
const dir =
|
|
2453
|
-
if (!
|
|
2454
|
-
|
|
2518
|
+
const fullPath = path3.resolve(reportPath);
|
|
2519
|
+
const dir = path3.dirname(fullPath);
|
|
2520
|
+
if (!fs3.existsSync(dir)) {
|
|
2521
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
2455
2522
|
}
|
|
2456
|
-
|
|
2523
|
+
fs3.writeFileSync(fullPath, JSON.stringify(report, null, 2));
|
|
2457
2524
|
}
|
|
2458
2525
|
function isCachedReportValid(reportPath = DEFAULT_REPORT_PATH, sourceFiles = []) {
|
|
2459
2526
|
const report = loadCachedReport(reportPath);
|
|
@@ -2463,7 +2530,7 @@ function isCachedReportValid(reportPath = DEFAULT_REPORT_PATH, sourceFiles = [])
|
|
|
2463
2530
|
const reportTime = new Date(report.generatedAt).getTime();
|
|
2464
2531
|
for (const file of sourceFiles) {
|
|
2465
2532
|
try {
|
|
2466
|
-
const stat =
|
|
2533
|
+
const stat = fs3.statSync(file);
|
|
2467
2534
|
if (stat.mtimeMs > reportTime) {
|
|
2468
2535
|
return false;
|
|
2469
2536
|
}
|
|
@@ -2473,16 +2540,418 @@ function isCachedReportValid(reportPath = DEFAULT_REPORT_PATH, sourceFiles = [])
|
|
|
2473
2540
|
}
|
|
2474
2541
|
return true;
|
|
2475
2542
|
}
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
}
|
|
2485
|
-
|
|
2543
|
+
function formatSignature(name, signature) {
|
|
2544
|
+
const params = (signature.parameters ?? []).map((p) => {
|
|
2545
|
+
const optional = p.required === false ? "?" : "";
|
|
2546
|
+
const rest = p.rest ? "..." : "";
|
|
2547
|
+
const typeStr = typeof p.schema === "string" ? p.schema : p.schema?.type ?? "unknown";
|
|
2548
|
+
return `${rest}${p.name}${optional}: ${typeStr}`;
|
|
2549
|
+
}).join(", ");
|
|
2550
|
+
const returnType = signature.returns ? typeof signature.returns.schema === "string" ? signature.returns.schema : signature.returns.schema?.type ?? "unknown" : "void";
|
|
2551
|
+
const typeParams = signature.typeParameters?.length ? `<${signature.typeParameters.map((tp) => tp.name).join(", ")}>` : "";
|
|
2552
|
+
return `${name}${typeParams}(${params}): ${returnType}`;
|
|
2553
|
+
}
|
|
2554
|
+
function formatExportToApiSurface(exp) {
|
|
2555
|
+
const lines = [];
|
|
2556
|
+
lines.push(`### ${exp.name}`);
|
|
2557
|
+
switch (exp.kind) {
|
|
2558
|
+
case "function": {
|
|
2559
|
+
const signatures = exp.signatures ?? [];
|
|
2560
|
+
if (signatures.length === 0) {
|
|
2561
|
+
lines.push(`\`\`\`typescript
|
|
2562
|
+
function ${exp.name}(): unknown
|
|
2563
|
+
\`\`\``);
|
|
2564
|
+
} else {
|
|
2565
|
+
for (const sig of signatures) {
|
|
2566
|
+
lines.push(`\`\`\`typescript
|
|
2567
|
+
function ${formatSignature(exp.name, sig)}
|
|
2568
|
+
\`\`\``);
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
break;
|
|
2572
|
+
}
|
|
2573
|
+
case "class": {
|
|
2574
|
+
const extendsClause = exp.extends ? ` extends ${exp.extends}` : "";
|
|
2575
|
+
const implementsClause = exp.implements?.length ? ` implements ${exp.implements.join(", ")}` : "";
|
|
2576
|
+
lines.push(`\`\`\`typescript
|
|
2577
|
+
class ${exp.name}${extendsClause}${implementsClause}
|
|
2578
|
+
\`\`\``);
|
|
2579
|
+
break;
|
|
2580
|
+
}
|
|
2581
|
+
case "interface":
|
|
2582
|
+
case "type": {
|
|
2583
|
+
const typeStr = typeof exp.type === "string" ? exp.type : exp.type?.type ?? "{ ... }";
|
|
2584
|
+
lines.push(`\`\`\`typescript
|
|
2585
|
+
type ${exp.name} = ${typeStr}
|
|
2586
|
+
\`\`\``);
|
|
2587
|
+
break;
|
|
2588
|
+
}
|
|
2589
|
+
case "variable": {
|
|
2590
|
+
const typeStr = typeof exp.type === "string" ? exp.type : exp.type?.type ?? "unknown";
|
|
2591
|
+
lines.push(`\`\`\`typescript
|
|
2592
|
+
const ${exp.name}: ${typeStr}
|
|
2593
|
+
\`\`\``);
|
|
2594
|
+
break;
|
|
2595
|
+
}
|
|
2596
|
+
case "enum": {
|
|
2597
|
+
lines.push(`\`\`\`typescript
|
|
2598
|
+
enum ${exp.name} { ... }
|
|
2599
|
+
\`\`\``);
|
|
2600
|
+
break;
|
|
2601
|
+
}
|
|
2602
|
+
default: {
|
|
2603
|
+
lines.push(`\`\`\`typescript
|
|
2604
|
+
${exp.kind} ${exp.name}
|
|
2605
|
+
\`\`\``);
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
return lines.join(`
|
|
2609
|
+
`);
|
|
2610
|
+
}
|
|
2611
|
+
function formatTypeToApiSurface(type) {
|
|
2612
|
+
const lines = [];
|
|
2613
|
+
lines.push(`### ${type.name}`);
|
|
2614
|
+
switch (type.kind) {
|
|
2615
|
+
case "interface": {
|
|
2616
|
+
const extendsClause = type.extends ? ` extends ${type.extends}` : "";
|
|
2617
|
+
lines.push(`\`\`\`typescript
|
|
2618
|
+
interface ${type.name}${extendsClause} { ... }
|
|
2619
|
+
\`\`\``);
|
|
2620
|
+
break;
|
|
2621
|
+
}
|
|
2622
|
+
case "type": {
|
|
2623
|
+
const typeStr = typeof type.type === "string" ? type.type : type.type?.type ?? "{ ... }";
|
|
2624
|
+
lines.push(`\`\`\`typescript
|
|
2625
|
+
type ${type.name} = ${typeStr}
|
|
2626
|
+
\`\`\``);
|
|
2627
|
+
break;
|
|
2628
|
+
}
|
|
2629
|
+
case "class": {
|
|
2630
|
+
const extendsClause = type.extends ? ` extends ${type.extends}` : "";
|
|
2631
|
+
lines.push(`\`\`\`typescript
|
|
2632
|
+
class ${type.name}${extendsClause}
|
|
2633
|
+
\`\`\``);
|
|
2634
|
+
break;
|
|
2635
|
+
}
|
|
2636
|
+
case "enum": {
|
|
2637
|
+
lines.push(`\`\`\`typescript
|
|
2638
|
+
enum ${type.name} { ... }
|
|
2639
|
+
\`\`\``);
|
|
2640
|
+
break;
|
|
2641
|
+
}
|
|
2642
|
+
default: {
|
|
2643
|
+
lines.push(`\`\`\`typescript
|
|
2644
|
+
${type.kind} ${type.name}
|
|
2645
|
+
\`\`\``);
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
return lines.join(`
|
|
2649
|
+
`);
|
|
2650
|
+
}
|
|
2651
|
+
function renderApiSurface(spec) {
|
|
2652
|
+
const lines = [];
|
|
2653
|
+
const version = spec.meta.version ? ` v${spec.meta.version}` : "";
|
|
2654
|
+
lines.push(`# API Surface: ${spec.meta.name}${version}`);
|
|
2655
|
+
lines.push("");
|
|
2656
|
+
lines.push("> This file is auto-generated. Do not edit manually.");
|
|
2657
|
+
lines.push("> Run `doccov spec --format api-surface` to regenerate.");
|
|
2658
|
+
lines.push("");
|
|
2659
|
+
const exportsByKind = {};
|
|
2660
|
+
for (const exp of spec.exports) {
|
|
2661
|
+
const kind = exp.kind;
|
|
2662
|
+
if (!exportsByKind[kind]) {
|
|
2663
|
+
exportsByKind[kind] = [];
|
|
2664
|
+
}
|
|
2665
|
+
exportsByKind[kind].push(exp);
|
|
2666
|
+
}
|
|
2667
|
+
for (const kind of Object.keys(exportsByKind)) {
|
|
2668
|
+
exportsByKind[kind].sort((a, b) => a.name.localeCompare(b.name));
|
|
2669
|
+
}
|
|
2670
|
+
const kindOrder = [
|
|
2671
|
+
"function",
|
|
2672
|
+
"class",
|
|
2673
|
+
"interface",
|
|
2674
|
+
"type",
|
|
2675
|
+
"variable",
|
|
2676
|
+
"enum",
|
|
2677
|
+
"namespace",
|
|
2678
|
+
"module"
|
|
2679
|
+
];
|
|
2680
|
+
for (const kind of kindOrder) {
|
|
2681
|
+
const exports = exportsByKind[kind];
|
|
2682
|
+
if (!exports || exports.length === 0)
|
|
2683
|
+
continue;
|
|
2684
|
+
const kindTitle = kind.charAt(0).toUpperCase() + kind.slice(1) + "s";
|
|
2685
|
+
lines.push(`## ${kindTitle}`);
|
|
2686
|
+
lines.push("");
|
|
2687
|
+
for (const exp of exports) {
|
|
2688
|
+
lines.push(formatExportToApiSurface(exp));
|
|
2689
|
+
lines.push("");
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
for (const kind of Object.keys(exportsByKind).sort()) {
|
|
2693
|
+
if (kindOrder.includes(kind))
|
|
2694
|
+
continue;
|
|
2695
|
+
const exports = exportsByKind[kind];
|
|
2696
|
+
if (!exports || exports.length === 0)
|
|
2697
|
+
continue;
|
|
2698
|
+
const kindTitle = kind.charAt(0).toUpperCase() + kind.slice(1) + "s";
|
|
2699
|
+
lines.push(`## ${kindTitle}`);
|
|
2700
|
+
lines.push("");
|
|
2701
|
+
for (const exp of exports) {
|
|
2702
|
+
lines.push(formatExportToApiSurface(exp));
|
|
2703
|
+
lines.push("");
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
const types = spec.types ?? [];
|
|
2707
|
+
if (types.length > 0) {
|
|
2708
|
+
const sortedTypes = [...types].sort((a, b) => a.name.localeCompare(b.name));
|
|
2709
|
+
lines.push("## Internal Types");
|
|
2710
|
+
lines.push("");
|
|
2711
|
+
for (const type of sortedTypes) {
|
|
2712
|
+
lines.push(formatTypeToApiSurface(type));
|
|
2713
|
+
lines.push("");
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
return lines.join(`
|
|
2717
|
+
`);
|
|
2718
|
+
}
|
|
2719
|
+
// src/analysis/history.ts
|
|
2720
|
+
import * as fs4 from "node:fs";
|
|
2721
|
+
import * as path4 from "node:path";
|
|
2722
|
+
var HISTORY_DIR = ".doccov/history";
|
|
2723
|
+
var RETENTION_DAYS = {
|
|
2724
|
+
free: 7,
|
|
2725
|
+
team: 30,
|
|
2726
|
+
pro: 90
|
|
2727
|
+
};
|
|
2728
|
+
function getSnapshotFilename(timestamp) {
|
|
2729
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
2730
|
+
const year = timestamp.getFullYear();
|
|
2731
|
+
const month = pad(timestamp.getMonth() + 1);
|
|
2732
|
+
const day = pad(timestamp.getDate());
|
|
2733
|
+
const hours = pad(timestamp.getHours());
|
|
2734
|
+
const minutes = pad(timestamp.getMinutes());
|
|
2735
|
+
const seconds = pad(timestamp.getSeconds());
|
|
2736
|
+
return `${year}-${month}-${day}-${hours}${minutes}${seconds}.json`;
|
|
2737
|
+
}
|
|
2738
|
+
function computeSnapshot(spec, options) {
|
|
2739
|
+
const exports = spec.exports ?? [];
|
|
2740
|
+
const documented = exports.filter((e) => e.description && e.description.trim().length > 0);
|
|
2741
|
+
const driftCount = exports.reduce((sum, e) => {
|
|
2742
|
+
const docs = e.docs;
|
|
2743
|
+
return sum + (docs?.drift?.length ?? 0);
|
|
2744
|
+
}, 0);
|
|
2745
|
+
const coverageScore = exports.length > 0 ? Math.round(documented.length / exports.length * 100) : 100;
|
|
2746
|
+
return {
|
|
2747
|
+
timestamp: new Date().toISOString(),
|
|
2748
|
+
package: spec.meta.name,
|
|
2749
|
+
version: spec.meta.version,
|
|
2750
|
+
coverageScore,
|
|
2751
|
+
totalExports: exports.length,
|
|
2752
|
+
documentedExports: documented.length,
|
|
2753
|
+
driftCount,
|
|
2754
|
+
commit: options?.commit,
|
|
2755
|
+
branch: options?.branch
|
|
2756
|
+
};
|
|
2757
|
+
}
|
|
2758
|
+
function saveSnapshot(snapshot, cwd) {
|
|
2759
|
+
const historyDir = path4.resolve(cwd, HISTORY_DIR);
|
|
2760
|
+
if (!fs4.existsSync(historyDir)) {
|
|
2761
|
+
fs4.mkdirSync(historyDir, { recursive: true });
|
|
2762
|
+
}
|
|
2763
|
+
const filename = getSnapshotFilename(new Date(snapshot.timestamp));
|
|
2764
|
+
const filepath = path4.join(historyDir, filename);
|
|
2765
|
+
fs4.writeFileSync(filepath, JSON.stringify(snapshot, null, 2));
|
|
2766
|
+
}
|
|
2767
|
+
function loadSnapshots(cwd) {
|
|
2768
|
+
const historyDir = path4.resolve(cwd, HISTORY_DIR);
|
|
2769
|
+
if (!fs4.existsSync(historyDir)) {
|
|
2770
|
+
return [];
|
|
2771
|
+
}
|
|
2772
|
+
const files = fs4.readdirSync(historyDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
2773
|
+
const snapshots = [];
|
|
2774
|
+
for (const file of files) {
|
|
2775
|
+
try {
|
|
2776
|
+
const content = fs4.readFileSync(path4.join(historyDir, file), "utf-8");
|
|
2777
|
+
snapshots.push(JSON.parse(content));
|
|
2778
|
+
} catch {}
|
|
2779
|
+
}
|
|
2780
|
+
return snapshots;
|
|
2781
|
+
}
|
|
2782
|
+
function getTrend(spec, cwd, options) {
|
|
2783
|
+
const current = computeSnapshot(spec, options);
|
|
2784
|
+
const history = loadSnapshots(cwd);
|
|
2785
|
+
const delta = history.length > 0 ? current.coverageScore - history[0].coverageScore : undefined;
|
|
2786
|
+
const sparklineHistory = history.slice(0, 9).map((s) => s.coverageScore);
|
|
2787
|
+
const sparkline = [current.coverageScore, ...sparklineHistory].reverse();
|
|
2788
|
+
return {
|
|
2789
|
+
current,
|
|
2790
|
+
history,
|
|
2791
|
+
delta,
|
|
2792
|
+
sparkline
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
function renderSparkline(values) {
|
|
2796
|
+
if (values.length === 0)
|
|
2797
|
+
return "";
|
|
2798
|
+
const chars = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
2799
|
+
const min = Math.min(...values);
|
|
2800
|
+
const max = Math.max(...values);
|
|
2801
|
+
const range = max - min || 1;
|
|
2802
|
+
return values.map((v) => {
|
|
2803
|
+
const normalized = (v - min) / range;
|
|
2804
|
+
const index = Math.min(Math.floor(normalized * chars.length), chars.length - 1);
|
|
2805
|
+
return chars[index];
|
|
2806
|
+
}).join("");
|
|
2807
|
+
}
|
|
2808
|
+
function formatDelta(delta) {
|
|
2809
|
+
if (delta > 0)
|
|
2810
|
+
return `↑${delta}%`;
|
|
2811
|
+
if (delta < 0)
|
|
2812
|
+
return `↓${Math.abs(delta)}%`;
|
|
2813
|
+
return "→0%";
|
|
2814
|
+
}
|
|
2815
|
+
function pruneHistory(cwd, keepCount = 100) {
|
|
2816
|
+
const historyDir = path4.resolve(cwd, HISTORY_DIR);
|
|
2817
|
+
if (!fs4.existsSync(historyDir)) {
|
|
2818
|
+
return 0;
|
|
2819
|
+
}
|
|
2820
|
+
const files = fs4.readdirSync(historyDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
2821
|
+
const toDelete = files.slice(keepCount);
|
|
2822
|
+
for (const file of toDelete) {
|
|
2823
|
+
try {
|
|
2824
|
+
fs4.unlinkSync(path4.join(historyDir, file));
|
|
2825
|
+
} catch {}
|
|
2826
|
+
}
|
|
2827
|
+
return toDelete.length;
|
|
2828
|
+
}
|
|
2829
|
+
function pruneByTier(cwd, tier) {
|
|
2830
|
+
const retentionDays = RETENTION_DAYS[tier];
|
|
2831
|
+
const cutoffDate = new Date;
|
|
2832
|
+
cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
|
|
2833
|
+
const historyDir = path4.resolve(cwd, HISTORY_DIR);
|
|
2834
|
+
if (!fs4.existsSync(historyDir)) {
|
|
2835
|
+
return 0;
|
|
2836
|
+
}
|
|
2837
|
+
const files = fs4.readdirSync(historyDir).filter((f) => f.endsWith(".json"));
|
|
2838
|
+
let deleted = 0;
|
|
2839
|
+
for (const file of files) {
|
|
2840
|
+
try {
|
|
2841
|
+
const filepath = path4.join(historyDir, file);
|
|
2842
|
+
const content = fs4.readFileSync(filepath, "utf-8");
|
|
2843
|
+
const snapshot = JSON.parse(content);
|
|
2844
|
+
const snapshotDate = new Date(snapshot.timestamp);
|
|
2845
|
+
if (snapshotDate < cutoffDate) {
|
|
2846
|
+
fs4.unlinkSync(filepath);
|
|
2847
|
+
deleted++;
|
|
2848
|
+
}
|
|
2849
|
+
} catch {}
|
|
2850
|
+
}
|
|
2851
|
+
return deleted;
|
|
2852
|
+
}
|
|
2853
|
+
function loadSnapshotsForDays(cwd, days) {
|
|
2854
|
+
const cutoffDate = new Date;
|
|
2855
|
+
cutoffDate.setDate(cutoffDate.getDate() - days);
|
|
2856
|
+
const allSnapshots = loadSnapshots(cwd);
|
|
2857
|
+
return allSnapshots.filter((s) => new Date(s.timestamp) >= cutoffDate);
|
|
2858
|
+
}
|
|
2859
|
+
function calculateVelocity(snapshots) {
|
|
2860
|
+
if (snapshots.length < 2)
|
|
2861
|
+
return 0;
|
|
2862
|
+
const newest = snapshots[0];
|
|
2863
|
+
const oldest = snapshots[snapshots.length - 1];
|
|
2864
|
+
const newestDate = new Date(newest.timestamp);
|
|
2865
|
+
const oldestDate = new Date(oldest.timestamp);
|
|
2866
|
+
const daysDiff = (newestDate.getTime() - oldestDate.getTime()) / (1000 * 60 * 60 * 24);
|
|
2867
|
+
if (daysDiff < 1)
|
|
2868
|
+
return 0;
|
|
2869
|
+
const coverageDiff = newest.coverageScore - oldest.coverageScore;
|
|
2870
|
+
return Math.round(coverageDiff / daysDiff * 100) / 100;
|
|
2871
|
+
}
|
|
2872
|
+
function getWeekStart(date) {
|
|
2873
|
+
const result = new Date(date);
|
|
2874
|
+
result.setDate(result.getDate() - result.getDay());
|
|
2875
|
+
result.setHours(0, 0, 0, 0);
|
|
2876
|
+
return result;
|
|
2877
|
+
}
|
|
2878
|
+
function generateWeeklySummaries(snapshots) {
|
|
2879
|
+
if (snapshots.length === 0)
|
|
2880
|
+
return [];
|
|
2881
|
+
const weeklyGroups = new Map;
|
|
2882
|
+
for (const snapshot of snapshots) {
|
|
2883
|
+
const weekStart = getWeekStart(new Date(snapshot.timestamp));
|
|
2884
|
+
const weekKey = weekStart.toISOString().split("T")[0];
|
|
2885
|
+
const group = weeklyGroups.get(weekKey) ?? [];
|
|
2886
|
+
group.push(snapshot);
|
|
2887
|
+
weeklyGroups.set(weekKey, group);
|
|
2888
|
+
}
|
|
2889
|
+
const summaries = [];
|
|
2890
|
+
for (const [weekKey, weekSnapshots] of weeklyGroups) {
|
|
2891
|
+
const sorted = [...weekSnapshots].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
2892
|
+
const weekStart = new Date(weekKey);
|
|
2893
|
+
const weekEnd = new Date(weekStart);
|
|
2894
|
+
weekEnd.setDate(weekEnd.getDate() + 6);
|
|
2895
|
+
const scores = sorted.map((s) => s.coverageScore);
|
|
2896
|
+
const avgCoverage = Math.round(scores.reduce((a, b) => a + b, 0) / scores.length);
|
|
2897
|
+
const startCoverage = sorted[0].coverageScore;
|
|
2898
|
+
const endCoverage = sorted[sorted.length - 1].coverageScore;
|
|
2899
|
+
summaries.push({
|
|
2900
|
+
weekStart: weekStart.toISOString(),
|
|
2901
|
+
weekEnd: weekEnd.toISOString(),
|
|
2902
|
+
avgCoverage,
|
|
2903
|
+
startCoverage,
|
|
2904
|
+
endCoverage,
|
|
2905
|
+
delta: endCoverage - startCoverage,
|
|
2906
|
+
snapshotCount: sorted.length
|
|
2907
|
+
});
|
|
2908
|
+
}
|
|
2909
|
+
return summaries.sort((a, b) => new Date(b.weekStart).getTime() - new Date(a.weekStart).getTime());
|
|
2910
|
+
}
|
|
2911
|
+
function getExtendedTrend(spec, cwd, options) {
|
|
2912
|
+
const tier = options?.tier ?? "pro";
|
|
2913
|
+
const retentionDays = RETENTION_DAYS[tier];
|
|
2914
|
+
const trend = getTrend(spec, cwd, options);
|
|
2915
|
+
const periodSnapshots = loadSnapshotsForDays(cwd, retentionDays);
|
|
2916
|
+
const snapshots7d = loadSnapshotsForDays(cwd, 7);
|
|
2917
|
+
const snapshots30d = loadSnapshotsForDays(cwd, 30);
|
|
2918
|
+
const snapshots90d = tier === "pro" ? loadSnapshotsForDays(cwd, 90) : [];
|
|
2919
|
+
const velocity7d = calculateVelocity(snapshots7d);
|
|
2920
|
+
const velocity30d = calculateVelocity(snapshots30d);
|
|
2921
|
+
const velocity90d = tier === "pro" ? calculateVelocity(snapshots90d) : undefined;
|
|
2922
|
+
const currentScore = trend.current.coverageScore;
|
|
2923
|
+
const projected30d = Math.min(100, Math.max(0, Math.round(currentScore + velocity30d * 30)));
|
|
2924
|
+
const allScores = [trend.current.coverageScore, ...trend.history.map((s) => s.coverageScore)];
|
|
2925
|
+
const allTimeHigh = Math.max(...allScores);
|
|
2926
|
+
const allTimeLow = Math.min(...allScores);
|
|
2927
|
+
const dataRange = trend.history.length > 0 ? {
|
|
2928
|
+
start: trend.history[trend.history.length - 1].timestamp,
|
|
2929
|
+
end: trend.current.timestamp
|
|
2930
|
+
} : null;
|
|
2931
|
+
const allSnapshots = [trend.current, ...periodSnapshots];
|
|
2932
|
+
const weeklySummaries = generateWeeklySummaries(allSnapshots);
|
|
2933
|
+
return {
|
|
2934
|
+
trend,
|
|
2935
|
+
weeklySummaries,
|
|
2936
|
+
velocity7d,
|
|
2937
|
+
velocity30d,
|
|
2938
|
+
velocity90d,
|
|
2939
|
+
projected30d,
|
|
2940
|
+
allTimeHigh,
|
|
2941
|
+
allTimeLow,
|
|
2942
|
+
dataRange
|
|
2943
|
+
};
|
|
2944
|
+
}
|
|
2945
|
+
// src/cache/hash.ts
|
|
2946
|
+
import * as crypto from "node:crypto";
|
|
2947
|
+
import * as fs5 from "node:fs";
|
|
2948
|
+
import * as path5 from "node:path";
|
|
2949
|
+
function hashFile(filePath) {
|
|
2950
|
+
try {
|
|
2951
|
+
const content = fs5.readFileSync(filePath);
|
|
2952
|
+
return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
2953
|
+
} catch {
|
|
2954
|
+
return null;
|
|
2486
2955
|
}
|
|
2487
2956
|
}
|
|
2488
2957
|
function hashString(content) {
|
|
@@ -2493,7 +2962,7 @@ function hashFiles(filePaths, cwd) {
|
|
|
2493
2962
|
for (const filePath of filePaths) {
|
|
2494
2963
|
const hash = hashFile(filePath);
|
|
2495
2964
|
if (hash) {
|
|
2496
|
-
const relativePath =
|
|
2965
|
+
const relativePath = path5.relative(cwd, filePath);
|
|
2497
2966
|
hashes[relativePath] = hash;
|
|
2498
2967
|
}
|
|
2499
2968
|
}
|
|
@@ -2514,17 +2983,17 @@ function diffHashes(cached, current) {
|
|
|
2514
2983
|
return changed;
|
|
2515
2984
|
}
|
|
2516
2985
|
// src/cache/spec-cache.ts
|
|
2517
|
-
import * as
|
|
2518
|
-
import * as
|
|
2986
|
+
import * as fs6 from "node:fs";
|
|
2987
|
+
import * as path6 from "node:path";
|
|
2519
2988
|
var CACHE_VERSION = "1.0.0";
|
|
2520
2989
|
var SPEC_CACHE_FILE = ".doccov/spec.cache.json";
|
|
2521
2990
|
function loadSpecCache(cwd) {
|
|
2522
2991
|
try {
|
|
2523
|
-
const cachePath =
|
|
2524
|
-
if (!
|
|
2992
|
+
const cachePath = path6.resolve(cwd, SPEC_CACHE_FILE);
|
|
2993
|
+
if (!fs6.existsSync(cachePath)) {
|
|
2525
2994
|
return null;
|
|
2526
2995
|
}
|
|
2527
|
-
const content =
|
|
2996
|
+
const content = fs6.readFileSync(cachePath, "utf-8");
|
|
2528
2997
|
return JSON.parse(content);
|
|
2529
2998
|
} catch {
|
|
2530
2999
|
return null;
|
|
@@ -2536,7 +3005,7 @@ function saveSpecCache(spec, context) {
|
|
|
2536
3005
|
cacheVersion: CACHE_VERSION,
|
|
2537
3006
|
generatedAt: new Date().toISOString(),
|
|
2538
3007
|
specVersion: spec.openpkg,
|
|
2539
|
-
entryFile:
|
|
3008
|
+
entryFile: path6.relative(cwd, entryFile),
|
|
2540
3009
|
hashes: {
|
|
2541
3010
|
tsconfig: tsconfigPath ? hashFile(tsconfigPath) : null,
|
|
2542
3011
|
packageJson: hashFile(packageJsonPath) ?? "",
|
|
@@ -2545,19 +3014,19 @@ function saveSpecCache(spec, context) {
|
|
|
2545
3014
|
config,
|
|
2546
3015
|
spec
|
|
2547
3016
|
};
|
|
2548
|
-
const cachePath =
|
|
2549
|
-
const dir =
|
|
2550
|
-
if (!
|
|
2551
|
-
|
|
3017
|
+
const cachePath = path6.resolve(cwd, SPEC_CACHE_FILE);
|
|
3018
|
+
const dir = path6.dirname(cachePath);
|
|
3019
|
+
if (!fs6.existsSync(dir)) {
|
|
3020
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
2552
3021
|
}
|
|
2553
|
-
|
|
3022
|
+
fs6.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
|
|
2554
3023
|
}
|
|
2555
3024
|
function validateSpecCache(cache, context) {
|
|
2556
3025
|
const { entryFile, sourceFiles, tsconfigPath, packageJsonPath, config, cwd } = context;
|
|
2557
3026
|
if (cache.cacheVersion !== CACHE_VERSION) {
|
|
2558
3027
|
return { valid: false, reason: "cache-version-mismatch" };
|
|
2559
3028
|
}
|
|
2560
|
-
const relativeEntry =
|
|
3029
|
+
const relativeEntry = path6.relative(cwd, entryFile);
|
|
2561
3030
|
if (cache.entryFile !== relativeEntry) {
|
|
2562
3031
|
return { valid: false, reason: "entry-file-changed" };
|
|
2563
3032
|
}
|
|
@@ -2580,34 +3049,34 @@ function validateSpecCache(cache, context) {
|
|
|
2580
3049
|
return { valid: true };
|
|
2581
3050
|
}
|
|
2582
3051
|
function clearSpecCache(cwd) {
|
|
2583
|
-
const cachePath =
|
|
2584
|
-
if (
|
|
2585
|
-
|
|
3052
|
+
const cachePath = path6.resolve(cwd, SPEC_CACHE_FILE);
|
|
3053
|
+
if (fs6.existsSync(cachePath)) {
|
|
3054
|
+
fs6.unlinkSync(cachePath);
|
|
2586
3055
|
return true;
|
|
2587
3056
|
}
|
|
2588
3057
|
return false;
|
|
2589
3058
|
}
|
|
2590
3059
|
function getSpecCachePath(cwd) {
|
|
2591
|
-
return
|
|
3060
|
+
return path6.resolve(cwd, SPEC_CACHE_FILE);
|
|
2592
3061
|
}
|
|
2593
3062
|
// src/config/types.ts
|
|
2594
3063
|
function defineConfig(config) {
|
|
2595
3064
|
return config;
|
|
2596
3065
|
}
|
|
2597
3066
|
// src/detect/utils.ts
|
|
2598
|
-
async function safeParseJson(
|
|
3067
|
+
async function safeParseJson(fs7, path7) {
|
|
2599
3068
|
try {
|
|
2600
|
-
if (!await
|
|
3069
|
+
if (!await fs7.exists(path7))
|
|
2601
3070
|
return null;
|
|
2602
|
-
const content = await
|
|
3071
|
+
const content = await fs7.readFile(path7);
|
|
2603
3072
|
return JSON.parse(content);
|
|
2604
3073
|
} catch {
|
|
2605
3074
|
return null;
|
|
2606
3075
|
}
|
|
2607
3076
|
}
|
|
2608
|
-
async function readPackageJson(
|
|
2609
|
-
const
|
|
2610
|
-
return safeParseJson(
|
|
3077
|
+
async function readPackageJson(fs7, dir) {
|
|
3078
|
+
const path7 = dir === "." ? "package.json" : `${dir}/package.json`;
|
|
3079
|
+
return safeParseJson(fs7, path7);
|
|
2611
3080
|
}
|
|
2612
3081
|
|
|
2613
3082
|
// src/detect/build.ts
|
|
@@ -2637,8 +3106,8 @@ var BUILD_TOOL_PATTERNS = [
|
|
|
2637
3106
|
"babel",
|
|
2638
3107
|
"ncc build"
|
|
2639
3108
|
];
|
|
2640
|
-
async function detectBuildInfo(
|
|
2641
|
-
const pkgJson = await readPackageJson(
|
|
3109
|
+
async function detectBuildInfo(fs7, packagePath = ".") {
|
|
3110
|
+
const pkgJson = await readPackageJson(fs7, packagePath);
|
|
2642
3111
|
const scripts = pkgJson?.scripts ?? {};
|
|
2643
3112
|
const scriptNames = Object.keys(scripts);
|
|
2644
3113
|
const buildScriptsByName = scriptNames.filter((name) => BUILD_SCRIPT_NAMES.has(name) || BUILD_SCRIPT_PREFIXES.some((prefix) => name.startsWith(prefix)));
|
|
@@ -2650,10 +3119,10 @@ async function detectBuildInfo(fs5, packagePath = ".") {
|
|
|
2650
3119
|
});
|
|
2651
3120
|
const buildScripts = [...new Set([...buildScriptsByName, ...buildScriptsByContent])];
|
|
2652
3121
|
const tsconfigPath = packagePath === "." ? "tsconfig.json" : `${packagePath}/tsconfig.json`;
|
|
2653
|
-
const hasTsConfig = await
|
|
3122
|
+
const hasTsConfig = await fs7.exists(tsconfigPath);
|
|
2654
3123
|
const hasTsDep = pkgJson?.devDependencies?.typescript !== undefined || pkgJson?.dependencies?.typescript !== undefined;
|
|
2655
3124
|
const hasTypeScript = hasTsConfig || hasTsDep;
|
|
2656
|
-
const wasm = await detectWasmProject(
|
|
3125
|
+
const wasm = await detectWasmProject(fs7, packagePath, pkgJson);
|
|
2657
3126
|
const napi = detectNapiProject(pkgJson);
|
|
2658
3127
|
return {
|
|
2659
3128
|
scripts: buildScripts,
|
|
@@ -2670,11 +3139,11 @@ var WASM_PACKAGES = new Set([
|
|
|
2670
3139
|
"wasm-bindgen",
|
|
2671
3140
|
"@aspect/aspect-cli"
|
|
2672
3141
|
]);
|
|
2673
|
-
async function detectWasmProject(
|
|
3142
|
+
async function detectWasmProject(fs7, packagePath, pkgJson) {
|
|
2674
3143
|
const pkgCargoPath = packagePath === "." ? "Cargo.toml" : `${packagePath}/Cargo.toml`;
|
|
2675
|
-
if (await
|
|
3144
|
+
if (await fs7.exists(pkgCargoPath))
|
|
2676
3145
|
return true;
|
|
2677
|
-
if (packagePath !== "." && await
|
|
3146
|
+
if (packagePath !== "." && await fs7.exists("Cargo.toml"))
|
|
2678
3147
|
return true;
|
|
2679
3148
|
if (pkgJson) {
|
|
2680
3149
|
const deps = Object.keys({
|
|
@@ -2715,15 +3184,15 @@ function getPrimaryBuildScript(buildInfo) {
|
|
|
2715
3184
|
return buildInfo.scripts[0] ?? null;
|
|
2716
3185
|
}
|
|
2717
3186
|
// src/detect/entry-point.ts
|
|
2718
|
-
async function detectEntryPoint(
|
|
2719
|
-
const pkgJson = await readPackageJson(
|
|
3187
|
+
async function detectEntryPoint(fs7, packagePath = ".") {
|
|
3188
|
+
const pkgJson = await readPackageJson(fs7, packagePath);
|
|
2720
3189
|
if (!pkgJson) {
|
|
2721
3190
|
throw new Error("No package.json found - not a valid npm package");
|
|
2722
3191
|
}
|
|
2723
|
-
const tsConfig = await parseTsConfig(
|
|
3192
|
+
const tsConfig = await parseTsConfig(fs7, packagePath);
|
|
2724
3193
|
const typesField = pkgJson.types || pkgJson.typings;
|
|
2725
3194
|
if (typesField && typeof typesField === "string") {
|
|
2726
|
-
const resolved = await resolveToSource(
|
|
3195
|
+
const resolved = await resolveToSource(fs7, packagePath, typesField, tsConfig);
|
|
2727
3196
|
if (resolved) {
|
|
2728
3197
|
return { ...resolved, source: "types" };
|
|
2729
3198
|
}
|
|
@@ -2733,7 +3202,7 @@ async function detectEntryPoint(fs5, packagePath = ".") {
|
|
|
2733
3202
|
if (dotExport && typeof dotExport === "object" && "types" in dotExport) {
|
|
2734
3203
|
const typesPath = dotExport.types;
|
|
2735
3204
|
if (typesPath && typeof typesPath === "string") {
|
|
2736
|
-
const resolved = await resolveToSource(
|
|
3205
|
+
const resolved = await resolveToSource(fs7, packagePath, typesPath, tsConfig);
|
|
2737
3206
|
if (resolved) {
|
|
2738
3207
|
return { ...resolved, source: "exports" };
|
|
2739
3208
|
}
|
|
@@ -2741,13 +3210,13 @@ async function detectEntryPoint(fs5, packagePath = ".") {
|
|
|
2741
3210
|
}
|
|
2742
3211
|
}
|
|
2743
3212
|
if (pkgJson.main && typeof pkgJson.main === "string") {
|
|
2744
|
-
const resolved = await resolveToSource(
|
|
3213
|
+
const resolved = await resolveToSource(fs7, packagePath, pkgJson.main, tsConfig);
|
|
2745
3214
|
if (resolved) {
|
|
2746
3215
|
return { ...resolved, source: "main" };
|
|
2747
3216
|
}
|
|
2748
3217
|
}
|
|
2749
3218
|
if (pkgJson.module && typeof pkgJson.module === "string") {
|
|
2750
|
-
const resolved = await resolveToSource(
|
|
3219
|
+
const resolved = await resolveToSource(fs7, packagePath, pkgJson.module, tsConfig);
|
|
2751
3220
|
if (resolved) {
|
|
2752
3221
|
return { ...resolved, source: "module" };
|
|
2753
3222
|
}
|
|
@@ -2765,27 +3234,27 @@ async function detectEntryPoint(fs5, packagePath = ".") {
|
|
|
2765
3234
|
];
|
|
2766
3235
|
for (const fallback of fallbacks) {
|
|
2767
3236
|
const checkPath = packagePath === "." ? fallback : `${packagePath}/${fallback}`;
|
|
2768
|
-
if (await
|
|
3237
|
+
if (await fs7.exists(checkPath)) {
|
|
2769
3238
|
return { path: fallback, source: "fallback", isDeclarationOnly: false };
|
|
2770
3239
|
}
|
|
2771
3240
|
}
|
|
2772
3241
|
throw new Error("Could not detect TypeScript entry point. No types field in package.json and no common entry paths found.");
|
|
2773
3242
|
}
|
|
2774
|
-
async function parseTsConfig(
|
|
3243
|
+
async function parseTsConfig(fs7, packagePath) {
|
|
2775
3244
|
const tsconfigPath = packagePath === "." ? "tsconfig.json" : `${packagePath}/tsconfig.json`;
|
|
2776
|
-
const tsconfig = await safeParseJson(
|
|
3245
|
+
const tsconfig = await safeParseJson(fs7, tsconfigPath);
|
|
2777
3246
|
if (!tsconfig?.compilerOptions) {
|
|
2778
3247
|
return null;
|
|
2779
3248
|
}
|
|
2780
3249
|
const { outDir, rootDir, baseUrl, paths } = tsconfig.compilerOptions;
|
|
2781
3250
|
return { outDir, rootDir, baseUrl, paths };
|
|
2782
3251
|
}
|
|
2783
|
-
async function resolveToSource(
|
|
3252
|
+
async function resolveToSource(fs7, basePath, filePath, tsConfig) {
|
|
2784
3253
|
const normalized = filePath.replace(/^\.\//, "");
|
|
2785
3254
|
const checkPath = (p) => basePath === "." ? p : `${basePath}/${p}`;
|
|
2786
3255
|
const isSourceTs = normalized.endsWith(".ts") && !normalized.endsWith(".d.ts") || normalized.endsWith(".tsx");
|
|
2787
3256
|
if (isSourceTs) {
|
|
2788
|
-
if (await
|
|
3257
|
+
if (await fs7.exists(checkPath(normalized))) {
|
|
2789
3258
|
return { path: normalized, isDeclarationOnly: false };
|
|
2790
3259
|
}
|
|
2791
3260
|
}
|
|
@@ -2827,19 +3296,19 @@ async function resolveToSource(fs5, basePath, filePath, tsConfig) {
|
|
|
2827
3296
|
for (const candidate of candidates) {
|
|
2828
3297
|
if (candidate.endsWith(".d.ts"))
|
|
2829
3298
|
continue;
|
|
2830
|
-
if (await
|
|
3299
|
+
if (await fs7.exists(checkPath(candidate))) {
|
|
2831
3300
|
return { path: candidate, isDeclarationOnly: false };
|
|
2832
3301
|
}
|
|
2833
3302
|
}
|
|
2834
3303
|
if (normalized.endsWith(".d.ts")) {
|
|
2835
|
-
if (await
|
|
3304
|
+
if (await fs7.exists(checkPath(normalized))) {
|
|
2836
3305
|
return { path: normalized, isDeclarationOnly: true };
|
|
2837
3306
|
}
|
|
2838
3307
|
}
|
|
2839
3308
|
return null;
|
|
2840
3309
|
}
|
|
2841
3310
|
// src/detect/filesystem.ts
|
|
2842
|
-
import * as
|
|
3311
|
+
import * as fs7 from "node:fs";
|
|
2843
3312
|
import * as nodePath from "node:path";
|
|
2844
3313
|
import { Writable } from "node:stream";
|
|
2845
3314
|
|
|
@@ -2852,19 +3321,19 @@ class NodeFileSystem {
|
|
|
2852
3321
|
return nodePath.join(this.basePath, relativePath);
|
|
2853
3322
|
}
|
|
2854
3323
|
async exists(relativePath) {
|
|
2855
|
-
return
|
|
3324
|
+
return fs7.existsSync(this.resolve(relativePath));
|
|
2856
3325
|
}
|
|
2857
3326
|
async readFile(relativePath) {
|
|
2858
|
-
return
|
|
3327
|
+
return fs7.readFileSync(this.resolve(relativePath), "utf-8");
|
|
2859
3328
|
}
|
|
2860
3329
|
async readDir(relativePath) {
|
|
2861
|
-
return
|
|
3330
|
+
return fs7.readdirSync(this.resolve(relativePath));
|
|
2862
3331
|
}
|
|
2863
3332
|
async isDirectory(relativePath) {
|
|
2864
3333
|
const fullPath = this.resolve(relativePath);
|
|
2865
|
-
if (!
|
|
3334
|
+
if (!fs7.existsSync(fullPath))
|
|
2866
3335
|
return false;
|
|
2867
|
-
return
|
|
3336
|
+
return fs7.statSync(fullPath).isDirectory();
|
|
2868
3337
|
}
|
|
2869
3338
|
}
|
|
2870
3339
|
function createCaptureStream() {
|
|
@@ -2880,9 +3349,9 @@ function createCaptureStream() {
|
|
|
2880
3349
|
|
|
2881
3350
|
class FileNotFoundError extends Error {
|
|
2882
3351
|
path;
|
|
2883
|
-
constructor(
|
|
2884
|
-
super(message ?? `File not found: ${
|
|
2885
|
-
this.path =
|
|
3352
|
+
constructor(path7, message) {
|
|
3353
|
+
super(message ?? `File not found: ${path7}`);
|
|
3354
|
+
this.path = path7;
|
|
2886
3355
|
this.name = "FileNotFoundError";
|
|
2887
3356
|
}
|
|
2888
3357
|
}
|
|
@@ -2892,34 +3361,34 @@ class SandboxFileSystem {
|
|
|
2892
3361
|
constructor(sandbox) {
|
|
2893
3362
|
this.sandbox = sandbox;
|
|
2894
3363
|
}
|
|
2895
|
-
async exists(
|
|
3364
|
+
async exists(path7) {
|
|
2896
3365
|
const result = await this.sandbox.runCommand({
|
|
2897
3366
|
cmd: "test",
|
|
2898
|
-
args: ["-e",
|
|
3367
|
+
args: ["-e", path7]
|
|
2899
3368
|
});
|
|
2900
3369
|
return result.exitCode === 0;
|
|
2901
3370
|
}
|
|
2902
|
-
async readFile(
|
|
2903
|
-
const exists = await this.exists(
|
|
3371
|
+
async readFile(path7) {
|
|
3372
|
+
const exists = await this.exists(path7);
|
|
2904
3373
|
if (!exists) {
|
|
2905
|
-
throw new FileNotFoundError(
|
|
3374
|
+
throw new FileNotFoundError(path7);
|
|
2906
3375
|
}
|
|
2907
3376
|
const capture = createCaptureStream();
|
|
2908
3377
|
const result = await this.sandbox.runCommand({
|
|
2909
3378
|
cmd: "cat",
|
|
2910
|
-
args: [
|
|
3379
|
+
args: [path7],
|
|
2911
3380
|
stdout: capture.stream
|
|
2912
3381
|
});
|
|
2913
3382
|
if (result.exitCode !== 0) {
|
|
2914
|
-
throw new FileNotFoundError(
|
|
3383
|
+
throw new FileNotFoundError(path7, `Failed to read file: ${path7}`);
|
|
2915
3384
|
}
|
|
2916
3385
|
return capture.getOutput();
|
|
2917
3386
|
}
|
|
2918
|
-
async readDir(
|
|
3387
|
+
async readDir(path7) {
|
|
2919
3388
|
const capture = createCaptureStream();
|
|
2920
3389
|
const result = await this.sandbox.runCommand({
|
|
2921
3390
|
cmd: "ls",
|
|
2922
|
-
args: ["-1",
|
|
3391
|
+
args: ["-1", path7],
|
|
2923
3392
|
stdout: capture.stream
|
|
2924
3393
|
});
|
|
2925
3394
|
if (result.exitCode !== 0) {
|
|
@@ -2928,20 +3397,20 @@ class SandboxFileSystem {
|
|
|
2928
3397
|
return capture.getOutput().split(`
|
|
2929
3398
|
`).filter(Boolean);
|
|
2930
3399
|
}
|
|
2931
|
-
async isDirectory(
|
|
3400
|
+
async isDirectory(path7) {
|
|
2932
3401
|
const result = await this.sandbox.runCommand({
|
|
2933
3402
|
cmd: "test",
|
|
2934
|
-
args: ["-d",
|
|
3403
|
+
args: ["-d", path7]
|
|
2935
3404
|
});
|
|
2936
3405
|
return result.exitCode === 0;
|
|
2937
3406
|
}
|
|
2938
3407
|
}
|
|
2939
3408
|
// src/detect/monorepo.ts
|
|
2940
|
-
async function detectMonorepo(
|
|
2941
|
-
const pkgJson = await readPackageJson(
|
|
3409
|
+
async function detectMonorepo(fs8) {
|
|
3410
|
+
const pkgJson = await readPackageJson(fs8, ".");
|
|
2942
3411
|
if (pkgJson?.workspaces) {
|
|
2943
3412
|
const patterns = extractWorkspacePatterns(pkgJson.workspaces);
|
|
2944
|
-
const packages = await resolveWorkspacePackages(
|
|
3413
|
+
const packages = await resolveWorkspacePackages(fs8, patterns, pkgJson.name, pkgJson.private);
|
|
2945
3414
|
return {
|
|
2946
3415
|
isMonorepo: packages.length > 0,
|
|
2947
3416
|
type: "npm-workspaces",
|
|
@@ -2949,10 +3418,10 @@ async function detectMonorepo(fs6) {
|
|
|
2949
3418
|
packages
|
|
2950
3419
|
};
|
|
2951
3420
|
}
|
|
2952
|
-
if (await
|
|
2953
|
-
const content = await
|
|
3421
|
+
if (await fs8.exists("pnpm-workspace.yaml")) {
|
|
3422
|
+
const content = await fs8.readFile("pnpm-workspace.yaml");
|
|
2954
3423
|
const patterns = parsePnpmWorkspace(content);
|
|
2955
|
-
const packages = await resolveWorkspacePackages(
|
|
3424
|
+
const packages = await resolveWorkspacePackages(fs8, patterns, pkgJson?.name, pkgJson?.private);
|
|
2956
3425
|
return {
|
|
2957
3426
|
isMonorepo: packages.length > 0,
|
|
2958
3427
|
type: "pnpm-workspaces",
|
|
@@ -2960,10 +3429,10 @@ async function detectMonorepo(fs6) {
|
|
|
2960
3429
|
packages
|
|
2961
3430
|
};
|
|
2962
3431
|
}
|
|
2963
|
-
if (await
|
|
2964
|
-
const lerna = await safeParseJson(
|
|
3432
|
+
if (await fs8.exists("lerna.json")) {
|
|
3433
|
+
const lerna = await safeParseJson(fs8, "lerna.json");
|
|
2965
3434
|
const patterns = lerna?.packages ?? ["packages/*"];
|
|
2966
|
-
const packages = await resolveWorkspacePackages(
|
|
3435
|
+
const packages = await resolveWorkspacePackages(fs8, patterns, pkgJson?.name, pkgJson?.private);
|
|
2967
3436
|
return {
|
|
2968
3437
|
isMonorepo: packages.length > 0,
|
|
2969
3438
|
type: "lerna",
|
|
@@ -3037,7 +3506,7 @@ function parsePnpmWorkspace(content) {
|
|
|
3037
3506
|
}
|
|
3038
3507
|
return patterns.length > 0 ? patterns : ["packages/*"];
|
|
3039
3508
|
}
|
|
3040
|
-
async function resolveWorkspacePackages(
|
|
3509
|
+
async function resolveWorkspacePackages(fs8, patterns, rootPackageName, rootIsPrivate) {
|
|
3041
3510
|
const packages = [];
|
|
3042
3511
|
const seen = new Set;
|
|
3043
3512
|
if (rootPackageName && !rootIsPrivate && rootPackageName !== "root") {
|
|
@@ -3059,18 +3528,18 @@ async function resolveWorkspacePackages(fs6, patterns, rootPackageName, rootIsPr
|
|
|
3059
3528
|
}
|
|
3060
3529
|
dirsToScan.add("packages");
|
|
3061
3530
|
for (const dir of dirsToScan) {
|
|
3062
|
-
if (!await
|
|
3531
|
+
if (!await fs8.exists(dir))
|
|
3063
3532
|
continue;
|
|
3064
|
-
if (!await
|
|
3533
|
+
if (!await fs8.isDirectory(dir))
|
|
3065
3534
|
continue;
|
|
3066
|
-
const subdirs = await
|
|
3535
|
+
const subdirs = await fs8.readDir(dir);
|
|
3067
3536
|
for (const subdir of subdirs) {
|
|
3068
3537
|
const pkgPath = `${dir}/${subdir}`;
|
|
3069
3538
|
const pkgJsonPath = `${pkgPath}/package.json`;
|
|
3070
|
-
if (!await
|
|
3539
|
+
if (!await fs8.exists(pkgJsonPath))
|
|
3071
3540
|
continue;
|
|
3072
3541
|
try {
|
|
3073
|
-
const content = await
|
|
3542
|
+
const content = await fs8.readFile(pkgJsonPath);
|
|
3074
3543
|
const pkg = JSON.parse(content);
|
|
3075
3544
|
if (pkg.name && !seen.has(pkg.name)) {
|
|
3076
3545
|
seen.add(pkg.name);
|
|
@@ -3142,14 +3611,14 @@ var DEFAULT_PM = {
|
|
|
3142
3611
|
installArgs: ["install", "--legacy-peer-deps"],
|
|
3143
3612
|
runPrefix: ["npm", "run"]
|
|
3144
3613
|
};
|
|
3145
|
-
async function detectPackageManager(
|
|
3146
|
-
const pkgJson = await safeParseJson(
|
|
3614
|
+
async function detectPackageManager(fs8) {
|
|
3615
|
+
const pkgJson = await safeParseJson(fs8, "package.json");
|
|
3147
3616
|
if (pkgJson?.packageManager) {
|
|
3148
3617
|
const pmName = parsePackageManagerField(pkgJson.packageManager);
|
|
3149
3618
|
if (pmName && PM_CONFIGS[pmName]) {
|
|
3150
3619
|
const config = PM_CONFIGS[pmName];
|
|
3151
3620
|
for (const lockfile of config.lockfiles) {
|
|
3152
|
-
if (await
|
|
3621
|
+
if (await fs8.exists(lockfile)) {
|
|
3153
3622
|
return { ...config.info, lockfile };
|
|
3154
3623
|
}
|
|
3155
3624
|
}
|
|
@@ -3159,7 +3628,7 @@ async function detectPackageManager(fs6) {
|
|
|
3159
3628
|
const foundLockfiles = [];
|
|
3160
3629
|
for (const [pmName, config] of Object.entries(PM_CONFIGS)) {
|
|
3161
3630
|
for (const lockfile of config.lockfiles) {
|
|
3162
|
-
if (await
|
|
3631
|
+
if (await fs8.exists(lockfile)) {
|
|
3163
3632
|
foundLockfiles.push({ lockfile, pm: pmName });
|
|
3164
3633
|
}
|
|
3165
3634
|
}
|
|
@@ -3191,10 +3660,10 @@ function getRunCommand(pm, script) {
|
|
|
3191
3660
|
return [...pm.runPrefix, script];
|
|
3192
3661
|
}
|
|
3193
3662
|
// src/detect/index.ts
|
|
3194
|
-
async function analyzeProject(
|
|
3663
|
+
async function analyzeProject(fs8, options = {}) {
|
|
3195
3664
|
const [packageManager, monorepo] = await Promise.all([
|
|
3196
|
-
detectPackageManager(
|
|
3197
|
-
detectMonorepo(
|
|
3665
|
+
detectPackageManager(fs8),
|
|
3666
|
+
detectMonorepo(fs8)
|
|
3198
3667
|
]);
|
|
3199
3668
|
let targetPath = ".";
|
|
3200
3669
|
if (monorepo.isMonorepo) {
|
|
@@ -3211,8 +3680,8 @@ async function analyzeProject(fs6, options = {}) {
|
|
|
3211
3680
|
targetPath = pkg.path;
|
|
3212
3681
|
}
|
|
3213
3682
|
const [entryPoint, build] = await Promise.all([
|
|
3214
|
-
detectEntryPoint(
|
|
3215
|
-
detectBuildInfo(
|
|
3683
|
+
detectEntryPoint(fs8, targetPath),
|
|
3684
|
+
detectBuildInfo(fs8, targetPath)
|
|
3216
3685
|
]);
|
|
3217
3686
|
return { packageManager, monorepo, entryPoint, build };
|
|
3218
3687
|
}
|
|
@@ -3256,17 +3725,17 @@ function shouldValidate(validations, check) {
|
|
|
3256
3725
|
return validations.includes(check);
|
|
3257
3726
|
}
|
|
3258
3727
|
// src/typecheck/example-typechecker.ts
|
|
3259
|
-
import * as
|
|
3260
|
-
import * as
|
|
3728
|
+
import * as fs8 from "node:fs";
|
|
3729
|
+
import * as path7 from "node:path";
|
|
3261
3730
|
import ts3 from "typescript";
|
|
3262
3731
|
function stripCodeBlockMarkers(code) {
|
|
3263
3732
|
return code.replace(/^```(?:ts|typescript|js|javascript)?\n?/i, "").replace(/\n?```$/i, "").trim();
|
|
3264
3733
|
}
|
|
3265
3734
|
function getPackageName(packagePath) {
|
|
3266
|
-
const pkgJsonPath =
|
|
3267
|
-
if (
|
|
3735
|
+
const pkgJsonPath = path7.join(packagePath, "package.json");
|
|
3736
|
+
if (fs8.existsSync(pkgJsonPath)) {
|
|
3268
3737
|
try {
|
|
3269
|
-
const pkgJson = JSON.parse(
|
|
3738
|
+
const pkgJson = JSON.parse(fs8.readFileSync(pkgJsonPath, "utf-8"));
|
|
3270
3739
|
return pkgJson.name;
|
|
3271
3740
|
} catch {
|
|
3272
3741
|
return;
|
|
@@ -3276,12 +3745,12 @@ function getPackageName(packagePath) {
|
|
|
3276
3745
|
}
|
|
3277
3746
|
function findTsConfig(packagePath) {
|
|
3278
3747
|
let dir = packagePath;
|
|
3279
|
-
while (dir !==
|
|
3280
|
-
const tsConfigPath =
|
|
3281
|
-
if (
|
|
3748
|
+
while (dir !== path7.dirname(dir)) {
|
|
3749
|
+
const tsConfigPath = path7.join(dir, "tsconfig.json");
|
|
3750
|
+
if (fs8.existsSync(tsConfigPath)) {
|
|
3282
3751
|
return tsConfigPath;
|
|
3283
3752
|
}
|
|
3284
|
-
dir =
|
|
3753
|
+
dir = path7.dirname(dir);
|
|
3285
3754
|
}
|
|
3286
3755
|
return;
|
|
3287
3756
|
}
|
|
@@ -3297,10 +3766,10 @@ function createVirtualSource(example, packageName, exportNames) {
|
|
|
3297
3766
|
`);
|
|
3298
3767
|
}
|
|
3299
3768
|
function getCompilerOptions(tsconfigPath) {
|
|
3300
|
-
if (tsconfigPath &&
|
|
3769
|
+
if (tsconfigPath && fs8.existsSync(tsconfigPath)) {
|
|
3301
3770
|
const configFile = ts3.readConfigFile(tsconfigPath, ts3.sys.readFile);
|
|
3302
3771
|
if (!configFile.error) {
|
|
3303
|
-
const parsed = ts3.parseJsonConfigFileContent(configFile.config, ts3.sys,
|
|
3772
|
+
const parsed = ts3.parseJsonConfigFileContent(configFile.config, ts3.sys, path7.dirname(tsconfigPath));
|
|
3304
3773
|
return {
|
|
3305
3774
|
...parsed.options,
|
|
3306
3775
|
noEmit: true,
|
|
@@ -3326,7 +3795,7 @@ function typecheckExample(example, packagePath, options = {}) {
|
|
|
3326
3795
|
const tsconfigPath = options.tsconfig ?? findTsConfig(packagePath);
|
|
3327
3796
|
const compilerOptions = getCompilerOptions(tsconfigPath);
|
|
3328
3797
|
const virtualSource = createVirtualSource(cleanCode, packageName, exportNames);
|
|
3329
|
-
const virtualFileName =
|
|
3798
|
+
const virtualFileName = path7.join(packagePath, "__doccov_example__.ts");
|
|
3330
3799
|
const hasImport = packageName !== undefined && exportNames && exportNames.length > 0;
|
|
3331
3800
|
const lineOffset = hasImport ? 2 : 0;
|
|
3332
3801
|
const sourceFile = ts3.createSourceFile(virtualFileName, virtualSource, ts3.ScriptTarget.ES2022, true);
|
|
@@ -3397,25 +3866,25 @@ function typecheckExamples(examples, packagePath, options = {}) {
|
|
|
3397
3866
|
}
|
|
3398
3867
|
|
|
3399
3868
|
// src/utils/example-runner.ts
|
|
3400
|
-
import { spawn } from "node:child_process";
|
|
3401
|
-
import * as
|
|
3869
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
3870
|
+
import * as fs9 from "node:fs";
|
|
3402
3871
|
import * as os from "node:os";
|
|
3403
|
-
import * as
|
|
3872
|
+
import * as path8 from "node:path";
|
|
3404
3873
|
function stripCodeBlockMarkers2(code) {
|
|
3405
3874
|
return code.replace(/^```(?:ts|typescript|js|javascript)?\n?/i, "").replace(/\n?```$/i, "").trim();
|
|
3406
3875
|
}
|
|
3407
3876
|
async function runExample(code, options = {}) {
|
|
3408
3877
|
const { timeout = 5000, cwd = process.cwd() } = options;
|
|
3409
3878
|
const cleanCode = stripCodeBlockMarkers2(code);
|
|
3410
|
-
const tmpFile =
|
|
3879
|
+
const tmpFile = path8.join(cwd, `doccov-example-${Date.now()}-${Math.random().toString(36).slice(2)}.ts`);
|
|
3411
3880
|
try {
|
|
3412
|
-
|
|
3881
|
+
fs9.writeFileSync(tmpFile, cleanCode, "utf-8");
|
|
3413
3882
|
const startTime = Date.now();
|
|
3414
|
-
return await new Promise((
|
|
3883
|
+
return await new Promise((resolve5) => {
|
|
3415
3884
|
let stdout = "";
|
|
3416
3885
|
let stderr = "";
|
|
3417
3886
|
let killed = false;
|
|
3418
|
-
const proc =
|
|
3887
|
+
const proc = spawn2("node", ["--experimental-strip-types", tmpFile], {
|
|
3419
3888
|
cwd,
|
|
3420
3889
|
timeout,
|
|
3421
3890
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -3434,7 +3903,7 @@ async function runExample(code, options = {}) {
|
|
|
3434
3903
|
clearTimeout(timeoutId);
|
|
3435
3904
|
const duration = Date.now() - startTime;
|
|
3436
3905
|
if (killed) {
|
|
3437
|
-
|
|
3906
|
+
resolve5({
|
|
3438
3907
|
success: false,
|
|
3439
3908
|
stdout,
|
|
3440
3909
|
stderr: stderr || `Example timed out after ${timeout}ms`,
|
|
@@ -3442,7 +3911,7 @@ async function runExample(code, options = {}) {
|
|
|
3442
3911
|
duration
|
|
3443
3912
|
});
|
|
3444
3913
|
} else {
|
|
3445
|
-
|
|
3914
|
+
resolve5({
|
|
3446
3915
|
success: exitCode === 0,
|
|
3447
3916
|
stdout,
|
|
3448
3917
|
stderr,
|
|
@@ -3453,7 +3922,7 @@ async function runExample(code, options = {}) {
|
|
|
3453
3922
|
});
|
|
3454
3923
|
proc.on("error", (error) => {
|
|
3455
3924
|
clearTimeout(timeoutId);
|
|
3456
|
-
|
|
3925
|
+
resolve5({
|
|
3457
3926
|
success: false,
|
|
3458
3927
|
stdout,
|
|
3459
3928
|
stderr: error.message,
|
|
@@ -3464,7 +3933,7 @@ async function runExample(code, options = {}) {
|
|
|
3464
3933
|
});
|
|
3465
3934
|
} finally {
|
|
3466
3935
|
try {
|
|
3467
|
-
|
|
3936
|
+
fs9.unlinkSync(tmpFile);
|
|
3468
3937
|
} catch {}
|
|
3469
3938
|
}
|
|
3470
3939
|
}
|
|
@@ -3479,15 +3948,15 @@ async function runExamples(examples, options = {}) {
|
|
|
3479
3948
|
return results;
|
|
3480
3949
|
}
|
|
3481
3950
|
function detectPackageManager2(cwd) {
|
|
3482
|
-
if (
|
|
3951
|
+
if (fs9.existsSync(path8.join(cwd, "bun.lockb")))
|
|
3483
3952
|
return "bun";
|
|
3484
|
-
if (
|
|
3953
|
+
if (fs9.existsSync(path8.join(cwd, "bun.lock")))
|
|
3485
3954
|
return "bun";
|
|
3486
|
-
if (
|
|
3955
|
+
if (fs9.existsSync(path8.join(cwd, "pnpm-lock.yaml")))
|
|
3487
3956
|
return "pnpm";
|
|
3488
|
-
if (
|
|
3957
|
+
if (fs9.existsSync(path8.join(cwd, "yarn.lock")))
|
|
3489
3958
|
return "yarn";
|
|
3490
|
-
if (
|
|
3959
|
+
if (fs9.existsSync(path8.join(cwd, "package-lock.json")))
|
|
3491
3960
|
return "npm";
|
|
3492
3961
|
return "npm";
|
|
3493
3962
|
}
|
|
@@ -3504,11 +3973,11 @@ function getInstallCommand2(pm, packagePath) {
|
|
|
3504
3973
|
}
|
|
3505
3974
|
}
|
|
3506
3975
|
async function runCommand(cmd, args, options) {
|
|
3507
|
-
return new Promise((
|
|
3976
|
+
return new Promise((resolve5) => {
|
|
3508
3977
|
let stdout = "";
|
|
3509
3978
|
let stderr = "";
|
|
3510
3979
|
let killed = false;
|
|
3511
|
-
const proc =
|
|
3980
|
+
const proc = spawn2(cmd, args, {
|
|
3512
3981
|
cwd: options.cwd,
|
|
3513
3982
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3514
3983
|
});
|
|
@@ -3525,14 +3994,14 @@ async function runCommand(cmd, args, options) {
|
|
|
3525
3994
|
proc.on("close", (exitCode) => {
|
|
3526
3995
|
clearTimeout(timeoutId);
|
|
3527
3996
|
if (killed) {
|
|
3528
|
-
|
|
3997
|
+
resolve5({
|
|
3529
3998
|
success: false,
|
|
3530
3999
|
stdout,
|
|
3531
4000
|
stderr: stderr || `Command timed out after ${options.timeout}ms`,
|
|
3532
4001
|
exitCode: exitCode ?? 1
|
|
3533
4002
|
});
|
|
3534
4003
|
} else {
|
|
3535
|
-
|
|
4004
|
+
resolve5({
|
|
3536
4005
|
success: exitCode === 0,
|
|
3537
4006
|
stdout,
|
|
3538
4007
|
stderr,
|
|
@@ -3542,7 +4011,7 @@ async function runCommand(cmd, args, options) {
|
|
|
3542
4011
|
});
|
|
3543
4012
|
proc.on("error", (error) => {
|
|
3544
4013
|
clearTimeout(timeoutId);
|
|
3545
|
-
|
|
4014
|
+
resolve5({
|
|
3546
4015
|
success: false,
|
|
3547
4016
|
stdout,
|
|
3548
4017
|
stderr: error.message,
|
|
@@ -3555,12 +4024,12 @@ async function runExamplesWithPackage(examples, options) {
|
|
|
3555
4024
|
const { packagePath, packageManager, installTimeout = 60000, timeout = 5000 } = options;
|
|
3556
4025
|
const startTime = Date.now();
|
|
3557
4026
|
const results = new Map;
|
|
3558
|
-
const absolutePackagePath =
|
|
3559
|
-
const workDir =
|
|
4027
|
+
const absolutePackagePath = path8.resolve(packagePath);
|
|
4028
|
+
const workDir = path8.join(os.tmpdir(), `doccov-examples-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
3560
4029
|
try {
|
|
3561
|
-
|
|
4030
|
+
fs9.mkdirSync(workDir, { recursive: true });
|
|
3562
4031
|
const pkgJson = { name: "doccov-example-runner", type: "module" };
|
|
3563
|
-
|
|
4032
|
+
fs9.writeFileSync(path8.join(workDir, "package.json"), JSON.stringify(pkgJson, null, 2));
|
|
3564
4033
|
const pm = packageManager ?? detectPackageManager2(options.cwd ?? process.cwd());
|
|
3565
4034
|
const { cmd, args } = getInstallCommand2(pm, absolutePackagePath);
|
|
3566
4035
|
const installResult = await runCommand(cmd, args, {
|
|
@@ -3588,7 +4057,7 @@ async function runExamplesWithPackage(examples, options) {
|
|
|
3588
4057
|
};
|
|
3589
4058
|
} finally {
|
|
3590
4059
|
try {
|
|
3591
|
-
|
|
4060
|
+
fs9.rmSync(workDir, { recursive: true, force: true });
|
|
3592
4061
|
} catch {}
|
|
3593
4062
|
}
|
|
3594
4063
|
}
|
|
@@ -3767,9 +4236,12 @@ async function validateExamples(exports, options) {
|
|
|
3767
4236
|
}
|
|
3768
4237
|
return result;
|
|
3769
4238
|
}
|
|
4239
|
+
// src/extractor.ts
|
|
4240
|
+
import * as path13 from "node:path";
|
|
4241
|
+
|
|
3770
4242
|
// src/analysis/run-analysis.ts
|
|
3771
|
-
import * as
|
|
3772
|
-
import * as
|
|
4243
|
+
import * as fs11 from "node:fs";
|
|
4244
|
+
import * as path12 from "node:path";
|
|
3773
4245
|
// src/utils/type-utils.ts
|
|
3774
4246
|
function getTypeId(type, typeChecker) {
|
|
3775
4247
|
const internalId = type.id;
|
|
@@ -3892,7 +4364,7 @@ function collectReferencedTypesFromNode(node, typeChecker, referencedTypes) {
|
|
|
3892
4364
|
}
|
|
3893
4365
|
|
|
3894
4366
|
// src/analysis/context.ts
|
|
3895
|
-
import * as
|
|
4367
|
+
import * as path10 from "node:path";
|
|
3896
4368
|
|
|
3897
4369
|
// src/options.ts
|
|
3898
4370
|
var DEFAULT_MAX_TYPE_DEPTH = 20;
|
|
@@ -3913,7 +4385,7 @@ function normalizeDocCovOptions(options = {}) {
|
|
|
3913
4385
|
}
|
|
3914
4386
|
|
|
3915
4387
|
// src/analysis/program.ts
|
|
3916
|
-
import * as
|
|
4388
|
+
import * as path9 from "node:path";
|
|
3917
4389
|
var DEFAULT_COMPILER_OPTIONS = {
|
|
3918
4390
|
target: ts.ScriptTarget.Latest,
|
|
3919
4391
|
module: ts.ModuleKind.CommonJS,
|
|
@@ -3923,14 +4395,14 @@ var DEFAULT_COMPILER_OPTIONS = {
|
|
|
3923
4395
|
};
|
|
3924
4396
|
function createProgram({
|
|
3925
4397
|
entryFile,
|
|
3926
|
-
baseDir =
|
|
4398
|
+
baseDir = path9.dirname(entryFile),
|
|
3927
4399
|
content
|
|
3928
4400
|
}) {
|
|
3929
4401
|
const configPath = ts.findConfigFile(baseDir, ts.sys.fileExists, "tsconfig.json");
|
|
3930
4402
|
let compilerOptions = { ...DEFAULT_COMPILER_OPTIONS };
|
|
3931
4403
|
if (configPath) {
|
|
3932
4404
|
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
3933
|
-
const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys,
|
|
4405
|
+
const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path9.dirname(configPath));
|
|
3934
4406
|
compilerOptions = { ...compilerOptions, ...parsedConfig.options };
|
|
3935
4407
|
}
|
|
3936
4408
|
const allowJsVal = compilerOptions.allowJs;
|
|
@@ -3965,9 +4437,10 @@ function createAnalysisContext({
|
|
|
3965
4437
|
entryFile,
|
|
3966
4438
|
packageDir,
|
|
3967
4439
|
content,
|
|
3968
|
-
options
|
|
4440
|
+
options,
|
|
4441
|
+
detectedSchemas
|
|
3969
4442
|
}) {
|
|
3970
|
-
const baseDir = packageDir ??
|
|
4443
|
+
const baseDir = packageDir ?? path10.dirname(entryFile);
|
|
3971
4444
|
const normalizedOptions = normalizeDocCovOptions(options);
|
|
3972
4445
|
const programResult = createProgram({ entryFile, baseDir, content });
|
|
3973
4446
|
if (!programResult.sourceFile) {
|
|
@@ -3982,13 +4455,14 @@ function createAnalysisContext({
|
|
|
3982
4455
|
compilerOptions: programResult.compilerOptions,
|
|
3983
4456
|
compilerHost: programResult.compilerHost,
|
|
3984
4457
|
options: normalizedOptions,
|
|
3985
|
-
configPath: programResult.configPath
|
|
4458
|
+
configPath: programResult.configPath,
|
|
4459
|
+
detectedSchemas
|
|
3986
4460
|
};
|
|
3987
4461
|
}
|
|
3988
4462
|
|
|
3989
4463
|
// src/analysis/spec-builder.ts
|
|
3990
|
-
import * as
|
|
3991
|
-
import * as
|
|
4464
|
+
import * as fs10 from "node:fs";
|
|
4465
|
+
import * as path11 from "node:path";
|
|
3992
4466
|
import { SCHEMA_URL, SCHEMA_VERSION } from "@openpkg-ts/spec";
|
|
3993
4467
|
|
|
3994
4468
|
// src/analysis/decorator-utils.ts
|
|
@@ -4020,354 +4494,100 @@ function extractParameterDecorators(param) {
|
|
|
4020
4494
|
return extractDecorators(param);
|
|
4021
4495
|
}
|
|
4022
4496
|
|
|
4023
|
-
// src/utils/
|
|
4024
|
-
var
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4497
|
+
// src/utils/schema-builder.ts
|
|
4498
|
+
var BUILTIN_TYPE_SCHEMAS = {
|
|
4499
|
+
Date: { type: "string", format: "date-time" },
|
|
4500
|
+
RegExp: { type: "object", description: "RegExp" },
|
|
4501
|
+
Error: { type: "object" },
|
|
4502
|
+
Promise: { type: "object" },
|
|
4503
|
+
Map: { type: "object" },
|
|
4504
|
+
Set: { type: "object" },
|
|
4505
|
+
WeakMap: { type: "object" },
|
|
4506
|
+
WeakSet: { type: "object" },
|
|
4507
|
+
Function: { type: "object" },
|
|
4508
|
+
ArrayBuffer: { type: "string", format: "binary" },
|
|
4509
|
+
ArrayBufferLike: { type: "string", format: "binary" },
|
|
4510
|
+
DataView: { type: "string", format: "binary" },
|
|
4511
|
+
Uint8Array: { type: "string", format: "byte" },
|
|
4512
|
+
Uint16Array: { type: "string", format: "byte" },
|
|
4513
|
+
Uint32Array: { type: "string", format: "byte" },
|
|
4514
|
+
Int8Array: { type: "string", format: "byte" },
|
|
4515
|
+
Int16Array: { type: "string", format: "byte" },
|
|
4516
|
+
Int32Array: { type: "string", format: "byte" },
|
|
4517
|
+
Float32Array: { type: "string", format: "byte" },
|
|
4518
|
+
Float64Array: { type: "string", format: "byte" },
|
|
4519
|
+
BigInt64Array: { type: "string", format: "byte" },
|
|
4520
|
+
BigUint64Array: { type: "string", format: "byte" }
|
|
4035
4521
|
};
|
|
4036
|
-
function
|
|
4037
|
-
|
|
4038
|
-
}
|
|
4039
|
-
function isInternalProperty(name) {
|
|
4040
|
-
return name.startsWith("__@");
|
|
4041
|
-
}
|
|
4042
|
-
function isTypeBoxOptionalMarker(type) {
|
|
4043
|
-
const props = type.getProperties();
|
|
4044
|
-
if (props.length !== 1)
|
|
4522
|
+
function isObjectLiteralType(type) {
|
|
4523
|
+
if (!(type.getFlags() & ts.TypeFlags.Object)) {
|
|
4045
4524
|
return false;
|
|
4046
|
-
|
|
4525
|
+
}
|
|
4526
|
+
const objectFlags = type.objectFlags;
|
|
4527
|
+
return (objectFlags & ts.ObjectFlags.ObjectLiteral) !== 0;
|
|
4047
4528
|
}
|
|
4048
|
-
function
|
|
4049
|
-
|
|
4050
|
-
|
|
4529
|
+
function isPureRefSchema(value) {
|
|
4530
|
+
return Object.keys(value).length === 1 && "$ref" in value;
|
|
4531
|
+
}
|
|
4532
|
+
function withDescription(schema, description) {
|
|
4533
|
+
if (isPureRefSchema(schema)) {
|
|
4534
|
+
return {
|
|
4535
|
+
allOf: [schema],
|
|
4536
|
+
description
|
|
4537
|
+
};
|
|
4051
4538
|
}
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4539
|
+
return {
|
|
4540
|
+
...schema,
|
|
4541
|
+
description
|
|
4542
|
+
};
|
|
4056
4543
|
}
|
|
4057
|
-
function
|
|
4058
|
-
|
|
4059
|
-
|
|
4544
|
+
function propertiesToSchema(properties, description) {
|
|
4545
|
+
const schema = {
|
|
4546
|
+
type: "object",
|
|
4547
|
+
properties: {}
|
|
4548
|
+
};
|
|
4549
|
+
const required = [];
|
|
4550
|
+
for (const prop of properties) {
|
|
4551
|
+
const propType = prop.type;
|
|
4552
|
+
let propSchema;
|
|
4553
|
+
if (typeof propType === "string") {
|
|
4554
|
+
if (["string", "number", "boolean", "bigint", "null"].includes(propType)) {
|
|
4555
|
+
propSchema = { type: propType === "bigint" ? "string" : propType };
|
|
4556
|
+
} else {
|
|
4557
|
+
propSchema = { type: propType };
|
|
4558
|
+
}
|
|
4559
|
+
} else if (propType && typeof propType === "object") {
|
|
4560
|
+
propSchema = propType;
|
|
4561
|
+
} else {
|
|
4562
|
+
propSchema = { type: "any" };
|
|
4563
|
+
}
|
|
4564
|
+
if (prop.description && typeof propSchema === "object") {
|
|
4565
|
+
propSchema = withDescription(propSchema, prop.description);
|
|
4566
|
+
}
|
|
4567
|
+
schema.properties[prop.name] = propSchema;
|
|
4568
|
+
if (!prop.optional) {
|
|
4569
|
+
required.push(prop.name);
|
|
4570
|
+
}
|
|
4060
4571
|
}
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
return propType;
|
|
4572
|
+
if (required.length > 0) {
|
|
4573
|
+
schema.required = required;
|
|
4064
4574
|
}
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
return typeChecker.getTypeOfSymbolAtLocation(prop, decl);
|
|
4575
|
+
if (description) {
|
|
4576
|
+
return withDescription(schema, description);
|
|
4068
4577
|
}
|
|
4069
|
-
return
|
|
4578
|
+
return schema;
|
|
4070
4579
|
}
|
|
4071
4580
|
var _formatTypeReference = null;
|
|
4072
|
-
function
|
|
4581
|
+
function setSchemaBuilderFormatTypeReference(fn) {
|
|
4073
4582
|
_formatTypeReference = fn;
|
|
4074
4583
|
}
|
|
4075
|
-
function
|
|
4076
|
-
if (
|
|
4077
|
-
return
|
|
4584
|
+
function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName) {
|
|
4585
|
+
if (ts.isParenthesizedTypeNode(node)) {
|
|
4586
|
+
return buildSchemaFromTypeNode(node.type, typeChecker, typeRefs, referencedTypes, functionDoc ?? null, parentParamName);
|
|
4078
4587
|
}
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
if (depth > maxDepth) {
|
|
4083
|
-
return { type: "unknown" };
|
|
4084
|
-
}
|
|
4085
|
-
const symbol = type.getSymbol();
|
|
4086
|
-
if (!symbol)
|
|
4087
|
-
return null;
|
|
4088
|
-
const symbolName = symbol.getName();
|
|
4089
|
-
if (TYPEBOX_PRIMITIVE_MAP[symbolName]) {
|
|
4090
|
-
return { ...TYPEBOX_PRIMITIVE_MAP[symbolName] };
|
|
4091
|
-
}
|
|
4092
|
-
const objectType = type;
|
|
4093
|
-
if (!(objectType.objectFlags & ts.ObjectFlags.Reference)) {
|
|
4094
|
-
return null;
|
|
4095
|
-
}
|
|
4096
|
-
const typeRef = type;
|
|
4097
|
-
const typeArgs = typeRef.typeArguments;
|
|
4098
|
-
switch (symbolName) {
|
|
4099
|
-
case "TObject": {
|
|
4100
|
-
if (!typeArgs || typeArgs.length === 0) {
|
|
4101
|
-
return { type: "object" };
|
|
4102
|
-
}
|
|
4103
|
-
const propsType = typeArgs[0];
|
|
4104
|
-
const properties = {};
|
|
4105
|
-
const required = [];
|
|
4106
|
-
for (const prop of propsType.getProperties()) {
|
|
4107
|
-
const propName = prop.getName();
|
|
4108
|
-
if (isInternalProperty(propName)) {
|
|
4109
|
-
continue;
|
|
4110
|
-
}
|
|
4111
|
-
const propType = getPropertyType(prop, propsType, typeChecker);
|
|
4112
|
-
const propSymbol = propType.getSymbol();
|
|
4113
|
-
const propSymbolName = propSymbol?.getName();
|
|
4114
|
-
if (propSymbolName && typeRefs.has(propSymbolName)) {
|
|
4115
|
-
properties[propName] = { $ref: `#/types/${propSymbolName}` };
|
|
4116
|
-
} else if (propSymbolName && isTypeBoxSchemaType(propSymbolName)) {
|
|
4117
|
-
const nested = formatTypeBoxSchema(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
4118
|
-
properties[propName] = nested ?? { type: "object" };
|
|
4119
|
-
} else {
|
|
4120
|
-
properties[propName] = formatTypeReferenceInternal(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
4121
|
-
}
|
|
4122
|
-
const { isOptional } = unwrapTypeBoxOptional(propType);
|
|
4123
|
-
if (propSymbolName !== "TOptional" && !isOptional) {
|
|
4124
|
-
required.push(propName);
|
|
4125
|
-
}
|
|
4126
|
-
}
|
|
4127
|
-
const schema = { type: "object", properties };
|
|
4128
|
-
if (required.length > 0) {
|
|
4129
|
-
schema.required = required;
|
|
4130
|
-
}
|
|
4131
|
-
return schema;
|
|
4132
|
-
}
|
|
4133
|
-
case "TArray": {
|
|
4134
|
-
if (!typeArgs || typeArgs.length === 0) {
|
|
4135
|
-
return { type: "array" };
|
|
4136
|
-
}
|
|
4137
|
-
const itemType = typeArgs[0];
|
|
4138
|
-
const itemSymbol = itemType.getSymbol();
|
|
4139
|
-
const itemSymbolName = itemSymbol?.getName();
|
|
4140
|
-
let items;
|
|
4141
|
-
if (itemSymbolName && typeRefs.has(itemSymbolName)) {
|
|
4142
|
-
items = { $ref: `#/types/${itemSymbolName}` };
|
|
4143
|
-
} else if (itemSymbolName && isTypeBoxSchemaType(itemSymbolName)) {
|
|
4144
|
-
items = formatTypeBoxSchema(itemType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
4145
|
-
type: "object"
|
|
4146
|
-
};
|
|
4147
|
-
} else {
|
|
4148
|
-
items = formatTypeReferenceInternal(itemType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
4149
|
-
}
|
|
4150
|
-
return { type: "array", items };
|
|
4151
|
-
}
|
|
4152
|
-
case "TUnion": {
|
|
4153
|
-
if (!typeArgs || typeArgs.length === 0) {
|
|
4154
|
-
return { anyOf: [] };
|
|
4155
|
-
}
|
|
4156
|
-
const tupleType = typeArgs[0];
|
|
4157
|
-
const members = [];
|
|
4158
|
-
if (tupleType.isUnion()) {
|
|
4159
|
-
for (const memberType of tupleType.types) {
|
|
4160
|
-
const memberSymbol = memberType.getSymbol();
|
|
4161
|
-
const memberSymbolName = memberSymbol?.getName();
|
|
4162
|
-
if (memberSymbolName && typeRefs.has(memberSymbolName)) {
|
|
4163
|
-
members.push({ $ref: `#/types/${memberSymbolName}` });
|
|
4164
|
-
} else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
|
|
4165
|
-
members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
4166
|
-
type: "object"
|
|
4167
|
-
});
|
|
4168
|
-
} else {
|
|
4169
|
-
members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
|
|
4170
|
-
}
|
|
4171
|
-
}
|
|
4172
|
-
} else if (tupleType.typeArguments) {
|
|
4173
|
-
for (const memberType of tupleType.typeArguments) {
|
|
4174
|
-
const memberSymbol = memberType.getSymbol();
|
|
4175
|
-
const memberSymbolName = memberSymbol?.getName();
|
|
4176
|
-
if (memberSymbolName && typeRefs.has(memberSymbolName)) {
|
|
4177
|
-
members.push({ $ref: `#/types/${memberSymbolName}` });
|
|
4178
|
-
} else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
|
|
4179
|
-
members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
4180
|
-
type: "object"
|
|
4181
|
-
});
|
|
4182
|
-
} else {
|
|
4183
|
-
members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
|
|
4184
|
-
}
|
|
4185
|
-
}
|
|
4186
|
-
}
|
|
4187
|
-
return { anyOf: members };
|
|
4188
|
-
}
|
|
4189
|
-
case "TIntersect": {
|
|
4190
|
-
if (!typeArgs || typeArgs.length === 0) {
|
|
4191
|
-
return { allOf: [] };
|
|
4192
|
-
}
|
|
4193
|
-
const tupleType = typeArgs[0];
|
|
4194
|
-
const members = [];
|
|
4195
|
-
if (tupleType.typeArguments) {
|
|
4196
|
-
for (const memberType of tupleType.typeArguments) {
|
|
4197
|
-
const memberSymbol = memberType.getSymbol();
|
|
4198
|
-
const memberSymbolName = memberSymbol?.getName();
|
|
4199
|
-
if (memberSymbolName && typeRefs.has(memberSymbolName)) {
|
|
4200
|
-
members.push({ $ref: `#/types/${memberSymbolName}` });
|
|
4201
|
-
} else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
|
|
4202
|
-
members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
4203
|
-
type: "object"
|
|
4204
|
-
});
|
|
4205
|
-
} else {
|
|
4206
|
-
members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
|
|
4207
|
-
}
|
|
4208
|
-
}
|
|
4209
|
-
}
|
|
4210
|
-
return { allOf: members };
|
|
4211
|
-
}
|
|
4212
|
-
case "TOptional": {
|
|
4213
|
-
if (!typeArgs || typeArgs.length === 0) {
|
|
4214
|
-
return {};
|
|
4215
|
-
}
|
|
4216
|
-
const innerType = typeArgs[0];
|
|
4217
|
-
const innerSymbol = innerType.getSymbol();
|
|
4218
|
-
const innerSymbolName = innerSymbol?.getName();
|
|
4219
|
-
if (innerSymbolName && typeRefs.has(innerSymbolName)) {
|
|
4220
|
-
return { $ref: `#/types/${innerSymbolName}` };
|
|
4221
|
-
} else if (innerSymbolName && isTypeBoxSchemaType(innerSymbolName)) {
|
|
4222
|
-
return formatTypeBoxSchema(innerType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
4223
|
-
type: "object"
|
|
4224
|
-
};
|
|
4225
|
-
}
|
|
4226
|
-
return formatTypeReferenceInternal(innerType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
4227
|
-
}
|
|
4228
|
-
case "TLiteral": {
|
|
4229
|
-
if (!typeArgs || typeArgs.length === 0) {
|
|
4230
|
-
return { enum: [] };
|
|
4231
|
-
}
|
|
4232
|
-
const literalType = typeArgs[0];
|
|
4233
|
-
if (literalType.isLiteral()) {
|
|
4234
|
-
const value = literalType.value;
|
|
4235
|
-
return { enum: [value] };
|
|
4236
|
-
}
|
|
4237
|
-
const literalStr = typeChecker.typeToString(literalType);
|
|
4238
|
-
if (literalStr.startsWith('"') && literalStr.endsWith('"')) {
|
|
4239
|
-
return { enum: [literalStr.slice(1, -1)] };
|
|
4240
|
-
}
|
|
4241
|
-
return { enum: [literalStr] };
|
|
4242
|
-
}
|
|
4243
|
-
case "TRecord": {
|
|
4244
|
-
if (!typeArgs || typeArgs.length < 2) {
|
|
4245
|
-
return { type: "object", additionalProperties: true };
|
|
4246
|
-
}
|
|
4247
|
-
const valueType = typeArgs[1];
|
|
4248
|
-
const valueSymbol = valueType.getSymbol();
|
|
4249
|
-
const valueSymbolName = valueSymbol?.getName();
|
|
4250
|
-
let additionalProperties;
|
|
4251
|
-
if (valueSymbolName && typeRefs.has(valueSymbolName)) {
|
|
4252
|
-
additionalProperties = { $ref: `#/types/${valueSymbolName}` };
|
|
4253
|
-
} else if (valueSymbolName && isTypeBoxSchemaType(valueSymbolName)) {
|
|
4254
|
-
additionalProperties = formatTypeBoxSchema(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? true;
|
|
4255
|
-
} else {
|
|
4256
|
-
additionalProperties = formatTypeReferenceInternal(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
4257
|
-
}
|
|
4258
|
-
return { type: "object", additionalProperties };
|
|
4259
|
-
}
|
|
4260
|
-
case "TRef": {
|
|
4261
|
-
if (!typeArgs || typeArgs.length === 0) {
|
|
4262
|
-
return { $ref: "#/types/unknown" };
|
|
4263
|
-
}
|
|
4264
|
-
const refType = typeArgs[0];
|
|
4265
|
-
const refSymbol = refType.getSymbol();
|
|
4266
|
-
const refSymbolName = refSymbol?.getName();
|
|
4267
|
-
if (refSymbolName) {
|
|
4268
|
-
return { $ref: `#/types/${refSymbolName}` };
|
|
4269
|
-
}
|
|
4270
|
-
return { type: "object" };
|
|
4271
|
-
}
|
|
4272
|
-
default:
|
|
4273
|
-
return null;
|
|
4274
|
-
}
|
|
4275
|
-
}
|
|
4276
|
-
|
|
4277
|
-
// src/utils/schema-builder.ts
|
|
4278
|
-
var BUILTIN_TYPE_SCHEMAS = {
|
|
4279
|
-
Date: { type: "string", format: "date-time" },
|
|
4280
|
-
RegExp: { type: "object", description: "RegExp" },
|
|
4281
|
-
Error: { type: "object" },
|
|
4282
|
-
Promise: { type: "object" },
|
|
4283
|
-
Map: { type: "object" },
|
|
4284
|
-
Set: { type: "object" },
|
|
4285
|
-
WeakMap: { type: "object" },
|
|
4286
|
-
WeakSet: { type: "object" },
|
|
4287
|
-
Function: { type: "object" },
|
|
4288
|
-
ArrayBuffer: { type: "string", format: "binary" },
|
|
4289
|
-
ArrayBufferLike: { type: "string", format: "binary" },
|
|
4290
|
-
DataView: { type: "string", format: "binary" },
|
|
4291
|
-
Uint8Array: { type: "string", format: "byte" },
|
|
4292
|
-
Uint16Array: { type: "string", format: "byte" },
|
|
4293
|
-
Uint32Array: { type: "string", format: "byte" },
|
|
4294
|
-
Int8Array: { type: "string", format: "byte" },
|
|
4295
|
-
Int16Array: { type: "string", format: "byte" },
|
|
4296
|
-
Int32Array: { type: "string", format: "byte" },
|
|
4297
|
-
Float32Array: { type: "string", format: "byte" },
|
|
4298
|
-
Float64Array: { type: "string", format: "byte" },
|
|
4299
|
-
BigInt64Array: { type: "string", format: "byte" },
|
|
4300
|
-
BigUint64Array: { type: "string", format: "byte" }
|
|
4301
|
-
};
|
|
4302
|
-
function isObjectLiteralType(type) {
|
|
4303
|
-
if (!(type.getFlags() & ts.TypeFlags.Object)) {
|
|
4304
|
-
return false;
|
|
4305
|
-
}
|
|
4306
|
-
const objectFlags = type.objectFlags;
|
|
4307
|
-
return (objectFlags & ts.ObjectFlags.ObjectLiteral) !== 0;
|
|
4308
|
-
}
|
|
4309
|
-
function isPureRefSchema(value) {
|
|
4310
|
-
return Object.keys(value).length === 1 && "$ref" in value;
|
|
4311
|
-
}
|
|
4312
|
-
function withDescription(schema, description) {
|
|
4313
|
-
if (isPureRefSchema(schema)) {
|
|
4314
|
-
return {
|
|
4315
|
-
allOf: [schema],
|
|
4316
|
-
description
|
|
4317
|
-
};
|
|
4318
|
-
}
|
|
4319
|
-
return {
|
|
4320
|
-
...schema,
|
|
4321
|
-
description
|
|
4322
|
-
};
|
|
4323
|
-
}
|
|
4324
|
-
function propertiesToSchema(properties, description) {
|
|
4325
|
-
const schema = {
|
|
4326
|
-
type: "object",
|
|
4327
|
-
properties: {}
|
|
4328
|
-
};
|
|
4329
|
-
const required = [];
|
|
4330
|
-
for (const prop of properties) {
|
|
4331
|
-
const propType = prop.type;
|
|
4332
|
-
let propSchema;
|
|
4333
|
-
if (typeof propType === "string") {
|
|
4334
|
-
if (["string", "number", "boolean", "bigint", "null"].includes(propType)) {
|
|
4335
|
-
propSchema = { type: propType === "bigint" ? "string" : propType };
|
|
4336
|
-
} else {
|
|
4337
|
-
propSchema = { type: propType };
|
|
4338
|
-
}
|
|
4339
|
-
} else if (propType && typeof propType === "object") {
|
|
4340
|
-
propSchema = propType;
|
|
4341
|
-
} else {
|
|
4342
|
-
propSchema = { type: "any" };
|
|
4343
|
-
}
|
|
4344
|
-
if (prop.description && typeof propSchema === "object") {
|
|
4345
|
-
propSchema = withDescription(propSchema, prop.description);
|
|
4346
|
-
}
|
|
4347
|
-
schema.properties[prop.name] = propSchema;
|
|
4348
|
-
if (!prop.optional) {
|
|
4349
|
-
required.push(prop.name);
|
|
4350
|
-
}
|
|
4351
|
-
}
|
|
4352
|
-
if (required.length > 0) {
|
|
4353
|
-
schema.required = required;
|
|
4354
|
-
}
|
|
4355
|
-
if (description) {
|
|
4356
|
-
return withDescription(schema, description);
|
|
4357
|
-
}
|
|
4358
|
-
return schema;
|
|
4359
|
-
}
|
|
4360
|
-
var _formatTypeReference2 = null;
|
|
4361
|
-
function setSchemaBuilderFormatTypeReference(fn) {
|
|
4362
|
-
_formatTypeReference2 = fn;
|
|
4363
|
-
}
|
|
4364
|
-
function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName) {
|
|
4365
|
-
if (ts.isParenthesizedTypeNode(node)) {
|
|
4366
|
-
return buildSchemaFromTypeNode(node.type, typeChecker, typeRefs, referencedTypes, functionDoc ?? null, parentParamName);
|
|
4367
|
-
}
|
|
4368
|
-
if (ts.isIntersectionTypeNode(node)) {
|
|
4369
|
-
const schemas = node.types.map((type) => buildSchemaFromTypeNode(type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName));
|
|
4370
|
-
return { allOf: schemas };
|
|
4588
|
+
if (ts.isIntersectionTypeNode(node)) {
|
|
4589
|
+
const schemas = node.types.map((type) => buildSchemaFromTypeNode(type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName));
|
|
4590
|
+
return { allOf: schemas };
|
|
4371
4591
|
}
|
|
4372
4592
|
if (ts.isUnionTypeNode(node)) {
|
|
4373
4593
|
const schemas = node.types.map((type) => buildSchemaFromTypeNode(type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName));
|
|
@@ -4390,7 +4610,7 @@ function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, f
|
|
|
4390
4610
|
let schema2 = "any";
|
|
4391
4611
|
if (member.type) {
|
|
4392
4612
|
const memberType = typeChecker.getTypeFromTypeNode(member.type);
|
|
4393
|
-
const formatted =
|
|
4613
|
+
const formatted = _formatTypeReference ? _formatTypeReference(memberType, typeChecker, typeRefs, referencedTypes) : { type: "any" };
|
|
4394
4614
|
if (typeof formatted === "string") {
|
|
4395
4615
|
if (formatted === "any") {
|
|
4396
4616
|
schema2 = buildSchemaFromTypeNode(member.type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName);
|
|
@@ -4501,75 +4721,328 @@ function findDiscriminatorProperty(unionTypes, typeChecker) {
|
|
|
4501
4721
|
} else if (propType.isNumberLiteral()) {
|
|
4502
4722
|
propValues.set(prop.getName(), propType.value);
|
|
4503
4723
|
}
|
|
4504
|
-
} catch {}
|
|
4724
|
+
} catch {}
|
|
4725
|
+
}
|
|
4726
|
+
memberProps.push(propValues);
|
|
4727
|
+
}
|
|
4728
|
+
if (memberProps.length < 2) {
|
|
4729
|
+
return;
|
|
4730
|
+
}
|
|
4731
|
+
const firstMember = memberProps[0];
|
|
4732
|
+
for (const [propName, firstValue] of firstMember) {
|
|
4733
|
+
const values = new Set([firstValue]);
|
|
4734
|
+
let isDiscriminator = true;
|
|
4735
|
+
for (let i = 1;i < memberProps.length; i++) {
|
|
4736
|
+
const value = memberProps[i].get(propName);
|
|
4737
|
+
if (value === undefined) {
|
|
4738
|
+
isDiscriminator = false;
|
|
4739
|
+
break;
|
|
4740
|
+
}
|
|
4741
|
+
if (values.has(value)) {
|
|
4742
|
+
isDiscriminator = false;
|
|
4743
|
+
break;
|
|
4744
|
+
}
|
|
4745
|
+
values.add(value);
|
|
4746
|
+
}
|
|
4747
|
+
if (isDiscriminator) {
|
|
4748
|
+
return propName;
|
|
4749
|
+
}
|
|
4750
|
+
}
|
|
4751
|
+
return;
|
|
4752
|
+
}
|
|
4753
|
+
function schemaIsAny(schema) {
|
|
4754
|
+
if (typeof schema === "string") {
|
|
4755
|
+
return schema === "any";
|
|
4756
|
+
}
|
|
4757
|
+
if ("type" in schema && schema.type === "any" && Object.keys(schema).length === 1) {
|
|
4758
|
+
return true;
|
|
4759
|
+
}
|
|
4760
|
+
return false;
|
|
4761
|
+
}
|
|
4762
|
+
function schemasAreEqual(left, right) {
|
|
4763
|
+
if (typeof left !== typeof right) {
|
|
4764
|
+
return false;
|
|
4765
|
+
}
|
|
4766
|
+
if (typeof left === "string" && typeof right === "string") {
|
|
4767
|
+
return left === right;
|
|
4768
|
+
}
|
|
4769
|
+
if (left == null || right == null) {
|
|
4770
|
+
return left === right;
|
|
4771
|
+
}
|
|
4772
|
+
const normalize = (value) => {
|
|
4773
|
+
if (Array.isArray(value)) {
|
|
4774
|
+
return value.map((item) => normalize(item));
|
|
4775
|
+
}
|
|
4776
|
+
if (value && typeof value === "object") {
|
|
4777
|
+
const sortedEntries = Object.entries(value).map(([key, val]) => [key, normalize(val)]).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
|
|
4778
|
+
return Object.fromEntries(sortedEntries);
|
|
4779
|
+
}
|
|
4780
|
+
return value;
|
|
4781
|
+
};
|
|
4782
|
+
return JSON.stringify(normalize(left)) === JSON.stringify(normalize(right));
|
|
4783
|
+
}
|
|
4784
|
+
function deduplicateSchemas(schemas) {
|
|
4785
|
+
const result = [];
|
|
4786
|
+
for (const schema of schemas) {
|
|
4787
|
+
const isDuplicate = result.some((existing) => schemasAreEqual(existing, schema));
|
|
4788
|
+
if (!isDuplicate) {
|
|
4789
|
+
result.push(schema);
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4792
|
+
return result;
|
|
4793
|
+
}
|
|
4794
|
+
// src/utils/typebox-handler.ts
|
|
4795
|
+
var TYPEBOX_PRIMITIVE_MAP = {
|
|
4796
|
+
TString: { type: "string" },
|
|
4797
|
+
TNumber: { type: "number" },
|
|
4798
|
+
TBoolean: { type: "boolean" },
|
|
4799
|
+
TInteger: { type: "integer" },
|
|
4800
|
+
TNull: { type: "null" },
|
|
4801
|
+
TAny: {},
|
|
4802
|
+
TUnknown: {},
|
|
4803
|
+
TNever: { not: {} },
|
|
4804
|
+
TVoid: { type: "null" },
|
|
4805
|
+
TUndefined: { type: "null" }
|
|
4806
|
+
};
|
|
4807
|
+
function isTypeBoxSchemaType(symbolName) {
|
|
4808
|
+
return /^T[A-Z][a-zA-Z]*$/.test(symbolName);
|
|
4809
|
+
}
|
|
4810
|
+
function isInternalProperty(name) {
|
|
4811
|
+
return name.startsWith("__@");
|
|
4812
|
+
}
|
|
4813
|
+
function isTypeBoxOptionalMarker(type) {
|
|
4814
|
+
const props = type.getProperties();
|
|
4815
|
+
if (props.length !== 1)
|
|
4816
|
+
return false;
|
|
4817
|
+
return isInternalProperty(props[0].getName());
|
|
4818
|
+
}
|
|
4819
|
+
function unwrapTypeBoxOptional(type) {
|
|
4820
|
+
if (!type.isIntersection()) {
|
|
4821
|
+
return { innerTypes: [type], isOptional: false };
|
|
4822
|
+
}
|
|
4823
|
+
const intersectionType = type;
|
|
4824
|
+
const filtered = intersectionType.types.filter((t) => !isTypeBoxOptionalMarker(t));
|
|
4825
|
+
const hadMarker = filtered.length < intersectionType.types.length;
|
|
4826
|
+
return { innerTypes: filtered, isOptional: hadMarker };
|
|
4827
|
+
}
|
|
4828
|
+
function getPropertyType(prop, parentType, typeChecker) {
|
|
4829
|
+
if (prop.valueDeclaration) {
|
|
4830
|
+
return typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
|
|
4831
|
+
}
|
|
4832
|
+
const propType = typeChecker.getTypeOfPropertyOfType(parentType, prop.getName());
|
|
4833
|
+
if (propType) {
|
|
4834
|
+
return propType;
|
|
4835
|
+
}
|
|
4836
|
+
const decl = prop.declarations?.[0];
|
|
4837
|
+
if (decl) {
|
|
4838
|
+
return typeChecker.getTypeOfSymbolAtLocation(prop, decl);
|
|
4839
|
+
}
|
|
4840
|
+
return typeChecker.getAnyType();
|
|
4841
|
+
}
|
|
4842
|
+
var _formatTypeReference2 = null;
|
|
4843
|
+
function setFormatTypeReference(fn) {
|
|
4844
|
+
_formatTypeReference2 = fn;
|
|
4845
|
+
}
|
|
4846
|
+
function formatTypeReferenceInternal(type, typeChecker, typeRefs, referencedTypes, visited, depth, maxDepth, typeIds) {
|
|
4847
|
+
if (_formatTypeReference2) {
|
|
4848
|
+
return _formatTypeReference2(type, typeChecker, typeRefs, referencedTypes, visited, depth, maxDepth, typeIds);
|
|
4849
|
+
}
|
|
4850
|
+
return { type: "object" };
|
|
4851
|
+
}
|
|
4852
|
+
function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visited, depth = 0, maxDepth = DEFAULT_MAX_TYPE_DEPTH, typeIds) {
|
|
4853
|
+
if (depth > maxDepth) {
|
|
4854
|
+
return { type: "unknown" };
|
|
4855
|
+
}
|
|
4856
|
+
const symbol = type.getSymbol();
|
|
4857
|
+
if (!symbol)
|
|
4858
|
+
return null;
|
|
4859
|
+
const symbolName = symbol.getName();
|
|
4860
|
+
if (TYPEBOX_PRIMITIVE_MAP[symbolName]) {
|
|
4861
|
+
return { ...TYPEBOX_PRIMITIVE_MAP[symbolName] };
|
|
4862
|
+
}
|
|
4863
|
+
const objectType = type;
|
|
4864
|
+
if (!(objectType.objectFlags & ts.ObjectFlags.Reference)) {
|
|
4865
|
+
return null;
|
|
4866
|
+
}
|
|
4867
|
+
const typeRef = type;
|
|
4868
|
+
const typeArgs = typeRef.typeArguments;
|
|
4869
|
+
switch (symbolName) {
|
|
4870
|
+
case "TObject": {
|
|
4871
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
4872
|
+
return { type: "object" };
|
|
4873
|
+
}
|
|
4874
|
+
const propsType = typeArgs[0];
|
|
4875
|
+
const properties = {};
|
|
4876
|
+
const required = [];
|
|
4877
|
+
for (const prop of propsType.getProperties()) {
|
|
4878
|
+
const propName = prop.getName();
|
|
4879
|
+
if (isInternalProperty(propName)) {
|
|
4880
|
+
continue;
|
|
4881
|
+
}
|
|
4882
|
+
const propType = getPropertyType(prop, propsType, typeChecker);
|
|
4883
|
+
const propSymbol = propType.getSymbol();
|
|
4884
|
+
const propSymbolName = propSymbol?.getName();
|
|
4885
|
+
if (propSymbolName && typeRefs.has(propSymbolName)) {
|
|
4886
|
+
properties[propName] = { $ref: `#/types/${propSymbolName}` };
|
|
4887
|
+
} else if (propSymbolName && isTypeBoxSchemaType(propSymbolName)) {
|
|
4888
|
+
const nested = formatTypeBoxSchema(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
4889
|
+
properties[propName] = nested ?? { type: "object" };
|
|
4890
|
+
} else {
|
|
4891
|
+
properties[propName] = formatTypeReferenceInternal(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
4892
|
+
}
|
|
4893
|
+
const { isOptional } = unwrapTypeBoxOptional(propType);
|
|
4894
|
+
if (propSymbolName !== "TOptional" && !isOptional) {
|
|
4895
|
+
required.push(propName);
|
|
4896
|
+
}
|
|
4897
|
+
}
|
|
4898
|
+
const schema = { type: "object", properties };
|
|
4899
|
+
if (required.length > 0) {
|
|
4900
|
+
schema.required = required;
|
|
4901
|
+
}
|
|
4902
|
+
return schema;
|
|
4903
|
+
}
|
|
4904
|
+
case "TArray": {
|
|
4905
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
4906
|
+
return { type: "array" };
|
|
4907
|
+
}
|
|
4908
|
+
const itemType = typeArgs[0];
|
|
4909
|
+
const itemSymbol = itemType.getSymbol();
|
|
4910
|
+
const itemSymbolName = itemSymbol?.getName();
|
|
4911
|
+
let items;
|
|
4912
|
+
if (itemSymbolName && typeRefs.has(itemSymbolName)) {
|
|
4913
|
+
items = { $ref: `#/types/${itemSymbolName}` };
|
|
4914
|
+
} else if (itemSymbolName && isTypeBoxSchemaType(itemSymbolName)) {
|
|
4915
|
+
items = formatTypeBoxSchema(itemType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
4916
|
+
type: "object"
|
|
4917
|
+
};
|
|
4918
|
+
} else {
|
|
4919
|
+
items = formatTypeReferenceInternal(itemType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
4920
|
+
}
|
|
4921
|
+
return { type: "array", items };
|
|
4922
|
+
}
|
|
4923
|
+
case "TUnion": {
|
|
4924
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
4925
|
+
return { anyOf: [] };
|
|
4926
|
+
}
|
|
4927
|
+
const tupleType = typeArgs[0];
|
|
4928
|
+
const members = [];
|
|
4929
|
+
if (tupleType.isUnion()) {
|
|
4930
|
+
for (const memberType of tupleType.types) {
|
|
4931
|
+
const memberSymbol = memberType.getSymbol();
|
|
4932
|
+
const memberSymbolName = memberSymbol?.getName();
|
|
4933
|
+
if (memberSymbolName && typeRefs.has(memberSymbolName)) {
|
|
4934
|
+
members.push({ $ref: `#/types/${memberSymbolName}` });
|
|
4935
|
+
} else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
|
|
4936
|
+
members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
4937
|
+
type: "object"
|
|
4938
|
+
});
|
|
4939
|
+
} else {
|
|
4940
|
+
members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4943
|
+
} else if (tupleType.typeArguments) {
|
|
4944
|
+
for (const memberType of tupleType.typeArguments) {
|
|
4945
|
+
const memberSymbol = memberType.getSymbol();
|
|
4946
|
+
const memberSymbolName = memberSymbol?.getName();
|
|
4947
|
+
if (memberSymbolName && typeRefs.has(memberSymbolName)) {
|
|
4948
|
+
members.push({ $ref: `#/types/${memberSymbolName}` });
|
|
4949
|
+
} else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
|
|
4950
|
+
members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
4951
|
+
type: "object"
|
|
4952
|
+
});
|
|
4953
|
+
} else {
|
|
4954
|
+
members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
|
|
4955
|
+
}
|
|
4956
|
+
}
|
|
4957
|
+
}
|
|
4958
|
+
return { anyOf: members };
|
|
4959
|
+
}
|
|
4960
|
+
case "TIntersect": {
|
|
4961
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
4962
|
+
return { allOf: [] };
|
|
4963
|
+
}
|
|
4964
|
+
const tupleType = typeArgs[0];
|
|
4965
|
+
const members = [];
|
|
4966
|
+
if (tupleType.typeArguments) {
|
|
4967
|
+
for (const memberType of tupleType.typeArguments) {
|
|
4968
|
+
const memberSymbol = memberType.getSymbol();
|
|
4969
|
+
const memberSymbolName = memberSymbol?.getName();
|
|
4970
|
+
if (memberSymbolName && typeRefs.has(memberSymbolName)) {
|
|
4971
|
+
members.push({ $ref: `#/types/${memberSymbolName}` });
|
|
4972
|
+
} else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
|
|
4973
|
+
members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
4974
|
+
type: "object"
|
|
4975
|
+
});
|
|
4976
|
+
} else {
|
|
4977
|
+
members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
|
|
4978
|
+
}
|
|
4979
|
+
}
|
|
4980
|
+
}
|
|
4981
|
+
return { allOf: members };
|
|
4505
4982
|
}
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
return;
|
|
4510
|
-
}
|
|
4511
|
-
const firstMember = memberProps[0];
|
|
4512
|
-
for (const [propName, firstValue] of firstMember) {
|
|
4513
|
-
const values = new Set([firstValue]);
|
|
4514
|
-
let isDiscriminator = true;
|
|
4515
|
-
for (let i = 1;i < memberProps.length; i++) {
|
|
4516
|
-
const value = memberProps[i].get(propName);
|
|
4517
|
-
if (value === undefined) {
|
|
4518
|
-
isDiscriminator = false;
|
|
4519
|
-
break;
|
|
4983
|
+
case "TOptional": {
|
|
4984
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
4985
|
+
return {};
|
|
4520
4986
|
}
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4987
|
+
const innerType = typeArgs[0];
|
|
4988
|
+
const innerSymbol = innerType.getSymbol();
|
|
4989
|
+
const innerSymbolName = innerSymbol?.getName();
|
|
4990
|
+
if (innerSymbolName && typeRefs.has(innerSymbolName)) {
|
|
4991
|
+
return { $ref: `#/types/${innerSymbolName}` };
|
|
4992
|
+
} else if (innerSymbolName && isTypeBoxSchemaType(innerSymbolName)) {
|
|
4993
|
+
return formatTypeBoxSchema(innerType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
4994
|
+
type: "object"
|
|
4995
|
+
};
|
|
4524
4996
|
}
|
|
4525
|
-
|
|
4526
|
-
}
|
|
4527
|
-
if (isDiscriminator) {
|
|
4528
|
-
return propName;
|
|
4997
|
+
return formatTypeReferenceInternal(innerType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
4529
4998
|
}
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
}
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
}
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
return false;
|
|
4545
|
-
}
|
|
4546
|
-
if (typeof left === "string" && typeof right === "string") {
|
|
4547
|
-
return left === right;
|
|
4548
|
-
}
|
|
4549
|
-
if (left == null || right == null) {
|
|
4550
|
-
return left === right;
|
|
4551
|
-
}
|
|
4552
|
-
const normalize = (value) => {
|
|
4553
|
-
if (Array.isArray(value)) {
|
|
4554
|
-
return value.map((item) => normalize(item));
|
|
4999
|
+
case "TLiteral": {
|
|
5000
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
5001
|
+
return { enum: [] };
|
|
5002
|
+
}
|
|
5003
|
+
const literalType = typeArgs[0];
|
|
5004
|
+
if (literalType.isLiteral()) {
|
|
5005
|
+
const value = literalType.value;
|
|
5006
|
+
return { enum: [value] };
|
|
5007
|
+
}
|
|
5008
|
+
const literalStr = typeChecker.typeToString(literalType);
|
|
5009
|
+
if (literalStr.startsWith('"') && literalStr.endsWith('"')) {
|
|
5010
|
+
return { enum: [literalStr.slice(1, -1)] };
|
|
5011
|
+
}
|
|
5012
|
+
return { enum: [literalStr] };
|
|
4555
5013
|
}
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
5014
|
+
case "TRecord": {
|
|
5015
|
+
if (!typeArgs || typeArgs.length < 2) {
|
|
5016
|
+
return { type: "object", additionalProperties: true };
|
|
5017
|
+
}
|
|
5018
|
+
const valueType = typeArgs[1];
|
|
5019
|
+
const valueSymbol = valueType.getSymbol();
|
|
5020
|
+
const valueSymbolName = valueSymbol?.getName();
|
|
5021
|
+
let additionalProperties;
|
|
5022
|
+
if (valueSymbolName && typeRefs.has(valueSymbolName)) {
|
|
5023
|
+
additionalProperties = { $ref: `#/types/${valueSymbolName}` };
|
|
5024
|
+
} else if (valueSymbolName && isTypeBoxSchemaType(valueSymbolName)) {
|
|
5025
|
+
additionalProperties = formatTypeBoxSchema(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? true;
|
|
5026
|
+
} else {
|
|
5027
|
+
additionalProperties = formatTypeReferenceInternal(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
5028
|
+
}
|
|
5029
|
+
return { type: "object", additionalProperties };
|
|
4559
5030
|
}
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
}
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
5031
|
+
case "TRef": {
|
|
5032
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
5033
|
+
return { $ref: "#/types/unknown" };
|
|
5034
|
+
}
|
|
5035
|
+
const refType = typeArgs[0];
|
|
5036
|
+
const refSymbol = refType.getSymbol();
|
|
5037
|
+
const refSymbolName = refSymbol?.getName();
|
|
5038
|
+
if (refSymbolName) {
|
|
5039
|
+
return { $ref: `#/types/${refSymbolName}` };
|
|
5040
|
+
}
|
|
5041
|
+
return { type: "object" };
|
|
4570
5042
|
}
|
|
5043
|
+
default:
|
|
5044
|
+
return null;
|
|
4571
5045
|
}
|
|
4572
|
-
return result;
|
|
4573
5046
|
}
|
|
4574
5047
|
|
|
4575
5048
|
// src/utils/type-formatter.ts
|
|
@@ -6509,9 +6982,52 @@ function serializeVariable(declaration, symbol, context) {
|
|
|
6509
6982
|
}
|
|
6510
6983
|
const typeRefs = typeRegistry.getTypeRefs();
|
|
6511
6984
|
const referencedTypes = typeRegistry.getReferencedTypes();
|
|
6985
|
+
const symbolName = symbol.getName();
|
|
6986
|
+
const standardSchema = context.detectedSchemas?.get(symbolName);
|
|
6987
|
+
if (standardSchema) {
|
|
6988
|
+
return {
|
|
6989
|
+
id: symbolName,
|
|
6990
|
+
name: symbolName,
|
|
6991
|
+
...metadata,
|
|
6992
|
+
kind: "variable",
|
|
6993
|
+
deprecated: isSymbolDeprecated(symbol),
|
|
6994
|
+
schema: standardSchema.schema,
|
|
6995
|
+
description,
|
|
6996
|
+
source: getSourceLocation(declaration),
|
|
6997
|
+
tags: [
|
|
6998
|
+
...parsedDoc?.tags ?? [],
|
|
6999
|
+
{ name: "schemaLibrary", text: standardSchema.vendor },
|
|
7000
|
+
{ name: "schemaSource", text: "standard-schema" }
|
|
7001
|
+
],
|
|
7002
|
+
examples: parsedDoc?.examples
|
|
7003
|
+
};
|
|
7004
|
+
}
|
|
7005
|
+
if (isSchemaType(variableType, checker)) {
|
|
7006
|
+
const schemaResult = extractSchemaType(variableType, checker);
|
|
7007
|
+
if (schemaResult?.outputType) {
|
|
7008
|
+
collectReferencedTypes(schemaResult.outputType, checker, referencedTypes);
|
|
7009
|
+
const outputTypeRef = formatTypeReference(schemaResult.outputType, checker, typeRefs, referencedTypes);
|
|
7010
|
+
return {
|
|
7011
|
+
id: symbolName,
|
|
7012
|
+
name: symbolName,
|
|
7013
|
+
...metadata,
|
|
7014
|
+
kind: "variable",
|
|
7015
|
+
deprecated: isSymbolDeprecated(symbol),
|
|
7016
|
+
type: outputTypeRef,
|
|
7017
|
+
description,
|
|
7018
|
+
source: getSourceLocation(declaration),
|
|
7019
|
+
tags: [
|
|
7020
|
+
...parsedDoc?.tags ?? [],
|
|
7021
|
+
{ name: "schemaLibrary", text: schemaResult.adapter.id },
|
|
7022
|
+
{ name: "schemaSource", text: "static-ast" }
|
|
7023
|
+
],
|
|
7024
|
+
examples: parsedDoc?.examples
|
|
7025
|
+
};
|
|
7026
|
+
}
|
|
7027
|
+
}
|
|
6512
7028
|
return {
|
|
6513
|
-
id:
|
|
6514
|
-
name:
|
|
7029
|
+
id: symbolName,
|
|
7030
|
+
name: symbolName,
|
|
6515
7031
|
...metadata,
|
|
6516
7032
|
kind: "variable",
|
|
6517
7033
|
deprecated: isSymbolDeprecated(symbol),
|
|
@@ -6597,9 +7113,9 @@ function createDefaultGenerationInfo(entryFile) {
|
|
|
6597
7113
|
}
|
|
6598
7114
|
function buildOpenPkgSpec(context, resolveExternalTypes, generation) {
|
|
6599
7115
|
const { baseDir, checker: typeChecker, sourceFile, program, entryFile } = context;
|
|
6600
|
-
const packageJsonPath =
|
|
6601
|
-
const packageJson =
|
|
6602
|
-
const generationInfo = generation ?? createDefaultGenerationInfo(
|
|
7116
|
+
const packageJsonPath = path11.join(baseDir, "package.json");
|
|
7117
|
+
const packageJson = fs10.existsSync(packageJsonPath) ? JSON.parse(fs10.readFileSync(packageJsonPath, "utf-8")) : {};
|
|
7118
|
+
const generationInfo = generation ?? createDefaultGenerationInfo(path11.relative(baseDir, entryFile));
|
|
6603
7119
|
const spec = {
|
|
6604
7120
|
$schema: SCHEMA_URL,
|
|
6605
7121
|
openpkg: SCHEMA_VERSION,
|
|
@@ -6619,7 +7135,8 @@ function buildOpenPkgSpec(context, resolveExternalTypes, generation) {
|
|
|
6619
7135
|
const serializerContext = {
|
|
6620
7136
|
checker: typeChecker,
|
|
6621
7137
|
typeRegistry,
|
|
6622
|
-
maxTypeDepth: context.options.maxDepth
|
|
7138
|
+
maxTypeDepth: context.options.maxDepth,
|
|
7139
|
+
detectedSchemas: context.detectedSchemas
|
|
6623
7140
|
};
|
|
6624
7141
|
const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
|
|
6625
7142
|
if (!moduleSymbol) {
|
|
@@ -6797,11 +7314,11 @@ function deriveImportPath(sourceFile, baseDir) {
|
|
|
6797
7314
|
if (!sourceFile) {
|
|
6798
7315
|
return;
|
|
6799
7316
|
}
|
|
6800
|
-
const
|
|
6801
|
-
if (!
|
|
7317
|
+
const relative5 = path11.relative(baseDir, sourceFile);
|
|
7318
|
+
if (!relative5 || relative5.startsWith("..")) {
|
|
6802
7319
|
return;
|
|
6803
7320
|
}
|
|
6804
|
-
const normalized =
|
|
7321
|
+
const normalized = relative5.replace(/\\/g, "/");
|
|
6805
7322
|
const withoutExt = stripExtensions(normalized);
|
|
6806
7323
|
if (!withoutExt) {
|
|
6807
7324
|
return;
|
|
@@ -6840,11 +7357,11 @@ function withExportName(entry, exportName) {
|
|
|
6840
7357
|
function findNearestPackageJson(startDir) {
|
|
6841
7358
|
let current = startDir;
|
|
6842
7359
|
while (true) {
|
|
6843
|
-
const candidate =
|
|
6844
|
-
if (
|
|
7360
|
+
const candidate = path12.join(current, "package.json");
|
|
7361
|
+
if (fs11.existsSync(candidate)) {
|
|
6845
7362
|
return candidate;
|
|
6846
7363
|
}
|
|
6847
|
-
const parent =
|
|
7364
|
+
const parent = path12.dirname(current);
|
|
6848
7365
|
if (parent === current) {
|
|
6849
7366
|
return;
|
|
6850
7367
|
}
|
|
@@ -6872,11 +7389,11 @@ function canResolveExternalModules(program, baseDir) {
|
|
|
6872
7389
|
function hasNodeModulesDirectoryFallback(startDir) {
|
|
6873
7390
|
let current = startDir;
|
|
6874
7391
|
while (true) {
|
|
6875
|
-
const candidate =
|
|
6876
|
-
if (
|
|
7392
|
+
const candidate = path12.join(current, "node_modules");
|
|
7393
|
+
if (fs11.existsSync(candidate)) {
|
|
6877
7394
|
return true;
|
|
6878
7395
|
}
|
|
6879
|
-
const parent =
|
|
7396
|
+
const parent = path12.dirname(current);
|
|
6880
7397
|
if (parent === current) {
|
|
6881
7398
|
break;
|
|
6882
7399
|
}
|
|
@@ -6977,6 +7494,17 @@ function runAnalysis(input, generationInput) {
|
|
|
6977
7494
|
issues: generationIssues
|
|
6978
7495
|
} : undefined;
|
|
6979
7496
|
const spec = buildOpenPkgSpec(context, resolveExternalTypes, generation);
|
|
7497
|
+
if (input.detectedSchemas && input.detectedSchemas.size > 0 && generation) {
|
|
7498
|
+
const vendors = new Set;
|
|
7499
|
+
for (const entry of input.detectedSchemas.values()) {
|
|
7500
|
+
vendors.add(entry.vendor);
|
|
7501
|
+
}
|
|
7502
|
+
generation.analysis.schemaExtraction = {
|
|
7503
|
+
method: "hybrid",
|
|
7504
|
+
runtimeCount: input.detectedSchemas.size,
|
|
7505
|
+
vendors: Array.from(vendors)
|
|
7506
|
+
};
|
|
7507
|
+
}
|
|
6980
7508
|
const danglingRefs = collectDanglingRefs(spec);
|
|
6981
7509
|
for (const ref of danglingRefs) {
|
|
6982
7510
|
const issue = {
|
|
@@ -7021,11 +7549,27 @@ function runAnalysis(input, generationInput) {
|
|
|
7021
7549
|
|
|
7022
7550
|
// src/extractor.ts
|
|
7023
7551
|
async function extractPackageSpec(entryFile, packageDir, content, options) {
|
|
7552
|
+
const baseDir = packageDir ?? path13.dirname(entryFile);
|
|
7553
|
+
const schemaMode = options?.schemaExtraction ?? "static";
|
|
7554
|
+
let detectedSchemas;
|
|
7555
|
+
if (schemaMode === "runtime" || schemaMode === "hybrid") {
|
|
7556
|
+
const extraction = await extractStandardSchemasFromProject(entryFile, baseDir);
|
|
7557
|
+
if (extraction.schemas.size > 0) {
|
|
7558
|
+
detectedSchemas = new Map;
|
|
7559
|
+
for (const [name, result2] of extraction.schemas) {
|
|
7560
|
+
detectedSchemas.set(name, {
|
|
7561
|
+
schema: result2.outputSchema,
|
|
7562
|
+
vendor: result2.vendor
|
|
7563
|
+
});
|
|
7564
|
+
}
|
|
7565
|
+
}
|
|
7566
|
+
}
|
|
7024
7567
|
const result = runAnalysis({
|
|
7025
7568
|
entryFile,
|
|
7026
7569
|
packageDir,
|
|
7027
7570
|
content,
|
|
7028
|
-
options
|
|
7571
|
+
options,
|
|
7572
|
+
detectedSchemas
|
|
7029
7573
|
});
|
|
7030
7574
|
return result.spec;
|
|
7031
7575
|
}
|
|
@@ -7118,16 +7662,33 @@ async function fetchSpecFromGitHub(parsed) {
|
|
|
7118
7662
|
}
|
|
7119
7663
|
return null;
|
|
7120
7664
|
}
|
|
7121
|
-
async function fetchSpec(owner, repo,
|
|
7122
|
-
|
|
7665
|
+
async function fetchSpec(owner, repo, branchOrOptions = "main") {
|
|
7666
|
+
const options = typeof branchOrOptions === "string" ? { ref: branchOrOptions, path: "openpkg.json" } : { ref: branchOrOptions.ref ?? "main", path: branchOrOptions.path ?? "openpkg.json" };
|
|
7667
|
+
const parsed = { owner, repo, ref: options.ref };
|
|
7668
|
+
return fetchSpecFromGitHubWithPath(parsed, options.path);
|
|
7669
|
+
}
|
|
7670
|
+
async function fetchSpecFromGitHubWithPath(parsed, specPath = "openpkg.json") {
|
|
7671
|
+
const urls = [
|
|
7672
|
+
buildRawUrl(parsed, specPath),
|
|
7673
|
+
`https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/master/${specPath}`
|
|
7674
|
+
];
|
|
7675
|
+
for (const url of urls) {
|
|
7676
|
+
try {
|
|
7677
|
+
const response = await fetch(url);
|
|
7678
|
+
if (response.ok) {
|
|
7679
|
+
return await response.json();
|
|
7680
|
+
}
|
|
7681
|
+
} catch {}
|
|
7682
|
+
}
|
|
7683
|
+
return null;
|
|
7123
7684
|
}
|
|
7124
7685
|
// src/install/index.ts
|
|
7125
7686
|
var DEFAULT_FALLBACK_ORDER = ["bun", "npm"];
|
|
7126
|
-
async function installDependencies(
|
|
7687
|
+
async function installDependencies(fs12, cwd, runCommand2, options = {}) {
|
|
7127
7688
|
const { timeout = 180000, fallbackOrder = DEFAULT_FALLBACK_ORDER, onProgress } = options;
|
|
7128
7689
|
const errors = [];
|
|
7129
7690
|
onProgress?.({ stage: "installing", message: "Detecting package manager..." });
|
|
7130
|
-
const pmInfo = await detectPackageManager(
|
|
7691
|
+
const pmInfo = await detectPackageManager(fs12);
|
|
7131
7692
|
if (pmInfo.lockfile) {
|
|
7132
7693
|
onProgress?.({
|
|
7133
7694
|
stage: "installing",
|
|
@@ -7782,7 +8343,7 @@ function diffMemberChanges(oldSpec, newSpec, changedClassNames) {
|
|
|
7782
8343
|
memberName,
|
|
7783
8344
|
memberKind: getMemberKind(newMember),
|
|
7784
8345
|
changeType: "added",
|
|
7785
|
-
newSignature:
|
|
8346
|
+
newSignature: formatSignature2(newMember)
|
|
7786
8347
|
});
|
|
7787
8348
|
}
|
|
7788
8349
|
}
|
|
@@ -7794,7 +8355,7 @@ function diffMemberChanges(oldSpec, newSpec, changedClassNames) {
|
|
|
7794
8355
|
memberName,
|
|
7795
8356
|
memberKind: getMemberKind(oldMember),
|
|
7796
8357
|
changeType: "removed",
|
|
7797
|
-
oldSignature:
|
|
8358
|
+
oldSignature: formatSignature2(oldMember),
|
|
7798
8359
|
suggestion
|
|
7799
8360
|
});
|
|
7800
8361
|
}
|
|
@@ -7807,8 +8368,8 @@ function diffMemberChanges(oldSpec, newSpec, changedClassNames) {
|
|
|
7807
8368
|
memberName,
|
|
7808
8369
|
memberKind: getMemberKind(newMember),
|
|
7809
8370
|
changeType: "signature-changed",
|
|
7810
|
-
oldSignature:
|
|
7811
|
-
newSignature:
|
|
8371
|
+
oldSignature: formatSignature2(oldMember),
|
|
8372
|
+
newSignature: formatSignature2(newMember)
|
|
7812
8373
|
});
|
|
7813
8374
|
}
|
|
7814
8375
|
}
|
|
@@ -7857,7 +8418,7 @@ function getMemberKind(member) {
|
|
|
7857
8418
|
return "method";
|
|
7858
8419
|
}
|
|
7859
8420
|
}
|
|
7860
|
-
function
|
|
8421
|
+
function formatSignature2(member) {
|
|
7861
8422
|
if (!member.signatures?.length) {
|
|
7862
8423
|
return member.name ?? "";
|
|
7863
8424
|
}
|
|
@@ -8032,11 +8593,34 @@ function getDocsImpactSummary(diff) {
|
|
|
8032
8593
|
}
|
|
8033
8594
|
// src/openpkg.ts
|
|
8034
8595
|
import * as fsSync from "node:fs";
|
|
8035
|
-
import * as
|
|
8036
|
-
import * as
|
|
8596
|
+
import * as fs12 from "node:fs/promises";
|
|
8597
|
+
import * as path14 from "node:path";
|
|
8037
8598
|
|
|
8038
8599
|
// src/filtering/apply-filters.ts
|
|
8039
8600
|
var TYPE_REF_PREFIX = "#/types/";
|
|
8601
|
+
var getExportReleaseTag = (exp) => {
|
|
8602
|
+
const tags = exp.tags ?? [];
|
|
8603
|
+
for (const tag of tags) {
|
|
8604
|
+
const tagName = tag.name?.toLowerCase();
|
|
8605
|
+
if (tagName === "public")
|
|
8606
|
+
return "public";
|
|
8607
|
+
if (tagName === "beta")
|
|
8608
|
+
return "beta";
|
|
8609
|
+
if (tagName === "alpha")
|
|
8610
|
+
return "alpha";
|
|
8611
|
+
if (tagName === "internal")
|
|
8612
|
+
return "internal";
|
|
8613
|
+
}
|
|
8614
|
+
return;
|
|
8615
|
+
};
|
|
8616
|
+
var matchesVisibility = (exp, visibility) => {
|
|
8617
|
+
if (!visibility || visibility.length === 0) {
|
|
8618
|
+
return true;
|
|
8619
|
+
}
|
|
8620
|
+
const tag = getExportReleaseTag(exp);
|
|
8621
|
+
const effectiveTag = tag ?? "public";
|
|
8622
|
+
return visibility.includes(effectiveTag);
|
|
8623
|
+
};
|
|
8040
8624
|
var toLowerKey = (value) => value.trim().toLowerCase();
|
|
8041
8625
|
var buildLookupMap = (values) => {
|
|
8042
8626
|
const map = new Map;
|
|
@@ -8098,7 +8682,8 @@ var collectTypeRefs = (value, refs, seen = new Set) => {
|
|
|
8098
8682
|
var applyFilters = (spec, options) => {
|
|
8099
8683
|
const includeLookup = buildLookupMap(options.include);
|
|
8100
8684
|
const excludeLookup = buildLookupMap(options.exclude);
|
|
8101
|
-
|
|
8685
|
+
const visibility = options.visibility;
|
|
8686
|
+
if (includeLookup.size === 0 && excludeLookup.size === 0 && (!visibility || visibility.length === 0)) {
|
|
8102
8687
|
return { spec, diagnostics: [], changed: false };
|
|
8103
8688
|
}
|
|
8104
8689
|
const includeMatches = new Set;
|
|
@@ -8111,10 +8696,11 @@ var applyFilters = (spec, options) => {
|
|
|
8111
8696
|
const excludeMatch = matches(entry, excludeLookup);
|
|
8112
8697
|
const allowedByInclude = includeLookup.size === 0 || Boolean(includeMatch);
|
|
8113
8698
|
const allowedByExclude = !excludeMatch;
|
|
8699
|
+
const allowedByVisibility = matchesVisibility(entry, visibility);
|
|
8114
8700
|
if (includeMatch) {
|
|
8115
8701
|
includeMatches.add(includeMatch);
|
|
8116
8702
|
}
|
|
8117
|
-
if (allowedByInclude && allowedByExclude) {
|
|
8703
|
+
if (allowedByInclude && allowedByExclude && allowedByVisibility) {
|
|
8118
8704
|
keptExports.push(entry);
|
|
8119
8705
|
}
|
|
8120
8706
|
}
|
|
@@ -8216,14 +8802,14 @@ class DocCov {
|
|
|
8216
8802
|
this.options = normalizeDocCovOptions(options);
|
|
8217
8803
|
}
|
|
8218
8804
|
async analyze(code, fileName = "temp.ts", analyzeOptions = {}) {
|
|
8219
|
-
const resolvedFileName =
|
|
8220
|
-
const tempDir =
|
|
8805
|
+
const resolvedFileName = path14.resolve(fileName);
|
|
8806
|
+
const tempDir = path14.dirname(resolvedFileName);
|
|
8221
8807
|
const spec = await extractPackageSpec(resolvedFileName, tempDir, code, this.options);
|
|
8222
8808
|
return this.applySpecFilters(spec, analyzeOptions.filters).spec;
|
|
8223
8809
|
}
|
|
8224
8810
|
async analyzeFile(filePath, analyzeOptions = {}) {
|
|
8225
|
-
const resolvedPath =
|
|
8226
|
-
const content = await
|
|
8811
|
+
const resolvedPath = path14.resolve(filePath);
|
|
8812
|
+
const content = await fs12.readFile(resolvedPath, "utf-8");
|
|
8227
8813
|
const packageDir = resolvePackageDir(resolvedPath);
|
|
8228
8814
|
const spec = await extractPackageSpec(resolvedPath, packageDir, content, this.options);
|
|
8229
8815
|
return this.applySpecFilters(spec, analyzeOptions.filters).spec;
|
|
@@ -8232,13 +8818,16 @@ class DocCov {
|
|
|
8232
8818
|
return this.analyzeFile(entryPath, analyzeOptions);
|
|
8233
8819
|
}
|
|
8234
8820
|
async analyzeWithDiagnostics(code, fileName, analyzeOptions = {}) {
|
|
8235
|
-
const resolvedFileName =
|
|
8821
|
+
const resolvedFileName = path14.resolve(fileName ?? "temp.ts");
|
|
8236
8822
|
const packageDir = resolvePackageDir(resolvedFileName);
|
|
8823
|
+
const isRealFile = fileName && !fileName.includes("temp.ts");
|
|
8824
|
+
const detectedSchemas = isRealFile ? await this.detectSchemas(resolvedFileName, packageDir) : undefined;
|
|
8237
8825
|
const analysis = runAnalysis({
|
|
8238
8826
|
entryFile: resolvedFileName,
|
|
8239
8827
|
packageDir,
|
|
8240
8828
|
content: code,
|
|
8241
|
-
options: this.options
|
|
8829
|
+
options: this.options,
|
|
8830
|
+
detectedSchemas
|
|
8242
8831
|
}, analyzeOptions.generationInput);
|
|
8243
8832
|
const filterOutcome = this.applySpecFilters(analysis.spec, analyzeOptions.filters);
|
|
8244
8833
|
return {
|
|
@@ -8252,7 +8841,7 @@ class DocCov {
|
|
|
8252
8841
|
};
|
|
8253
8842
|
}
|
|
8254
8843
|
async analyzeFileWithDiagnostics(filePath, analyzeOptions = {}) {
|
|
8255
|
-
const resolvedPath =
|
|
8844
|
+
const resolvedPath = path14.resolve(filePath);
|
|
8256
8845
|
const packageDir = resolvePackageDir(resolvedPath);
|
|
8257
8846
|
const { useCache, resolveExternalTypes } = this.options;
|
|
8258
8847
|
if (useCache) {
|
|
@@ -8268,12 +8857,14 @@ class DocCov {
|
|
|
8268
8857
|
};
|
|
8269
8858
|
}
|
|
8270
8859
|
}
|
|
8271
|
-
const content = await
|
|
8860
|
+
const content = await fs12.readFile(resolvedPath, "utf-8");
|
|
8861
|
+
const detectedSchemas = await this.detectSchemas(resolvedPath, packageDir);
|
|
8272
8862
|
const analysis = runAnalysis({
|
|
8273
8863
|
entryFile: resolvedPath,
|
|
8274
8864
|
packageDir,
|
|
8275
8865
|
content,
|
|
8276
|
-
options: this.options
|
|
8866
|
+
options: this.options,
|
|
8867
|
+
detectedSchemas
|
|
8277
8868
|
}, analyzeOptions.generationInput);
|
|
8278
8869
|
const filterOutcome = this.applySpecFilters(analysis.spec, analyzeOptions.filters);
|
|
8279
8870
|
const metadata = this.normalizeMetadata(analysis.metadata);
|
|
@@ -8303,10 +8894,10 @@ class DocCov {
|
|
|
8303
8894
|
if (!packageJsonPath) {
|
|
8304
8895
|
return null;
|
|
8305
8896
|
}
|
|
8306
|
-
const
|
|
8897
|
+
const currentSourceFiles = this.getCurrentSourceFiles(entryFile, packageDir);
|
|
8307
8898
|
const cacheContext = {
|
|
8308
8899
|
entryFile,
|
|
8309
|
-
sourceFiles,
|
|
8900
|
+
sourceFiles: currentSourceFiles,
|
|
8310
8901
|
tsconfigPath,
|
|
8311
8902
|
packageJsonPath,
|
|
8312
8903
|
config: {
|
|
@@ -8318,6 +8909,7 @@ class DocCov {
|
|
|
8318
8909
|
if (!validation.valid) {
|
|
8319
8910
|
return null;
|
|
8320
8911
|
}
|
|
8912
|
+
const cachedSourceFiles = Object.keys(cache.hashes.sourceFiles).map((relativePath) => path14.resolve(cwd, relativePath));
|
|
8321
8913
|
return {
|
|
8322
8914
|
spec: cache.spec,
|
|
8323
8915
|
metadata: {
|
|
@@ -8326,10 +8918,18 @@ class DocCov {
|
|
|
8326
8918
|
packageJsonPath,
|
|
8327
8919
|
hasNodeModules: true,
|
|
8328
8920
|
resolveExternalTypes: cache.config.resolveExternalTypes,
|
|
8329
|
-
sourceFiles
|
|
8921
|
+
sourceFiles: cachedSourceFiles
|
|
8330
8922
|
}
|
|
8331
8923
|
};
|
|
8332
8924
|
}
|
|
8925
|
+
getCurrentSourceFiles(entryFile, baseDir) {
|
|
8926
|
+
try {
|
|
8927
|
+
const { program } = createProgram({ entryFile, baseDir });
|
|
8928
|
+
return program.getSourceFiles().filter((sf) => !sf.isDeclarationFile && sf.fileName.startsWith(baseDir)).map((sf) => sf.fileName);
|
|
8929
|
+
} catch {
|
|
8930
|
+
return [];
|
|
8931
|
+
}
|
|
8932
|
+
}
|
|
8333
8933
|
saveToCache(result, entryFile, metadata) {
|
|
8334
8934
|
const { cwd } = this.options;
|
|
8335
8935
|
if (!metadata.packageJsonPath) {
|
|
@@ -8352,11 +8952,11 @@ class DocCov {
|
|
|
8352
8952
|
findTsConfig(startDir) {
|
|
8353
8953
|
let current = startDir;
|
|
8354
8954
|
while (true) {
|
|
8355
|
-
const candidate =
|
|
8955
|
+
const candidate = path14.join(current, "tsconfig.json");
|
|
8356
8956
|
if (fsSync.existsSync(candidate)) {
|
|
8357
8957
|
return candidate;
|
|
8358
8958
|
}
|
|
8359
|
-
const parent =
|
|
8959
|
+
const parent = path14.dirname(current);
|
|
8360
8960
|
if (parent === current) {
|
|
8361
8961
|
return null;
|
|
8362
8962
|
}
|
|
@@ -8366,17 +8966,38 @@ class DocCov {
|
|
|
8366
8966
|
findPackageJson(startDir) {
|
|
8367
8967
|
let current = startDir;
|
|
8368
8968
|
while (true) {
|
|
8369
|
-
const candidate =
|
|
8969
|
+
const candidate = path14.join(current, "package.json");
|
|
8370
8970
|
if (fsSync.existsSync(candidate)) {
|
|
8371
8971
|
return candidate;
|
|
8372
8972
|
}
|
|
8373
|
-
const parent =
|
|
8973
|
+
const parent = path14.dirname(current);
|
|
8374
8974
|
if (parent === current) {
|
|
8375
8975
|
return null;
|
|
8376
8976
|
}
|
|
8377
8977
|
current = parent;
|
|
8378
8978
|
}
|
|
8379
8979
|
}
|
|
8980
|
+
async detectSchemas(entryFile, packageDir) {
|
|
8981
|
+
try {
|
|
8982
|
+
const result = await detectRuntimeSchemas({
|
|
8983
|
+
baseDir: packageDir,
|
|
8984
|
+
entryFile
|
|
8985
|
+
});
|
|
8986
|
+
if (result.schemas.size === 0) {
|
|
8987
|
+
return;
|
|
8988
|
+
}
|
|
8989
|
+
const detected = new Map;
|
|
8990
|
+
for (const [name, schema] of result.schemas) {
|
|
8991
|
+
detected.set(name, {
|
|
8992
|
+
schema: schema.schema,
|
|
8993
|
+
vendor: schema.vendor
|
|
8994
|
+
});
|
|
8995
|
+
}
|
|
8996
|
+
return detected;
|
|
8997
|
+
} catch {
|
|
8998
|
+
return;
|
|
8999
|
+
}
|
|
9000
|
+
}
|
|
8380
9001
|
normalizeDiagnostic(tsDiagnostic) {
|
|
8381
9002
|
const message = ts.flattenDiagnosticMessageText(tsDiagnostic.messageText, `
|
|
8382
9003
|
`);
|
|
@@ -8438,14 +9059,14 @@ async function analyzeFile(filePath, options = {}) {
|
|
|
8438
9059
|
return new DocCov().analyzeFile(filePath, options);
|
|
8439
9060
|
}
|
|
8440
9061
|
function resolvePackageDir(entryFile) {
|
|
8441
|
-
const fallbackDir =
|
|
9062
|
+
const fallbackDir = path14.dirname(entryFile);
|
|
8442
9063
|
let currentDir = fallbackDir;
|
|
8443
9064
|
while (true) {
|
|
8444
|
-
const candidate =
|
|
9065
|
+
const candidate = path14.join(currentDir, "package.json");
|
|
8445
9066
|
if (fsSync.existsSync(candidate)) {
|
|
8446
9067
|
return currentDir;
|
|
8447
9068
|
}
|
|
8448
|
-
const parentDir =
|
|
9069
|
+
const parentDir = path14.dirname(currentDir);
|
|
8449
9070
|
if (parentDir === currentDir) {
|
|
8450
9071
|
return fallbackDir;
|
|
8451
9072
|
}
|
|
@@ -8453,12 +9074,12 @@ function resolvePackageDir(entryFile) {
|
|
|
8453
9074
|
}
|
|
8454
9075
|
}
|
|
8455
9076
|
// src/resolve/index.ts
|
|
8456
|
-
import * as
|
|
8457
|
-
async function resolveTarget(
|
|
9077
|
+
import * as path15 from "node:path";
|
|
9078
|
+
async function resolveTarget(fs13, options) {
|
|
8458
9079
|
let targetDir = options.cwd;
|
|
8459
9080
|
let packageInfo;
|
|
8460
9081
|
if (options.package) {
|
|
8461
|
-
const mono = await detectMonorepo(
|
|
9082
|
+
const mono = await detectMonorepo(fs13);
|
|
8462
9083
|
if (!mono.isMonorepo) {
|
|
8463
9084
|
throw new Error("Not a monorepo. Remove --package flag for single-package repos.");
|
|
8464
9085
|
}
|
|
@@ -8467,21 +9088,21 @@ async function resolveTarget(fs11, options) {
|
|
|
8467
9088
|
const available = mono.packages.map((p) => p.name).join(", ");
|
|
8468
9089
|
throw new Error(`Package "${options.package}" not found. Available: ${available}`);
|
|
8469
9090
|
}
|
|
8470
|
-
targetDir =
|
|
9091
|
+
targetDir = path15.join(options.cwd, pkg.path);
|
|
8471
9092
|
packageInfo = pkg;
|
|
8472
9093
|
}
|
|
8473
9094
|
let entryFile;
|
|
8474
9095
|
let entryPointInfo;
|
|
8475
9096
|
if (!options.entry) {
|
|
8476
|
-
entryPointInfo = await detectEntryPoint(
|
|
8477
|
-
entryFile =
|
|
9097
|
+
entryPointInfo = await detectEntryPoint(fs13, getRelativePath(options.cwd, targetDir));
|
|
9098
|
+
entryFile = path15.join(targetDir, entryPointInfo.path);
|
|
8478
9099
|
} else {
|
|
8479
|
-
const explicitPath =
|
|
8480
|
-
const isDirectory = await isDir(
|
|
9100
|
+
const explicitPath = path15.resolve(targetDir, options.entry);
|
|
9101
|
+
const isDirectory = await isDir(fs13, getRelativePath(options.cwd, explicitPath));
|
|
8481
9102
|
if (isDirectory) {
|
|
8482
9103
|
targetDir = explicitPath;
|
|
8483
|
-
entryPointInfo = await detectEntryPoint(
|
|
8484
|
-
entryFile =
|
|
9104
|
+
entryPointInfo = await detectEntryPoint(fs13, getRelativePath(options.cwd, explicitPath));
|
|
9105
|
+
entryFile = path15.join(explicitPath, entryPointInfo.path);
|
|
8485
9106
|
} else {
|
|
8486
9107
|
entryFile = explicitPath;
|
|
8487
9108
|
entryPointInfo = {
|
|
@@ -8501,52 +9122,20 @@ async function resolveTarget(fs11, options) {
|
|
|
8501
9122
|
function getRelativePath(base, target) {
|
|
8502
9123
|
if (base === target)
|
|
8503
9124
|
return ".";
|
|
8504
|
-
const rel =
|
|
9125
|
+
const rel = path15.relative(base, target);
|
|
8505
9126
|
return rel || ".";
|
|
8506
9127
|
}
|
|
8507
|
-
async function isDir(
|
|
8508
|
-
const hasPackageJson = await
|
|
9128
|
+
async function isDir(fs13, relativePath) {
|
|
9129
|
+
const hasPackageJson = await fs13.exists(path15.join(relativePath, "package.json"));
|
|
8509
9130
|
if (hasPackageJson)
|
|
8510
9131
|
return true;
|
|
8511
9132
|
const commonEntryFiles = ["index.ts", "index.tsx", "src/index.ts", "main.ts"];
|
|
8512
9133
|
for (const entry of commonEntryFiles) {
|
|
8513
|
-
if (await
|
|
9134
|
+
if (await fs13.exists(path15.join(relativePath, entry))) {
|
|
8514
9135
|
return true;
|
|
8515
9136
|
}
|
|
8516
9137
|
}
|
|
8517
|
-
return !
|
|
8518
|
-
}
|
|
8519
|
-
// src/scan/summary.ts
|
|
8520
|
-
function extractSpecSummary(spec) {
|
|
8521
|
-
const exports = spec.exports ?? [];
|
|
8522
|
-
const undocumented = [];
|
|
8523
|
-
const drift = [];
|
|
8524
|
-
for (const exp of exports) {
|
|
8525
|
-
const docs = exp.docs;
|
|
8526
|
-
if (!docs)
|
|
8527
|
-
continue;
|
|
8528
|
-
const hasMissing = (docs.missing?.length ?? 0) > 0;
|
|
8529
|
-
const isPartial = (docs.coverageScore ?? 0) < 100;
|
|
8530
|
-
if (hasMissing || isPartial) {
|
|
8531
|
-
undocumented.push(exp.name);
|
|
8532
|
-
}
|
|
8533
|
-
for (const d of docs.drift ?? []) {
|
|
8534
|
-
drift.push({
|
|
8535
|
-
export: exp.name,
|
|
8536
|
-
type: d.type,
|
|
8537
|
-
issue: d.issue,
|
|
8538
|
-
suggestion: d.suggestion
|
|
8539
|
-
});
|
|
8540
|
-
}
|
|
8541
|
-
}
|
|
8542
|
-
return {
|
|
8543
|
-
coverage: spec.docs?.coverageScore ?? 0,
|
|
8544
|
-
exportCount: exports.length,
|
|
8545
|
-
typeCount: spec.types?.length ?? 0,
|
|
8546
|
-
driftCount: drift.length,
|
|
8547
|
-
undocumented,
|
|
8548
|
-
drift
|
|
8549
|
-
};
|
|
9138
|
+
return !path15.extname(relativePath);
|
|
8550
9139
|
}
|
|
8551
9140
|
// src/scan/github-context.ts
|
|
8552
9141
|
function parseGitHubUrl2(url) {
|
|
@@ -8557,10 +9146,14 @@ function parseGitHubUrl2(url) {
|
|
|
8557
9146
|
return null;
|
|
8558
9147
|
}
|
|
8559
9148
|
}
|
|
8560
|
-
async function fetchRawFile(owner, repo, ref,
|
|
8561
|
-
const url = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${
|
|
9149
|
+
async function fetchRawFile(owner, repo, ref, path16, authToken) {
|
|
9150
|
+
const url = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path16}`;
|
|
8562
9151
|
try {
|
|
8563
|
-
const
|
|
9152
|
+
const headers = {};
|
|
9153
|
+
if (authToken) {
|
|
9154
|
+
headers.Authorization = `Bearer ${authToken}`;
|
|
9155
|
+
}
|
|
9156
|
+
const response = await fetch(url, { headers });
|
|
8564
9157
|
if (response.ok) {
|
|
8565
9158
|
return await response.text();
|
|
8566
9159
|
}
|
|
@@ -8569,14 +9162,16 @@ async function fetchRawFile(owner, repo, ref, path13) {
|
|
|
8569
9162
|
return null;
|
|
8570
9163
|
}
|
|
8571
9164
|
}
|
|
8572
|
-
async function fetchRepoMetadata(owner, repo) {
|
|
9165
|
+
async function fetchRepoMetadata(owner, repo, authToken) {
|
|
8573
9166
|
const url = `https://api.github.com/repos/${owner}/${repo}`;
|
|
8574
|
-
const
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
9167
|
+
const headers = {
|
|
9168
|
+
Accept: "application/vnd.github.v3+json",
|
|
9169
|
+
"User-Agent": "DocCov-Scanner"
|
|
9170
|
+
};
|
|
9171
|
+
if (authToken) {
|
|
9172
|
+
headers.Authorization = `Bearer ${authToken}`;
|
|
9173
|
+
}
|
|
9174
|
+
const response = await fetch(url, { headers });
|
|
8580
9175
|
if (!response.ok) {
|
|
8581
9176
|
throw new Error(`Failed to fetch repository: ${response.status} ${response.statusText}`);
|
|
8582
9177
|
}
|
|
@@ -8591,7 +9186,7 @@ async function fetchRepoMetadata(owner, repo) {
|
|
|
8591
9186
|
isPrivate: data.private
|
|
8592
9187
|
};
|
|
8593
9188
|
}
|
|
8594
|
-
async function detectPackageManager3(owner, repo, ref) {
|
|
9189
|
+
async function detectPackageManager3(owner, repo, ref, authToken) {
|
|
8595
9190
|
const lockfiles = [
|
|
8596
9191
|
{ name: "bun.lockb", manager: "bun" },
|
|
8597
9192
|
{ name: "pnpm-lock.yaml", manager: "pnpm" },
|
|
@@ -8599,15 +9194,15 @@ async function detectPackageManager3(owner, repo, ref) {
|
|
|
8599
9194
|
{ name: "package-lock.json", manager: "npm" }
|
|
8600
9195
|
];
|
|
8601
9196
|
for (const { name, manager } of lockfiles) {
|
|
8602
|
-
const content = await fetchRawFile(owner, repo, ref, name);
|
|
9197
|
+
const content = await fetchRawFile(owner, repo, ref, name, authToken);
|
|
8603
9198
|
if (content !== null) {
|
|
8604
9199
|
return { manager, lockfile: { name, content: content.slice(0, 1e4) } };
|
|
8605
9200
|
}
|
|
8606
9201
|
}
|
|
8607
9202
|
return { manager: "unknown" };
|
|
8608
9203
|
}
|
|
8609
|
-
async function detectWorkspace(packageJson, owner, repo, ref) {
|
|
8610
|
-
const pnpmWorkspace = await fetchRawFile(owner, repo, ref, "pnpm-workspace.yaml");
|
|
9204
|
+
async function detectWorkspace(packageJson, owner, repo, ref, authToken) {
|
|
9205
|
+
const pnpmWorkspace = await fetchRawFile(owner, repo, ref, "pnpm-workspace.yaml", authToken);
|
|
8611
9206
|
if (pnpmWorkspace) {
|
|
8612
9207
|
const packagesMatch = pnpmWorkspace.match(/packages:\s*\n((?:\s+-\s+['"]?[^\n]+['"]?\n?)+)/);
|
|
8613
9208
|
const packages = packagesMatch ? packagesMatch[1].split(`
|
|
@@ -8618,7 +9213,7 @@ async function detectWorkspace(packageJson, owner, repo, ref) {
|
|
|
8618
9213
|
packages
|
|
8619
9214
|
};
|
|
8620
9215
|
}
|
|
8621
|
-
const lernaJson = await fetchRawFile(owner, repo, ref, "lerna.json");
|
|
9216
|
+
const lernaJson = await fetchRawFile(owner, repo, ref, "lerna.json", authToken);
|
|
8622
9217
|
if (lernaJson) {
|
|
8623
9218
|
return { isMonorepo: true, tool: "lerna" };
|
|
8624
9219
|
}
|
|
@@ -8678,18 +9273,20 @@ function detectBuildHints(packageJson, tsconfigJson) {
|
|
|
8678
9273
|
}
|
|
8679
9274
|
return hints;
|
|
8680
9275
|
}
|
|
8681
|
-
async function fetchGitHubContext(repoUrl,
|
|
9276
|
+
async function fetchGitHubContext(repoUrl, refOrOptions) {
|
|
8682
9277
|
const parsed = parseGitHubUrl2(repoUrl);
|
|
8683
9278
|
if (!parsed) {
|
|
8684
9279
|
throw new Error(`Invalid GitHub URL: ${repoUrl}`);
|
|
8685
9280
|
}
|
|
9281
|
+
const options = typeof refOrOptions === "string" ? { ref: refOrOptions } : refOrOptions ?? {};
|
|
9282
|
+
const { authToken } = options;
|
|
8686
9283
|
const { owner, repo } = parsed;
|
|
8687
|
-
const metadata = await fetchRepoMetadata(owner, repo);
|
|
8688
|
-
const targetRef = ref ?? metadata.defaultBranch;
|
|
9284
|
+
const metadata = await fetchRepoMetadata(owner, repo, authToken);
|
|
9285
|
+
const targetRef = options.ref ?? metadata.defaultBranch;
|
|
8689
9286
|
const [packageJsonRaw, tsconfigJsonRaw, pmResult] = await Promise.all([
|
|
8690
|
-
fetchRawFile(owner, repo, targetRef, "package.json"),
|
|
8691
|
-
fetchRawFile(owner, repo, targetRef, "tsconfig.json"),
|
|
8692
|
-
detectPackageManager3(owner, repo, targetRef)
|
|
9287
|
+
fetchRawFile(owner, repo, targetRef, "package.json", authToken),
|
|
9288
|
+
fetchRawFile(owner, repo, targetRef, "tsconfig.json", authToken),
|
|
9289
|
+
detectPackageManager3(owner, repo, targetRef, authToken)
|
|
8693
9290
|
]);
|
|
8694
9291
|
let packageJson = null;
|
|
8695
9292
|
let tsconfigJson = null;
|
|
@@ -8703,7 +9300,7 @@ async function fetchGitHubContext(repoUrl, ref) {
|
|
|
8703
9300
|
tsconfigJson = JSON.parse(tsconfigJsonRaw);
|
|
8704
9301
|
} catch {}
|
|
8705
9302
|
}
|
|
8706
|
-
const workspace = await detectWorkspace(packageJson, owner, repo, targetRef);
|
|
9303
|
+
const workspace = await detectWorkspace(packageJson, owner, repo, targetRef, authToken);
|
|
8707
9304
|
const buildHints = detectBuildHints(packageJson, tsconfigJson);
|
|
8708
9305
|
return {
|
|
8709
9306
|
metadata,
|
|
@@ -8718,18 +9315,20 @@ async function fetchGitHubContext(repoUrl, ref) {
|
|
|
8718
9315
|
}
|
|
8719
9316
|
};
|
|
8720
9317
|
}
|
|
8721
|
-
async function listWorkspacePackages(owner, repo, ref, patterns) {
|
|
9318
|
+
async function listWorkspacePackages(owner, repo, ref, patterns, authToken) {
|
|
8722
9319
|
const packages = [];
|
|
8723
9320
|
for (const pattern of patterns) {
|
|
8724
9321
|
const baseDir = pattern.replace(/\/\*.*$/, "");
|
|
8725
9322
|
try {
|
|
8726
9323
|
const url = `https://api.github.com/repos/${owner}/${repo}/contents/${baseDir}?ref=${ref}`;
|
|
8727
|
-
const
|
|
8728
|
-
|
|
8729
|
-
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
|
|
9324
|
+
const headers = {
|
|
9325
|
+
Accept: "application/vnd.github.v3+json",
|
|
9326
|
+
"User-Agent": "DocCov-Scanner"
|
|
9327
|
+
};
|
|
9328
|
+
if (authToken) {
|
|
9329
|
+
headers.Authorization = `Bearer ${authToken}`;
|
|
9330
|
+
}
|
|
9331
|
+
const response = await fetch(url, { headers });
|
|
8733
9332
|
if (response.ok) {
|
|
8734
9333
|
const contents = await response.json();
|
|
8735
9334
|
for (const item of contents) {
|
|
@@ -8742,6 +9341,38 @@ async function listWorkspacePackages(owner, repo, ref, patterns) {
|
|
|
8742
9341
|
}
|
|
8743
9342
|
return packages;
|
|
8744
9343
|
}
|
|
9344
|
+
// src/scan/summary.ts
|
|
9345
|
+
function extractSpecSummary(spec) {
|
|
9346
|
+
const exports = spec.exports ?? [];
|
|
9347
|
+
const undocumented = [];
|
|
9348
|
+
const drift = [];
|
|
9349
|
+
for (const exp of exports) {
|
|
9350
|
+
const docs = exp.docs;
|
|
9351
|
+
if (!docs)
|
|
9352
|
+
continue;
|
|
9353
|
+
const hasMissing = (docs.missing?.length ?? 0) > 0;
|
|
9354
|
+
const isPartial = (docs.coverageScore ?? 0) < 100;
|
|
9355
|
+
if (hasMissing || isPartial) {
|
|
9356
|
+
undocumented.push(exp.name);
|
|
9357
|
+
}
|
|
9358
|
+
for (const d of docs.drift ?? []) {
|
|
9359
|
+
drift.push({
|
|
9360
|
+
export: exp.name,
|
|
9361
|
+
type: d.type,
|
|
9362
|
+
issue: d.issue,
|
|
9363
|
+
suggestion: d.suggestion
|
|
9364
|
+
});
|
|
9365
|
+
}
|
|
9366
|
+
}
|
|
9367
|
+
return {
|
|
9368
|
+
coverage: spec.docs?.coverageScore ?? 0,
|
|
9369
|
+
exportCount: exports.length,
|
|
9370
|
+
typeCount: spec.types?.length ?? 0,
|
|
9371
|
+
driftCount: drift.length,
|
|
9372
|
+
undocumented,
|
|
9373
|
+
drift
|
|
9374
|
+
};
|
|
9375
|
+
}
|
|
8745
9376
|
export {
|
|
8746
9377
|
validateSpecCache,
|
|
8747
9378
|
validateExamples,
|
|
@@ -8750,13 +9381,19 @@ export {
|
|
|
8750
9381
|
shouldValidate,
|
|
8751
9382
|
serializeJSDoc,
|
|
8752
9383
|
saveSpecCache,
|
|
9384
|
+
saveSnapshot,
|
|
8753
9385
|
saveReport,
|
|
8754
9386
|
safeParseJson,
|
|
8755
9387
|
runExamplesWithPackage,
|
|
8756
9388
|
runExamples,
|
|
8757
9389
|
runExample,
|
|
8758
9390
|
resolveTarget,
|
|
9391
|
+
resolveCompiledPath,
|
|
9392
|
+
renderSparkline,
|
|
9393
|
+
renderApiSurface,
|
|
8759
9394
|
readPackageJson,
|
|
9395
|
+
pruneHistory,
|
|
9396
|
+
pruneByTier,
|
|
8760
9397
|
parseGitHubUrl2 as parseScanGitHubUrl,
|
|
8761
9398
|
parseMarkdownFiles,
|
|
8762
9399
|
parseMarkdownFile,
|
|
@@ -8767,10 +9404,13 @@ export {
|
|
|
8767
9404
|
parseAssertions,
|
|
8768
9405
|
mergeFixes,
|
|
8769
9406
|
mergeFilters,
|
|
8770
|
-
mergeConfig,
|
|
8771
9407
|
loadSpecCache,
|
|
9408
|
+
loadSnapshotsForDays,
|
|
9409
|
+
loadSnapshots,
|
|
8772
9410
|
loadCachedReport,
|
|
8773
9411
|
listWorkspacePackages,
|
|
9412
|
+
isStandardJSONSchema,
|
|
9413
|
+
isSchemaType,
|
|
8774
9414
|
isFixableDrift,
|
|
8775
9415
|
isExecutableLang,
|
|
8776
9416
|
isCachedReportValid,
|
|
@@ -8783,43 +9423,49 @@ export {
|
|
|
8783
9423
|
hasDocsForExport,
|
|
8784
9424
|
groupDriftsByCategory,
|
|
8785
9425
|
getUndocumentedExports,
|
|
9426
|
+
getTrend,
|
|
9427
|
+
getSupportedLibraries,
|
|
8786
9428
|
getSpecCachePath,
|
|
8787
9429
|
getRunCommand,
|
|
8788
|
-
getRulesForKind,
|
|
8789
|
-
getRule,
|
|
8790
9430
|
getReportPath,
|
|
9431
|
+
getRegisteredAdapters,
|
|
8791
9432
|
getPrimaryBuildScript,
|
|
8792
9433
|
getInstallCommand,
|
|
9434
|
+
getExtendedTrend,
|
|
8793
9435
|
getDriftSummary,
|
|
8794
9436
|
getDocumentedExports,
|
|
8795
9437
|
getDocsImpactSummary,
|
|
8796
9438
|
getDiffReportPath,
|
|
8797
|
-
|
|
8798
|
-
getCoverageRules,
|
|
9439
|
+
generateWeeklySummaries,
|
|
8799
9440
|
generateReportFromEnriched,
|
|
8800
9441
|
generateReport,
|
|
8801
9442
|
generateFixesForExport,
|
|
8802
9443
|
generateFix,
|
|
8803
9444
|
formatPackageList,
|
|
8804
9445
|
formatDriftSummaryLine,
|
|
9446
|
+
formatDelta,
|
|
8805
9447
|
findRemovedReferences,
|
|
8806
9448
|
findPackageByName,
|
|
8807
9449
|
findJSDocLocation,
|
|
8808
9450
|
findExportReferences,
|
|
8809
9451
|
findDeprecatedReferences,
|
|
9452
|
+
findAdapter,
|
|
8810
9453
|
fetchSpecFromGitHub,
|
|
8811
9454
|
fetchSpec,
|
|
8812
9455
|
fetchGitHubContext,
|
|
9456
|
+
extractStandardSchemasFromProject,
|
|
9457
|
+
extractStandardSchemas,
|
|
8813
9458
|
extractSpecSummary,
|
|
9459
|
+
extractSchemaType,
|
|
9460
|
+
extractSchemaOutputType,
|
|
8814
9461
|
extractPackageSpec,
|
|
8815
9462
|
extractImports,
|
|
8816
9463
|
extractFunctionCalls,
|
|
8817
|
-
evaluateQuality,
|
|
8818
|
-
evaluateExportQuality,
|
|
8819
9464
|
ensureSpecCoverage,
|
|
8820
9465
|
enrichSpec,
|
|
8821
9466
|
diffSpecWithDocs,
|
|
8822
9467
|
diffHashes,
|
|
9468
|
+
detectRuntimeSchemas,
|
|
8823
9469
|
detectPackageManager,
|
|
8824
9470
|
detectMonorepo,
|
|
8825
9471
|
detectExampleRuntimeErrors,
|
|
@@ -8829,9 +9475,11 @@ export {
|
|
|
8829
9475
|
defineConfig,
|
|
8830
9476
|
createSourceFile,
|
|
8831
9477
|
createNodeCommandRunner,
|
|
9478
|
+
computeSnapshot,
|
|
8832
9479
|
computeExportDrift,
|
|
8833
9480
|
computeDrift,
|
|
8834
9481
|
clearSpecCache,
|
|
9482
|
+
clearSchemaCache,
|
|
8835
9483
|
categorizeDrifts,
|
|
8836
9484
|
categorizeDrift,
|
|
8837
9485
|
calculateAggregateCoverage,
|
|
@@ -8848,16 +9496,15 @@ export {
|
|
|
8848
9496
|
analyze,
|
|
8849
9497
|
VALIDATION_INFO,
|
|
8850
9498
|
SandboxFileSystem,
|
|
8851
|
-
STYLE_RULES,
|
|
8852
9499
|
SPEC_CACHE_FILE,
|
|
9500
|
+
RETENTION_DAYS,
|
|
8853
9501
|
REPORT_VERSION,
|
|
8854
9502
|
REPORT_EXTENSIONS,
|
|
8855
9503
|
NodeFileSystem,
|
|
9504
|
+
HISTORY_DIR,
|
|
8856
9505
|
DocCov,
|
|
8857
9506
|
DEFAULT_REPORT_PATH,
|
|
8858
9507
|
DEFAULT_REPORT_DIR,
|
|
8859
|
-
CORE_RULES,
|
|
8860
9508
|
CACHE_VERSION,
|
|
8861
|
-
BUILTIN_RULES,
|
|
8862
9509
|
ALL_VALIDATIONS
|
|
8863
9510
|
};
|