@doccov/sdk 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +9 -10
- package/dist/index.js +209 -157
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -114,21 +114,19 @@ interface DetectedSchemaEntry {
|
|
|
114
114
|
schema: Record<string, unknown>;
|
|
115
115
|
vendor: string;
|
|
116
116
|
}
|
|
117
|
-
/**
|
|
118
|
-
* Runtime Schema Detection (Stubbed)
|
|
119
|
-
*
|
|
120
|
-
* Standard Schema extraction has been removed. This module provides
|
|
121
|
-
* empty stubs to maintain API compatibility.
|
|
122
|
-
*/
|
|
123
117
|
interface SchemaDetectionContext {
|
|
124
118
|
baseDir: string;
|
|
125
119
|
entryFile: string;
|
|
126
120
|
}
|
|
121
|
+
interface DetectedSchema {
|
|
122
|
+
schema: Record<string, unknown>;
|
|
123
|
+
vendor: string;
|
|
124
|
+
}
|
|
127
125
|
interface SchemaDetectionResult {
|
|
128
|
-
schemas: Map<string,
|
|
126
|
+
schemas: Map<string, DetectedSchema>;
|
|
129
127
|
errors: string[];
|
|
130
128
|
}
|
|
131
|
-
declare function detectRuntimeSchemas(
|
|
129
|
+
declare function detectRuntimeSchemas(context: SchemaDetectionContext): Promise<SchemaDetectionResult>;
|
|
132
130
|
declare function clearSchemaCache(): void;
|
|
133
131
|
import * as TS from "typescript";
|
|
134
132
|
/**
|
|
@@ -2298,8 +2296,9 @@ declare class DocCov {
|
|
|
2298
2296
|
*/
|
|
2299
2297
|
private findPackageJson;
|
|
2300
2298
|
/**
|
|
2301
|
-
*
|
|
2302
|
-
*
|
|
2299
|
+
* Detect Standard Schema exports from compiled modules.
|
|
2300
|
+
* Only runs when schemaExtraction is 'runtime' or 'hybrid'.
|
|
2301
|
+
* Returns undefined if detection is disabled, fails, or no schemas found.
|
|
2303
2302
|
*/
|
|
2304
2303
|
private detectSchemas;
|
|
2305
2304
|
private normalizeDiagnostic;
|
package/dist/index.js
CHANGED
|
@@ -17,12 +17,208 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
17
17
|
};
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
|
-
// src/
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
// src/extract/schema/standard-schema.ts
|
|
21
|
+
import { spawn } from "node:child_process";
|
|
22
|
+
import * as fs from "node:fs";
|
|
23
|
+
import * as path from "node:path";
|
|
24
|
+
function isStandardJSONSchema(obj) {
|
|
25
|
+
if (typeof obj !== "object" || obj === null)
|
|
26
|
+
return false;
|
|
27
|
+
const std = obj["~standard"];
|
|
28
|
+
if (typeof std !== "object" || std === null)
|
|
29
|
+
return false;
|
|
30
|
+
const stdObj = std;
|
|
31
|
+
if (typeof stdObj.version !== "number")
|
|
32
|
+
return false;
|
|
33
|
+
if (typeof stdObj.vendor !== "string")
|
|
34
|
+
return false;
|
|
35
|
+
const jsonSchema = stdObj.jsonSchema;
|
|
36
|
+
if (typeof jsonSchema !== "object" || jsonSchema === null)
|
|
37
|
+
return false;
|
|
38
|
+
const jsObj = jsonSchema;
|
|
39
|
+
return typeof jsObj.output === "function";
|
|
40
|
+
}
|
|
41
|
+
var WORKER_SCRIPT = `
|
|
42
|
+
const path = require('path');
|
|
43
|
+
const { pathToFileURL } = require('url');
|
|
44
|
+
|
|
45
|
+
// TypeBox detection: schemas have Symbol.for('TypeBox.Kind') and are JSON Schema
|
|
46
|
+
const TYPEBOX_KIND = Symbol.for('TypeBox.Kind');
|
|
47
|
+
|
|
48
|
+
function isTypeBoxSchema(obj) {
|
|
49
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
50
|
+
// TypeBox schemas always have Kind symbol (Union, Object, String, etc.)
|
|
51
|
+
// Also check for common JSON Schema props to avoid false positives
|
|
52
|
+
if (!obj[TYPEBOX_KIND]) return false;
|
|
53
|
+
return typeof obj.type === 'string' || 'anyOf' in obj || 'oneOf' in obj || 'allOf' in obj;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function sanitizeTypeBoxSchema(schema) {
|
|
57
|
+
// JSON.stringify removes symbol keys, keeping only JSON Schema props
|
|
58
|
+
return JSON.parse(JSON.stringify(schema));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function extract() {
|
|
62
|
+
// With node -e, argv is: [node, arg1, arg2, ...]
|
|
63
|
+
// (the -e script is NOT in argv)
|
|
64
|
+
const [modulePath, target] = process.argv.slice(1);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// Import the module using dynamic import (works with ESM and CJS)
|
|
68
|
+
const absPath = path.resolve(modulePath);
|
|
69
|
+
const mod = await import(pathToFileURL(absPath).href);
|
|
70
|
+
const results = [];
|
|
71
|
+
|
|
72
|
+
// Check each export
|
|
73
|
+
for (const [name, value] of Object.entries(mod)) {
|
|
74
|
+
if (name.startsWith('_')) continue;
|
|
75
|
+
if (typeof value !== 'object' || value === null) continue;
|
|
76
|
+
|
|
77
|
+
// Priority 1: Standard Schema (Zod 4.2+, ArkType, etc.)
|
|
78
|
+
const std = value['~standard'];
|
|
79
|
+
if (std && typeof std === 'object' && typeof std.version === 'number' && typeof std.vendor === 'string' && std.jsonSchema && typeof std.jsonSchema.output === 'function') {
|
|
80
|
+
try {
|
|
81
|
+
const outputSchema = std.jsonSchema.output(target);
|
|
82
|
+
const inputSchema = std.jsonSchema.input ? std.jsonSchema.input(target) : undefined;
|
|
83
|
+
results.push({
|
|
84
|
+
exportName: name,
|
|
85
|
+
vendor: std.vendor,
|
|
86
|
+
outputSchema,
|
|
87
|
+
inputSchema
|
|
88
|
+
});
|
|
89
|
+
} catch (e) {
|
|
90
|
+
// Skip schemas that fail to extract
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Priority 2: TypeBox (schema IS JSON Schema)
|
|
96
|
+
if (isTypeBoxSchema(value)) {
|
|
97
|
+
try {
|
|
98
|
+
results.push({
|
|
99
|
+
exportName: name,
|
|
100
|
+
vendor: 'typebox',
|
|
101
|
+
outputSchema: sanitizeTypeBoxSchema(value)
|
|
102
|
+
});
|
|
103
|
+
} catch (e) {
|
|
104
|
+
// Skip schemas that fail to extract
|
|
105
|
+
}
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log(JSON.stringify({ success: true, results }));
|
|
111
|
+
} catch (e) {
|
|
112
|
+
console.log(JSON.stringify({ success: false, error: e.message }));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
extract();
|
|
117
|
+
`;
|
|
118
|
+
function resolveCompiledPath(tsPath, baseDir) {
|
|
119
|
+
const relativePath = path.relative(baseDir, tsPath);
|
|
120
|
+
const withoutExt = relativePath.replace(/\.tsx?$/, "");
|
|
121
|
+
const candidates = [
|
|
122
|
+
path.join(baseDir, `${withoutExt}.js`),
|
|
123
|
+
path.join(baseDir, "dist", `${withoutExt.replace(/^src\//, "")}.js`),
|
|
124
|
+
path.join(baseDir, "build", `${withoutExt.replace(/^src\//, "")}.js`),
|
|
125
|
+
path.join(baseDir, "lib", `${withoutExt.replace(/^src\//, "")}.js`)
|
|
126
|
+
];
|
|
127
|
+
for (const candidate of candidates) {
|
|
128
|
+
if (fs.existsSync(candidate)) {
|
|
129
|
+
return candidate;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
async function extractStandardSchemas(compiledJsPath, options = {}) {
|
|
135
|
+
const { timeout = 1e4, target = "draft-2020-12" } = options;
|
|
136
|
+
const result = {
|
|
23
137
|
schemas: new Map,
|
|
24
138
|
errors: []
|
|
25
139
|
};
|
|
140
|
+
if (!fs.existsSync(compiledJsPath)) {
|
|
141
|
+
result.errors.push(`Compiled JS not found: ${compiledJsPath}`);
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
return new Promise((resolve) => {
|
|
145
|
+
const child = spawn("node", ["-e", WORKER_SCRIPT, compiledJsPath, target], {
|
|
146
|
+
timeout,
|
|
147
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
148
|
+
});
|
|
149
|
+
let stdout = "";
|
|
150
|
+
let stderr = "";
|
|
151
|
+
child.stdout.on("data", (data) => {
|
|
152
|
+
stdout += data.toString();
|
|
153
|
+
});
|
|
154
|
+
child.stderr.on("data", (data) => {
|
|
155
|
+
stderr += data.toString();
|
|
156
|
+
});
|
|
157
|
+
child.on("close", (code) => {
|
|
158
|
+
if (code !== 0) {
|
|
159
|
+
result.errors.push(`Extraction process failed: ${stderr || `exit code ${code}`}`);
|
|
160
|
+
resolve(result);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const parsed = JSON.parse(stdout);
|
|
165
|
+
if (!parsed.success) {
|
|
166
|
+
result.errors.push(`Extraction failed: ${parsed.error}`);
|
|
167
|
+
resolve(result);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
for (const item of parsed.results) {
|
|
171
|
+
result.schemas.set(item.exportName, {
|
|
172
|
+
exportName: item.exportName,
|
|
173
|
+
vendor: item.vendor,
|
|
174
|
+
outputSchema: item.outputSchema,
|
|
175
|
+
inputSchema: item.inputSchema
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
} catch (e) {
|
|
179
|
+
result.errors.push(`Failed to parse extraction output: ${e}`);
|
|
180
|
+
}
|
|
181
|
+
resolve(result);
|
|
182
|
+
});
|
|
183
|
+
child.on("error", (err) => {
|
|
184
|
+
result.errors.push(`Subprocess error: ${err.message}`);
|
|
185
|
+
resolve(result);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
async function extractStandardSchemasFromProject(entryFile, baseDir, options = {}) {
|
|
190
|
+
const compiledPath = resolveCompiledPath(entryFile, baseDir);
|
|
191
|
+
if (!compiledPath) {
|
|
192
|
+
return {
|
|
193
|
+
schemas: new Map,
|
|
194
|
+
errors: [`Could not find compiled JS for ${entryFile}. Build the project first.`]
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return extractStandardSchemas(compiledPath, options);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/analysis/schema-detection.ts
|
|
201
|
+
async function detectRuntimeSchemas(context) {
|
|
202
|
+
const { baseDir, entryFile } = context;
|
|
203
|
+
const compiledPath = resolveCompiledPath(entryFile, baseDir);
|
|
204
|
+
if (!compiledPath) {
|
|
205
|
+
return {
|
|
206
|
+
schemas: new Map,
|
|
207
|
+
errors: []
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const extraction = await extractStandardSchemasFromProject(entryFile, baseDir);
|
|
211
|
+
const schemas = new Map;
|
|
212
|
+
for (const [name, result] of extraction.schemas) {
|
|
213
|
+
schemas.set(name, {
|
|
214
|
+
schema: result.outputSchema,
|
|
215
|
+
vendor: result.vendor
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
schemas,
|
|
220
|
+
errors: extraction.errors
|
|
221
|
+
};
|
|
26
222
|
}
|
|
27
223
|
function clearSchemaCache() {}
|
|
28
224
|
// src/extract/schema/types.ts
|
|
@@ -209,155 +405,6 @@ function getRegisteredAdapters() {
|
|
|
209
405
|
function getSupportedLibraries() {
|
|
210
406
|
return adapters.flatMap((a) => a.packages);
|
|
211
407
|
}
|
|
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
|
-
}
|
|
361
408
|
// src/analysis/docs-coverage.ts
|
|
362
409
|
import {
|
|
363
410
|
DRIFT_CATEGORIES
|
|
@@ -6983,21 +7030,22 @@ function serializeVariable(declaration, symbol, context) {
|
|
|
6983
7030
|
const typeRefs = typeRegistry.getTypeRefs();
|
|
6984
7031
|
const referencedTypes = typeRegistry.getReferencedTypes();
|
|
6985
7032
|
const symbolName = symbol.getName();
|
|
6986
|
-
const
|
|
6987
|
-
if (
|
|
7033
|
+
const runtimeSchema = context.detectedSchemas?.get(symbolName);
|
|
7034
|
+
if (runtimeSchema) {
|
|
7035
|
+
const schemaSource = runtimeSchema.vendor === "typebox" ? "typebox-native" : "standard-schema";
|
|
6988
7036
|
return {
|
|
6989
7037
|
id: symbolName,
|
|
6990
7038
|
name: symbolName,
|
|
6991
7039
|
...metadata,
|
|
6992
7040
|
kind: "variable",
|
|
6993
7041
|
deprecated: isSymbolDeprecated(symbol),
|
|
6994
|
-
schema:
|
|
7042
|
+
schema: runtimeSchema.schema,
|
|
6995
7043
|
description,
|
|
6996
7044
|
source: getSourceLocation(declaration),
|
|
6997
7045
|
tags: [
|
|
6998
7046
|
...parsedDoc?.tags ?? [],
|
|
6999
|
-
{ name: "schemaLibrary", text:
|
|
7000
|
-
{ name: "schemaSource", text:
|
|
7047
|
+
{ name: "schemaLibrary", text: runtimeSchema.vendor },
|
|
7048
|
+
{ name: "schemaSource", text: schemaSource }
|
|
7001
7049
|
],
|
|
7002
7050
|
examples: parsedDoc?.examples
|
|
7003
7051
|
};
|
|
@@ -8978,6 +9026,10 @@ class DocCov {
|
|
|
8978
9026
|
}
|
|
8979
9027
|
}
|
|
8980
9028
|
async detectSchemas(entryFile, packageDir) {
|
|
9029
|
+
const mode = this.options.schemaExtraction ?? "static";
|
|
9030
|
+
if (mode === "static") {
|
|
9031
|
+
return;
|
|
9032
|
+
}
|
|
8981
9033
|
try {
|
|
8982
9034
|
const result = await detectRuntimeSchemas({
|
|
8983
9035
|
baseDir: packageDir,
|