@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 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, never>;
126
+ schemas: Map<string, DetectedSchema>;
129
127
  errors: string[];
130
128
  }
131
- declare function detectRuntimeSchemas(_context: SchemaDetectionContext): Promise<SchemaDetectionResult>;
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
- * Opportunistically detect Standard Schema exports from compiled modules.
2302
- * Returns undefined if detection fails or no schemas found (fallback to AST).
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/analysis/schema-detection.ts
21
- async function detectRuntimeSchemas(_context) {
22
- return {
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 standardSchema = context.detectedSchemas?.get(symbolName);
6987
- if (standardSchema) {
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: standardSchema.schema,
7042
+ schema: runtimeSchema.schema,
6995
7043
  description,
6996
7044
  source: getSourceLocation(declaration),
6997
7045
  tags: [
6998
7046
  ...parsedDoc?.tags ?? [],
6999
- { name: "schemaLibrary", text: standardSchema.vendor },
7000
- { name: "schemaSource", text: "standard-schema" }
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doccov/sdk",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "DocCov SDK - Documentation coverage and drift detection for TypeScript",
5
5
  "keywords": [
6
6
  "typescript",