@openpkg-ts/extract 0.20.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/bin/tspec.js +10 -2
- package/dist/shared/{chunk-rej3ws8m.js → chunk-yh8v9dbt.js} +319 -20
- package/dist/src/index.d.ts +94 -80
- package/dist/src/index.js +6 -196
- package/package.json +1 -1
package/dist/bin/tspec.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
extract
|
|
4
|
-
} from "../shared/chunk-
|
|
4
|
+
} from "../shared/chunk-yh8v9dbt.js";
|
|
5
5
|
|
|
6
6
|
// src/cli/spec.ts
|
|
7
7
|
import * as fs from "node:fs";
|
|
@@ -451,13 +451,21 @@ function createProgram() {
|
|
|
451
451
|
}
|
|
452
452
|
fs.writeFileSync(options.output, JSON.stringify(normalized, null, 2));
|
|
453
453
|
spin.success(`Extracted to ${options.output}`);
|
|
454
|
+
if (result.runtimeSchemas) {
|
|
455
|
+
const { extracted, merged, vendors } = result.runtimeSchemas;
|
|
456
|
+
console.log(`ℹ Runtime schemas: ${merged}/${extracted} merged (${vendors.join(", ")})`);
|
|
457
|
+
}
|
|
454
458
|
for (const diag of result.diagnostics) {
|
|
455
459
|
if (diag.severity === "info" && !options.verbose)
|
|
456
460
|
continue;
|
|
457
461
|
const prefix2 = diag.severity === "error" ? "✗" : diag.severity === "warning" ? "⚠" : "ℹ";
|
|
458
462
|
console.log(`${prefix2} ${diag.message}`);
|
|
459
463
|
}
|
|
460
|
-
summary().addKeyValue("Exports", normalized.exports.length).addKeyValue("Types", normalized.types?.length || 0)
|
|
464
|
+
const sum = summary().addKeyValue("Exports", normalized.exports.length).addKeyValue("Types", normalized.types?.length || 0);
|
|
465
|
+
if (result.runtimeSchemas) {
|
|
466
|
+
sum.addKeyValue("Runtime Schemas", result.runtimeSchemas.merged);
|
|
467
|
+
}
|
|
468
|
+
sum.print();
|
|
461
469
|
});
|
|
462
470
|
return program;
|
|
463
471
|
}
|
|
@@ -164,8 +164,8 @@ function buildSchema(type, checker, ctx, _depth = 0) {
|
|
|
164
164
|
return { type: "number", enum: [literal] };
|
|
165
165
|
}
|
|
166
166
|
if (type.flags & ts.TypeFlags.BooleanLiteral) {
|
|
167
|
-
const
|
|
168
|
-
return { type: "boolean", enum: [
|
|
167
|
+
const typeString = checker.typeToString(type);
|
|
168
|
+
return { type: "boolean", enum: [typeString === "true"] };
|
|
169
169
|
}
|
|
170
170
|
if (type.isUnion()) {
|
|
171
171
|
const types = type.types;
|
|
@@ -329,6 +329,9 @@ function isPureRefSchema(schema) {
|
|
|
329
329
|
return typeof schema === "object" && Object.keys(schema).length === 1 && "$ref" in schema;
|
|
330
330
|
}
|
|
331
331
|
function withDescription(schema, description) {
|
|
332
|
+
if (typeof schema === "string") {
|
|
333
|
+
return { type: schema, description };
|
|
334
|
+
}
|
|
332
335
|
if (isPureRefSchema(schema)) {
|
|
333
336
|
return {
|
|
334
337
|
allOf: [schema],
|
|
@@ -702,9 +705,9 @@ function getJSDocComment(node) {
|
|
|
702
705
|
}
|
|
703
706
|
return { name: tag.tagName.text, text: rawText };
|
|
704
707
|
});
|
|
705
|
-
const jsDocComments = node.
|
|
708
|
+
const jsDocComments = ts3.getJSDocCommentsAndTags(node).filter(ts3.isJSDoc);
|
|
706
709
|
let description;
|
|
707
|
-
if (jsDocComments
|
|
710
|
+
if (jsDocComments.length > 0) {
|
|
708
711
|
const firstDoc = jsDocComments[0];
|
|
709
712
|
if (firstDoc.comment) {
|
|
710
713
|
description = typeof firstDoc.comment === "string" ? firstDoc.comment : ts3.getTextOfJSDocComment(firstDoc.comment);
|
|
@@ -1076,7 +1079,7 @@ function getMemberName(member) {
|
|
|
1076
1079
|
return member.name.getText();
|
|
1077
1080
|
}
|
|
1078
1081
|
function getVisibility(member) {
|
|
1079
|
-
const modifiers = ts6.getModifiers(member);
|
|
1082
|
+
const modifiers = ts6.canHaveModifiers(member) ? ts6.getModifiers(member) : undefined;
|
|
1080
1083
|
if (!modifiers)
|
|
1081
1084
|
return;
|
|
1082
1085
|
for (const mod of modifiers) {
|
|
@@ -1090,11 +1093,11 @@ function getVisibility(member) {
|
|
|
1090
1093
|
return;
|
|
1091
1094
|
}
|
|
1092
1095
|
function isStatic(member) {
|
|
1093
|
-
const modifiers = ts6.getModifiers(member);
|
|
1096
|
+
const modifiers = ts6.canHaveModifiers(member) ? ts6.getModifiers(member) : undefined;
|
|
1094
1097
|
return modifiers?.some((m) => m.kind === ts6.SyntaxKind.StaticKeyword) ?? false;
|
|
1095
1098
|
}
|
|
1096
1099
|
function isReadonly(member) {
|
|
1097
|
-
const modifiers = ts6.getModifiers(member);
|
|
1100
|
+
const modifiers = ts6.canHaveModifiers(member) ? ts6.getModifiers(member) : undefined;
|
|
1098
1101
|
return modifiers?.some((m) => m.kind === ts6.SyntaxKind.ReadonlyKeyword) ?? false;
|
|
1099
1102
|
}
|
|
1100
1103
|
function serializeProperty(node, ctx) {
|
|
@@ -1463,7 +1466,7 @@ function getInterfaceExtends(node, checker) {
|
|
|
1463
1466
|
const type = checker.getTypeAtLocation(expr);
|
|
1464
1467
|
return type.getSymbol()?.getName() ?? expr.expression.getText();
|
|
1465
1468
|
});
|
|
1466
|
-
return names.
|
|
1469
|
+
return names.join(" & ");
|
|
1467
1470
|
}
|
|
1468
1471
|
}
|
|
1469
1472
|
return;
|
|
@@ -1519,9 +1522,240 @@ function serializeVariable(node, statement, ctx) {
|
|
|
1519
1522
|
};
|
|
1520
1523
|
}
|
|
1521
1524
|
|
|
1522
|
-
// src/
|
|
1525
|
+
// src/schema/standard-schema.ts
|
|
1526
|
+
import { spawn } from "node:child_process";
|
|
1523
1527
|
import * as fs from "node:fs";
|
|
1524
1528
|
import * as path2 from "node:path";
|
|
1529
|
+
function isStandardJSONSchema(obj) {
|
|
1530
|
+
if (typeof obj !== "object" || obj === null)
|
|
1531
|
+
return false;
|
|
1532
|
+
const std = obj["~standard"];
|
|
1533
|
+
if (typeof std !== "object" || std === null)
|
|
1534
|
+
return false;
|
|
1535
|
+
const stdObj = std;
|
|
1536
|
+
if (stdObj.version !== 1)
|
|
1537
|
+
return false;
|
|
1538
|
+
if (typeof stdObj.vendor !== "string")
|
|
1539
|
+
return false;
|
|
1540
|
+
const jsonSchema = stdObj.jsonSchema;
|
|
1541
|
+
if (typeof jsonSchema !== "object" || jsonSchema === null)
|
|
1542
|
+
return false;
|
|
1543
|
+
const jsObj = jsonSchema;
|
|
1544
|
+
return typeof jsObj.output === "function" && typeof jsObj.input === "function";
|
|
1545
|
+
}
|
|
1546
|
+
var WORKER_SCRIPT = `
|
|
1547
|
+
const path = require('path');
|
|
1548
|
+
const { pathToFileURL } = require('url');
|
|
1549
|
+
|
|
1550
|
+
// TypeBox detection: schemas have Symbol.for('TypeBox.Kind') and are JSON Schema
|
|
1551
|
+
const TYPEBOX_KIND = Symbol.for('TypeBox.Kind');
|
|
1552
|
+
|
|
1553
|
+
function isTypeBoxSchema(obj) {
|
|
1554
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
1555
|
+
// TypeBox schemas always have Kind symbol (Union, Object, String, etc.)
|
|
1556
|
+
// Also check for common JSON Schema props to avoid false positives
|
|
1557
|
+
if (!obj[TYPEBOX_KIND]) return false;
|
|
1558
|
+
return typeof obj.type === 'string' || 'anyOf' in obj || 'oneOf' in obj || 'allOf' in obj;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
function sanitizeTypeBoxSchema(schema) {
|
|
1562
|
+
// JSON.stringify removes symbol keys, keeping only JSON Schema props
|
|
1563
|
+
return JSON.parse(JSON.stringify(schema));
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
async function extract() {
|
|
1567
|
+
// With node -e, argv is: [node, arg1, arg2, ...]
|
|
1568
|
+
// (the -e script is NOT in argv)
|
|
1569
|
+
const [modulePath, optionsJson] = process.argv.slice(1);
|
|
1570
|
+
const { target, libraryOptions } = JSON.parse(optionsJson || '{}');
|
|
1571
|
+
|
|
1572
|
+
try {
|
|
1573
|
+
// Import the module using dynamic import (works with ESM and CJS)
|
|
1574
|
+
const absPath = path.resolve(modulePath);
|
|
1575
|
+
const mod = await import(pathToFileURL(absPath).href);
|
|
1576
|
+
const results = [];
|
|
1577
|
+
|
|
1578
|
+
// Build exports map - handle both ESM and CJS (where exports are in mod.default)
|
|
1579
|
+
const exports = {};
|
|
1580
|
+
for (const [name, value] of Object.entries(mod)) {
|
|
1581
|
+
if (name === 'default' && typeof value === 'object' && value !== null) {
|
|
1582
|
+
// CJS module: spread default exports
|
|
1583
|
+
Object.assign(exports, value);
|
|
1584
|
+
} else if (name !== 'default') {
|
|
1585
|
+
exports[name] = value;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
// Check each export
|
|
1590
|
+
for (const [name, value] of Object.entries(exports)) {
|
|
1591
|
+
if (name.startsWith('_')) continue;
|
|
1592
|
+
if (typeof value !== 'object' || value === null) continue;
|
|
1593
|
+
|
|
1594
|
+
// Priority 1: Standard JSON Schema (Zod 4.2+, ArkType 2.1.28+, Valibot 1.2+)
|
|
1595
|
+
const std = value['~standard'];
|
|
1596
|
+
if (std && typeof std === 'object' && std.version === 1 && typeof std.vendor === 'string' && std.jsonSchema && typeof std.jsonSchema.output === 'function') {
|
|
1597
|
+
try {
|
|
1598
|
+
// Per spec: pass options object with target and optional libraryOptions
|
|
1599
|
+
const options = { target: target || 'draft-2020-12', ...(libraryOptions && { libraryOptions }) };
|
|
1600
|
+
const outputSchema = std.jsonSchema.output(options);
|
|
1601
|
+
const inputSchema = typeof std.jsonSchema.input === 'function' ? std.jsonSchema.input(options) : undefined;
|
|
1602
|
+
results.push({
|
|
1603
|
+
exportName: name,
|
|
1604
|
+
vendor: std.vendor,
|
|
1605
|
+
outputSchema,
|
|
1606
|
+
inputSchema
|
|
1607
|
+
});
|
|
1608
|
+
} catch (e) {
|
|
1609
|
+
// Skip schemas that fail to extract
|
|
1610
|
+
}
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// Priority 2: TypeBox (schema IS JSON Schema)
|
|
1615
|
+
if (isTypeBoxSchema(value)) {
|
|
1616
|
+
try {
|
|
1617
|
+
results.push({
|
|
1618
|
+
exportName: name,
|
|
1619
|
+
vendor: 'typebox',
|
|
1620
|
+
outputSchema: sanitizeTypeBoxSchema(value)
|
|
1621
|
+
});
|
|
1622
|
+
} catch (e) {
|
|
1623
|
+
// Skip schemas that fail to extract
|
|
1624
|
+
}
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
console.log(JSON.stringify({ success: true, results }));
|
|
1630
|
+
} catch (e) {
|
|
1631
|
+
console.log(JSON.stringify({ success: false, error: e.message }));
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
extract();
|
|
1636
|
+
`;
|
|
1637
|
+
function readTsconfigOutDir(baseDir) {
|
|
1638
|
+
const tsconfigPath = path2.join(baseDir, "tsconfig.json");
|
|
1639
|
+
try {
|
|
1640
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
1641
|
+
return null;
|
|
1642
|
+
}
|
|
1643
|
+
const content = fs.readFileSync(tsconfigPath, "utf-8");
|
|
1644
|
+
const stripped = content.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
|
1645
|
+
const tsconfig = JSON.parse(stripped);
|
|
1646
|
+
if (tsconfig.compilerOptions?.outDir) {
|
|
1647
|
+
return tsconfig.compilerOptions.outDir.replace(/^\.\//, "");
|
|
1648
|
+
}
|
|
1649
|
+
} catch {}
|
|
1650
|
+
return null;
|
|
1651
|
+
}
|
|
1652
|
+
function resolveCompiledPath(tsPath, baseDir) {
|
|
1653
|
+
const relativePath = path2.relative(baseDir, tsPath);
|
|
1654
|
+
const withoutExt = relativePath.replace(/\.tsx?$/, "");
|
|
1655
|
+
const srcPrefix = withoutExt.replace(/^src\//, "");
|
|
1656
|
+
const tsconfigOutDir = readTsconfigOutDir(baseDir);
|
|
1657
|
+
const extensions = [".js", ".mjs", ".cjs"];
|
|
1658
|
+
const candidates = [];
|
|
1659
|
+
if (tsconfigOutDir) {
|
|
1660
|
+
for (const ext of extensions) {
|
|
1661
|
+
candidates.push(path2.join(baseDir, tsconfigOutDir, `${srcPrefix}${ext}`));
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
const commonOutDirs = ["dist", "build", "lib", "out"];
|
|
1665
|
+
for (const outDir of commonOutDirs) {
|
|
1666
|
+
if (outDir === tsconfigOutDir)
|
|
1667
|
+
continue;
|
|
1668
|
+
for (const ext of extensions) {
|
|
1669
|
+
candidates.push(path2.join(baseDir, outDir, `${srcPrefix}${ext}`));
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
for (const ext of extensions) {
|
|
1673
|
+
candidates.push(path2.join(baseDir, `${withoutExt}${ext}`));
|
|
1674
|
+
}
|
|
1675
|
+
const workspaceMatch = baseDir.match(/^(.+\/packages\/[^/]+)$/);
|
|
1676
|
+
if (workspaceMatch) {
|
|
1677
|
+
const pkgRoot = workspaceMatch[1];
|
|
1678
|
+
for (const ext of extensions) {
|
|
1679
|
+
candidates.push(path2.join(pkgRoot, "dist", `${srcPrefix}${ext}`));
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
for (const candidate of candidates) {
|
|
1683
|
+
if (fs.existsSync(candidate)) {
|
|
1684
|
+
return candidate;
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
return null;
|
|
1688
|
+
}
|
|
1689
|
+
async function extractStandardSchemas(compiledJsPath, options = {}) {
|
|
1690
|
+
const { timeout = 1e4, target = "draft-2020-12", libraryOptions } = options;
|
|
1691
|
+
const result = {
|
|
1692
|
+
schemas: new Map,
|
|
1693
|
+
errors: []
|
|
1694
|
+
};
|
|
1695
|
+
if (!fs.existsSync(compiledJsPath)) {
|
|
1696
|
+
result.errors.push(`Compiled JS not found: ${compiledJsPath}`);
|
|
1697
|
+
return result;
|
|
1698
|
+
}
|
|
1699
|
+
const optionsJson = JSON.stringify({ target, libraryOptions });
|
|
1700
|
+
return new Promise((resolve) => {
|
|
1701
|
+
const child = spawn("node", ["-e", WORKER_SCRIPT, compiledJsPath, optionsJson], {
|
|
1702
|
+
timeout,
|
|
1703
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1704
|
+
});
|
|
1705
|
+
let stdout = "";
|
|
1706
|
+
let stderr = "";
|
|
1707
|
+
child.stdout.on("data", (data) => {
|
|
1708
|
+
stdout += data.toString();
|
|
1709
|
+
});
|
|
1710
|
+
child.stderr.on("data", (data) => {
|
|
1711
|
+
stderr += data.toString();
|
|
1712
|
+
});
|
|
1713
|
+
child.on("close", (code) => {
|
|
1714
|
+
if (code !== 0) {
|
|
1715
|
+
result.errors.push(`Extraction process failed: ${stderr || `exit code ${code}`}`);
|
|
1716
|
+
resolve(result);
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
try {
|
|
1720
|
+
const parsed = JSON.parse(stdout);
|
|
1721
|
+
if (!parsed.success) {
|
|
1722
|
+
result.errors.push(`Extraction failed: ${parsed.error}`);
|
|
1723
|
+
resolve(result);
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
for (const item of parsed.results) {
|
|
1727
|
+
result.schemas.set(item.exportName, {
|
|
1728
|
+
exportName: item.exportName,
|
|
1729
|
+
vendor: item.vendor,
|
|
1730
|
+
outputSchema: item.outputSchema,
|
|
1731
|
+
inputSchema: item.inputSchema
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
} catch (e) {
|
|
1735
|
+
result.errors.push(`Failed to parse extraction output: ${e}`);
|
|
1736
|
+
}
|
|
1737
|
+
resolve(result);
|
|
1738
|
+
});
|
|
1739
|
+
child.on("error", (err) => {
|
|
1740
|
+
result.errors.push(`Subprocess error: ${err.message}`);
|
|
1741
|
+
resolve(result);
|
|
1742
|
+
});
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
async function extractStandardSchemasFromProject(entryFile, baseDir, options = {}) {
|
|
1746
|
+
const compiledPath = resolveCompiledPath(entryFile, baseDir);
|
|
1747
|
+
if (!compiledPath) {
|
|
1748
|
+
return {
|
|
1749
|
+
schemas: new Map,
|
|
1750
|
+
errors: [`Could not find compiled JS for ${entryFile}. Build the project first.`]
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
return extractStandardSchemas(compiledPath, options);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
// src/builder/spec-builder.ts
|
|
1757
|
+
import * as fs2 from "node:fs";
|
|
1758
|
+
import * as path3 from "node:path";
|
|
1525
1759
|
import { SCHEMA_URL, SCHEMA_VERSION } from "@openpkg-ts/spec";
|
|
1526
1760
|
import ts8 from "typescript";
|
|
1527
1761
|
|
|
@@ -1541,6 +1775,34 @@ function createContext(program, sourceFile, options = {}) {
|
|
|
1541
1775
|
};
|
|
1542
1776
|
}
|
|
1543
1777
|
|
|
1778
|
+
// src/builder/schema-merger.ts
|
|
1779
|
+
function mergeRuntimeSchemas(staticExports, runtimeSchemas) {
|
|
1780
|
+
let merged = 0;
|
|
1781
|
+
const exports = staticExports.map((exp) => {
|
|
1782
|
+
const runtime = runtimeSchemas.get(exp.name);
|
|
1783
|
+
if (!runtime)
|
|
1784
|
+
return exp;
|
|
1785
|
+
merged++;
|
|
1786
|
+
const mergedExport = {
|
|
1787
|
+
...exp,
|
|
1788
|
+
schema: runtime.outputSchema,
|
|
1789
|
+
tags: [
|
|
1790
|
+
...exp.tags || [],
|
|
1791
|
+
{ name: "vendor", text: runtime.vendor },
|
|
1792
|
+
{ name: "schema-source", text: "standard-json-schema" }
|
|
1793
|
+
]
|
|
1794
|
+
};
|
|
1795
|
+
if (runtime.inputSchema && JSON.stringify(runtime.inputSchema) !== JSON.stringify(runtime.outputSchema)) {
|
|
1796
|
+
mergedExport.flags = {
|
|
1797
|
+
...mergedExport.flags,
|
|
1798
|
+
inputSchema: runtime.inputSchema
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
return mergedExport;
|
|
1802
|
+
});
|
|
1803
|
+
return { merged, exports };
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1544
1806
|
// src/builder/spec-builder.ts
|
|
1545
1807
|
var BUILTIN_TYPES2 = new Set([
|
|
1546
1808
|
"Array",
|
|
@@ -1638,7 +1900,7 @@ async function extract(options) {
|
|
|
1638
1900
|
ignore
|
|
1639
1901
|
} = options;
|
|
1640
1902
|
const diagnostics = [];
|
|
1641
|
-
|
|
1903
|
+
let exports = [];
|
|
1642
1904
|
const result = createProgram({ entryFile, baseDir, content });
|
|
1643
1905
|
const { program, sourceFile } = result;
|
|
1644
1906
|
if (!sourceFile) {
|
|
@@ -1715,6 +1977,41 @@ async function extract(options) {
|
|
|
1715
1977
|
code: "EXTERNAL_TYPES"
|
|
1716
1978
|
});
|
|
1717
1979
|
}
|
|
1980
|
+
let runtimeMetadata;
|
|
1981
|
+
if (options.schemaExtraction === "hybrid") {
|
|
1982
|
+
const projectBaseDir = baseDir || path3.dirname(entryFile);
|
|
1983
|
+
const compiledPath = resolveCompiledPath(entryFile, projectBaseDir);
|
|
1984
|
+
if (compiledPath) {
|
|
1985
|
+
const runtimeResult = await extractStandardSchemas(compiledPath, {
|
|
1986
|
+
target: options.schemaTarget || "draft-2020-12",
|
|
1987
|
+
timeout: 15000
|
|
1988
|
+
});
|
|
1989
|
+
if (runtimeResult.schemas.size > 0) {
|
|
1990
|
+
const mergeResult = mergeRuntimeSchemas(exports, runtimeResult.schemas);
|
|
1991
|
+
exports = mergeResult.exports;
|
|
1992
|
+
runtimeMetadata = {
|
|
1993
|
+
extracted: runtimeResult.schemas.size,
|
|
1994
|
+
merged: mergeResult.merged,
|
|
1995
|
+
vendors: [...new Set([...runtimeResult.schemas.values()].map((s) => s.vendor))],
|
|
1996
|
+
errors: runtimeResult.errors
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
for (const error of runtimeResult.errors) {
|
|
2000
|
+
diagnostics.push({
|
|
2001
|
+
message: `Runtime schema extraction: ${error}`,
|
|
2002
|
+
severity: "warning",
|
|
2003
|
+
code: "RUNTIME_SCHEMA_ERROR"
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
} else {
|
|
2007
|
+
diagnostics.push({
|
|
2008
|
+
message: "Hybrid mode: Could not find compiled JS. Build the project first.",
|
|
2009
|
+
severity: "warning",
|
|
2010
|
+
code: "NO_COMPILED_JS",
|
|
2011
|
+
suggestion: "Run `npm run build` or `tsc` before extraction"
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
1718
2015
|
const spec = {
|
|
1719
2016
|
...includeSchema ? { $schema: SCHEMA_URL } : {},
|
|
1720
2017
|
openpkg: SCHEMA_VERSION,
|
|
@@ -1723,14 +2020,16 @@ async function extract(options) {
|
|
|
1723
2020
|
types,
|
|
1724
2021
|
generation: {
|
|
1725
2022
|
generator: "@openpkg-ts/extract",
|
|
1726
|
-
timestamp: new Date().toISOString()
|
|
2023
|
+
timestamp: new Date().toISOString(),
|
|
2024
|
+
...options.schemaExtraction === "hybrid" ? { schemaExtraction: "hybrid" } : {}
|
|
1727
2025
|
}
|
|
1728
2026
|
};
|
|
1729
2027
|
const internalForgotten = forgottenExports.filter((f) => !f.isExternal);
|
|
1730
2028
|
return {
|
|
1731
2029
|
spec,
|
|
1732
2030
|
diagnostics,
|
|
1733
|
-
...internalForgotten.length > 0 ? { forgottenExports: internalForgotten } : {}
|
|
2031
|
+
...internalForgotten.length > 0 ? { forgottenExports: internalForgotten } : {},
|
|
2032
|
+
...runtimeMetadata ? { runtimeSchemas: runtimeMetadata } : {}
|
|
1734
2033
|
};
|
|
1735
2034
|
}
|
|
1736
2035
|
function collectAllRefsWithContext(obj, refs, state) {
|
|
@@ -2036,7 +2335,7 @@ function createEmptySpec(entryFile, includeSchema) {
|
|
|
2036
2335
|
return {
|
|
2037
2336
|
...includeSchema ? { $schema: SCHEMA_URL } : {},
|
|
2038
2337
|
openpkg: SCHEMA_VERSION,
|
|
2039
|
-
meta: { name:
|
|
2338
|
+
meta: { name: path3.basename(entryFile, path3.extname(entryFile)) },
|
|
2040
2339
|
exports: [],
|
|
2041
2340
|
generation: {
|
|
2042
2341
|
generator: "@openpkg-ts/extract",
|
|
@@ -2045,18 +2344,18 @@ function createEmptySpec(entryFile, includeSchema) {
|
|
|
2045
2344
|
};
|
|
2046
2345
|
}
|
|
2047
2346
|
async function getPackageMeta(entryFile, baseDir) {
|
|
2048
|
-
const searchDir = baseDir ??
|
|
2049
|
-
const pkgPath =
|
|
2347
|
+
const searchDir = baseDir ?? path3.dirname(entryFile);
|
|
2348
|
+
const pkgPath = path3.join(searchDir, "package.json");
|
|
2050
2349
|
try {
|
|
2051
|
-
if (
|
|
2052
|
-
const pkg = JSON.parse(
|
|
2350
|
+
if (fs2.existsSync(pkgPath)) {
|
|
2351
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
2053
2352
|
return {
|
|
2054
|
-
name: pkg.name ??
|
|
2353
|
+
name: pkg.name ?? path3.basename(searchDir),
|
|
2055
2354
|
version: pkg.version,
|
|
2056
2355
|
description: pkg.description
|
|
2057
2356
|
};
|
|
2058
2357
|
}
|
|
2059
2358
|
} catch {}
|
|
2060
|
-
return { name:
|
|
2359
|
+
return { name: path3.basename(searchDir) };
|
|
2061
2360
|
}
|
|
2062
|
-
export { BUILTIN_TYPE_SCHEMAS, isPrimitiveName, isBuiltinGeneric, isAnonymous, buildSchema, isPureRefSchema, withDescription, schemaIsAny, schemasAreEqual, deduplicateSchemas, findDiscriminatorProperty, TypeRegistry, getJSDocComment, getSourceLocation, getParamDescription, extractTypeParameters, isSymbolDeprecated, createProgram, extractParameters, registerReferencedTypes, serializeClass, serializeEnum, serializeFunctionExport, serializeInterface, serializeTypeAlias, serializeVariable, extract };
|
|
2361
|
+
export { BUILTIN_TYPE_SCHEMAS, isPrimitiveName, isBuiltinGeneric, isAnonymous, buildSchema, isPureRefSchema, withDescription, schemaIsAny, schemasAreEqual, deduplicateSchemas, findDiscriminatorProperty, TypeRegistry, getJSDocComment, getSourceLocation, getParamDescription, extractTypeParameters, isSymbolDeprecated, createProgram, extractParameters, registerReferencedTypes, serializeClass, serializeEnum, serializeFunctionExport, serializeInterface, serializeTypeAlias, serializeVariable, isStandardJSONSchema, resolveCompiledPath, extractStandardSchemas, extractStandardSchemasFromProject, extract };
|
package/dist/src/index.d.ts
CHANGED
|
@@ -73,6 +73,8 @@ interface ExtractOptions {
|
|
|
73
73
|
maxExternalTypeDepth?: number;
|
|
74
74
|
resolveExternalTypes?: boolean;
|
|
75
75
|
schemaExtraction?: "static" | "hybrid";
|
|
76
|
+
/** Target JSON Schema dialect for runtime schema extraction */
|
|
77
|
+
schemaTarget?: "draft-2020-12" | "draft-07" | "openapi-3.0";
|
|
76
78
|
/** Include $schema URL in output */
|
|
77
79
|
includeSchema?: boolean;
|
|
78
80
|
/** Only extract these exports (supports * wildcards) */
|
|
@@ -84,6 +86,17 @@ interface ExtractResult {
|
|
|
84
86
|
spec: OpenPkg;
|
|
85
87
|
diagnostics: Diagnostic[];
|
|
86
88
|
forgottenExports?: ForgottenExport[];
|
|
89
|
+
/** Metadata about runtime schema extraction (when schemaExtraction: 'hybrid') */
|
|
90
|
+
runtimeSchemas?: {
|
|
91
|
+
/** Number of schema exports found */
|
|
92
|
+
extracted: number;
|
|
93
|
+
/** Number of schemas successfully merged with static types */
|
|
94
|
+
merged: number;
|
|
95
|
+
/** Schema vendors detected (e.g., 'zod', 'arktype', 'valibot', 'typebox') */
|
|
96
|
+
vendors: string[];
|
|
97
|
+
/** Any errors encountered during runtime extraction */
|
|
98
|
+
errors: string[];
|
|
99
|
+
};
|
|
87
100
|
}
|
|
88
101
|
interface Diagnostic {
|
|
89
102
|
message: string;
|
|
@@ -112,73 +125,6 @@ interface ForgottenExport {
|
|
|
112
125
|
fix?: string;
|
|
113
126
|
}
|
|
114
127
|
declare function extract(options: ExtractOptions): Promise<ExtractResult>;
|
|
115
|
-
import ts4 from "typescript";
|
|
116
|
-
interface ProgramOptions {
|
|
117
|
-
entryFile: string;
|
|
118
|
-
baseDir?: string;
|
|
119
|
-
content?: string;
|
|
120
|
-
}
|
|
121
|
-
interface ProgramResult {
|
|
122
|
-
program: ts4.Program;
|
|
123
|
-
compilerHost: ts4.CompilerHost;
|
|
124
|
-
compilerOptions: ts4.CompilerOptions;
|
|
125
|
-
sourceFile?: ts4.SourceFile;
|
|
126
|
-
configPath?: string;
|
|
127
|
-
}
|
|
128
|
-
declare function createProgram({ entryFile, baseDir, content }: ProgramOptions): ProgramResult;
|
|
129
|
-
import * as TS from "typescript";
|
|
130
|
-
/**
|
|
131
|
-
* A schema adapter can detect and extract output types from a specific
|
|
132
|
-
* schema validation library.
|
|
133
|
-
*/
|
|
134
|
-
interface SchemaAdapter {
|
|
135
|
-
/** Unique identifier for this adapter */
|
|
136
|
-
readonly id: string;
|
|
137
|
-
/** npm package name(s) this adapter handles */
|
|
138
|
-
readonly packages: readonly string[];
|
|
139
|
-
/**
|
|
140
|
-
* Check if a type matches this adapter's schema library.
|
|
141
|
-
* Should be fast - called for every export.
|
|
142
|
-
*/
|
|
143
|
-
matches(type: TS.Type, checker: TS.TypeChecker): boolean;
|
|
144
|
-
/**
|
|
145
|
-
* Extract the output type from a schema type.
|
|
146
|
-
* Returns null if extraction fails.
|
|
147
|
-
*/
|
|
148
|
-
extractOutputType(type: TS.Type, checker: TS.TypeChecker): TS.Type | null;
|
|
149
|
-
/**
|
|
150
|
-
* Extract the input type from a schema type (optional).
|
|
151
|
-
* Useful for transforms where input differs from output.
|
|
152
|
-
*/
|
|
153
|
-
extractInputType?(type: TS.Type, checker: TS.TypeChecker): TS.Type | null;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Result of schema type extraction
|
|
157
|
-
*/
|
|
158
|
-
interface SchemaExtractionResult {
|
|
159
|
-
/** The adapter that matched */
|
|
160
|
-
adapter: SchemaAdapter;
|
|
161
|
-
/** The extracted output type */
|
|
162
|
-
outputType: TS.Type;
|
|
163
|
-
/** The extracted input type (if different from output) */
|
|
164
|
-
inputType?: TS.Type;
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Utility: Check if type is an object type reference (has type arguments)
|
|
168
|
-
*/
|
|
169
|
-
declare function isTypeReference(type: TS.Type): type is TS.TypeReference;
|
|
170
|
-
/**
|
|
171
|
-
* Utility: Remove undefined/null from a union type
|
|
172
|
-
*/
|
|
173
|
-
declare function getNonNullableType(type: TS.Type): TS.Type;
|
|
174
|
-
declare function registerAdapter(adapter: SchemaAdapter): void;
|
|
175
|
-
declare function findAdapter(type: TS.Type, checker: TS.TypeChecker): SchemaAdapter | undefined;
|
|
176
|
-
declare function isSchemaType(type: TS.Type, checker: TS.TypeChecker): boolean;
|
|
177
|
-
declare function extractSchemaType(type: TS.Type, checker: TS.TypeChecker): SchemaExtractionResult | null;
|
|
178
|
-
declare const arktypeAdapter: SchemaAdapter;
|
|
179
|
-
declare const typeboxAdapter: SchemaAdapter;
|
|
180
|
-
declare const valibotAdapter: SchemaAdapter;
|
|
181
|
-
declare const zodAdapter: SchemaAdapter;
|
|
182
128
|
/**
|
|
183
129
|
* Target version for JSON Schema generation.
|
|
184
130
|
* @see https://standardschema.dev/json-schema
|
|
@@ -254,7 +200,8 @@ interface StandardSchemaExtractionOutput {
|
|
|
254
200
|
declare function isStandardJSONSchema(obj: unknown): obj is StandardJSONSchemaV1;
|
|
255
201
|
/**
|
|
256
202
|
* Resolve compiled JS path from TypeScript source.
|
|
257
|
-
*
|
|
203
|
+
* Reads tsconfig.json for outDir and tries multiple output patterns.
|
|
204
|
+
* Supports .js, .mjs, and .cjs extensions.
|
|
258
205
|
*/
|
|
259
206
|
declare function resolveCompiledPath(tsPath: string, baseDir: string): string | null;
|
|
260
207
|
/**
|
|
@@ -278,24 +225,91 @@ declare function extractStandardSchemas(compiledJsPath: string, options?: Extrac
|
|
|
278
225
|
* @param options - Extraction options
|
|
279
226
|
*/
|
|
280
227
|
declare function extractStandardSchemasFromProject(entryFile: string, baseDir: string, options?: ExtractStandardSchemasOptions): Promise<StandardSchemaExtractionOutput>;
|
|
281
|
-
import
|
|
282
|
-
|
|
283
|
-
|
|
228
|
+
import ts4 from "typescript";
|
|
229
|
+
interface ProgramOptions {
|
|
230
|
+
entryFile: string;
|
|
231
|
+
baseDir?: string;
|
|
232
|
+
content?: string;
|
|
233
|
+
}
|
|
234
|
+
interface ProgramResult {
|
|
235
|
+
program: ts4.Program;
|
|
236
|
+
compilerHost: ts4.CompilerHost;
|
|
237
|
+
compilerOptions: ts4.CompilerOptions;
|
|
238
|
+
sourceFile?: ts4.SourceFile;
|
|
239
|
+
configPath?: string;
|
|
240
|
+
}
|
|
241
|
+
declare function createProgram({ entryFile, baseDir, content }: ProgramOptions): ProgramResult;
|
|
242
|
+
import * as TS from "typescript";
|
|
243
|
+
/**
|
|
244
|
+
* A schema adapter can detect and extract output types from a specific
|
|
245
|
+
* schema validation library.
|
|
246
|
+
*/
|
|
247
|
+
interface SchemaAdapter {
|
|
248
|
+
/** Unique identifier for this adapter */
|
|
249
|
+
readonly id: string;
|
|
250
|
+
/** npm package name(s) this adapter handles */
|
|
251
|
+
readonly packages: readonly string[];
|
|
252
|
+
/**
|
|
253
|
+
* Check if a type matches this adapter's schema library.
|
|
254
|
+
* Should be fast - called for every export.
|
|
255
|
+
*/
|
|
256
|
+
matches(type: TS.Type, checker: TS.TypeChecker): boolean;
|
|
257
|
+
/**
|
|
258
|
+
* Extract the output type from a schema type.
|
|
259
|
+
* Returns null if extraction fails.
|
|
260
|
+
*/
|
|
261
|
+
extractOutputType(type: TS.Type, checker: TS.TypeChecker): TS.Type | null;
|
|
262
|
+
/**
|
|
263
|
+
* Extract the input type from a schema type (optional).
|
|
264
|
+
* Useful for transforms where input differs from output.
|
|
265
|
+
*/
|
|
266
|
+
extractInputType?(type: TS.Type, checker: TS.TypeChecker): TS.Type | null;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Result of schema type extraction
|
|
270
|
+
*/
|
|
271
|
+
interface SchemaExtractionResult {
|
|
272
|
+
/** The adapter that matched */
|
|
273
|
+
adapter: SchemaAdapter;
|
|
274
|
+
/** The extracted output type */
|
|
275
|
+
outputType: TS.Type;
|
|
276
|
+
/** The extracted input type (if different from output) */
|
|
277
|
+
inputType?: TS.Type;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Utility: Check if type is an object type reference (has type arguments)
|
|
281
|
+
*/
|
|
282
|
+
declare function isTypeReference(type: TS.Type): type is TS.TypeReference;
|
|
283
|
+
/**
|
|
284
|
+
* Utility: Remove undefined/null from a union type
|
|
285
|
+
*/
|
|
286
|
+
declare function getNonNullableType(type: TS.Type): TS.Type;
|
|
287
|
+
declare function registerAdapter(adapter: SchemaAdapter): void;
|
|
288
|
+
declare function findAdapter(type: TS.Type, checker: TS.TypeChecker): SchemaAdapter | undefined;
|
|
289
|
+
declare function isSchemaType(type: TS.Type, checker: TS.TypeChecker): boolean;
|
|
290
|
+
declare function extractSchemaType(type: TS.Type, checker: TS.TypeChecker): SchemaExtractionResult | null;
|
|
291
|
+
declare const arktypeAdapter: SchemaAdapter;
|
|
292
|
+
declare const typeboxAdapter: SchemaAdapter;
|
|
293
|
+
declare const valibotAdapter: SchemaAdapter;
|
|
294
|
+
declare const zodAdapter: SchemaAdapter;
|
|
284
295
|
import { SpecExport as SpecExport2 } from "@openpkg-ts/spec";
|
|
285
|
-
import
|
|
286
|
-
declare function
|
|
296
|
+
import ts5 from "typescript";
|
|
297
|
+
declare function serializeClass(node: ts5.ClassDeclaration, ctx: SerializerContext): SpecExport2 | null;
|
|
287
298
|
import { SpecExport as SpecExport3 } from "@openpkg-ts/spec";
|
|
288
|
-
import
|
|
289
|
-
declare function
|
|
299
|
+
import ts6 from "typescript";
|
|
300
|
+
declare function serializeEnum(node: ts6.EnumDeclaration, ctx: SerializerContext): SpecExport3 | null;
|
|
290
301
|
import { SpecExport as SpecExport4 } from "@openpkg-ts/spec";
|
|
291
|
-
import
|
|
292
|
-
declare function
|
|
302
|
+
import ts7 from "typescript";
|
|
303
|
+
declare function serializeFunctionExport(node: ts7.FunctionDeclaration | ts7.ArrowFunction, ctx: SerializerContext): SpecExport4 | null;
|
|
293
304
|
import { SpecExport as SpecExport5 } from "@openpkg-ts/spec";
|
|
294
|
-
import
|
|
295
|
-
declare function
|
|
305
|
+
import ts8 from "typescript";
|
|
306
|
+
declare function serializeInterface(node: ts8.InterfaceDeclaration, ctx: SerializerContext): SpecExport5 | null;
|
|
296
307
|
import { SpecExport as SpecExport6 } from "@openpkg-ts/spec";
|
|
308
|
+
import ts9 from "typescript";
|
|
309
|
+
declare function serializeTypeAlias(node: ts9.TypeAliasDeclaration, ctx: SerializerContext): SpecExport6 | null;
|
|
310
|
+
import { SpecExport as SpecExport7 } from "@openpkg-ts/spec";
|
|
297
311
|
import ts10 from "typescript";
|
|
298
|
-
declare function serializeVariable(node: ts10.VariableDeclaration, statement: ts10.VariableStatement, ctx: SerializerContext):
|
|
312
|
+
declare function serializeVariable(node: ts10.VariableDeclaration, statement: ts10.VariableStatement, ctx: SerializerContext): SpecExport7 | null;
|
|
299
313
|
import { SpecSignatureParameter } from "@openpkg-ts/spec";
|
|
300
314
|
import ts11 from "typescript";
|
|
301
315
|
declare function extractParameters(signature: ts11.Signature, ctx: SerializerContext): SpecSignatureParameter[];
|
package/dist/src/index.js
CHANGED
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
deduplicateSchemas,
|
|
7
7
|
extract,
|
|
8
8
|
extractParameters,
|
|
9
|
+
extractStandardSchemas,
|
|
10
|
+
extractStandardSchemasFromProject,
|
|
9
11
|
extractTypeParameters,
|
|
10
12
|
findDiscriminatorProperty,
|
|
11
13
|
getJSDocComment,
|
|
@@ -15,8 +17,10 @@ import {
|
|
|
15
17
|
isBuiltinGeneric,
|
|
16
18
|
isPrimitiveName,
|
|
17
19
|
isPureRefSchema,
|
|
20
|
+
isStandardJSONSchema,
|
|
18
21
|
isSymbolDeprecated,
|
|
19
22
|
registerReferencedTypes,
|
|
23
|
+
resolveCompiledPath,
|
|
20
24
|
schemaIsAny,
|
|
21
25
|
schemasAreEqual,
|
|
22
26
|
serializeClass,
|
|
@@ -26,7 +30,7 @@ import {
|
|
|
26
30
|
serializeTypeAlias,
|
|
27
31
|
serializeVariable,
|
|
28
32
|
withDescription
|
|
29
|
-
} from "../shared/chunk-
|
|
33
|
+
} from "../shared/chunk-yh8v9dbt.js";
|
|
30
34
|
// src/schema/registry.ts
|
|
31
35
|
function isTypeReference(type) {
|
|
32
36
|
return !!(type.flags & 524288 && type.objectFlags && type.objectFlags & 4);
|
|
@@ -57,7 +61,7 @@ function extractSchemaType(type, checker) {
|
|
|
57
61
|
const outputType = adapter.extractOutputType(type, checker);
|
|
58
62
|
if (!outputType)
|
|
59
63
|
return null;
|
|
60
|
-
const inputType = adapter.extractInputType?.(type, checker);
|
|
64
|
+
const inputType = adapter.extractInputType?.(type, checker) ?? undefined;
|
|
61
65
|
return {
|
|
62
66
|
adapter,
|
|
63
67
|
outputType,
|
|
@@ -189,200 +193,6 @@ registerAdapter(zodAdapter);
|
|
|
189
193
|
registerAdapter(valibotAdapter);
|
|
190
194
|
registerAdapter(arktypeAdapter);
|
|
191
195
|
registerAdapter(typeboxAdapter);
|
|
192
|
-
// src/schema/standard-schema.ts
|
|
193
|
-
import { spawn } from "node:child_process";
|
|
194
|
-
import * as fs from "node:fs";
|
|
195
|
-
import * as path from "node:path";
|
|
196
|
-
function isStandardJSONSchema(obj) {
|
|
197
|
-
if (typeof obj !== "object" || obj === null)
|
|
198
|
-
return false;
|
|
199
|
-
const std = obj["~standard"];
|
|
200
|
-
if (typeof std !== "object" || std === null)
|
|
201
|
-
return false;
|
|
202
|
-
const stdObj = std;
|
|
203
|
-
if (stdObj.version !== 1)
|
|
204
|
-
return false;
|
|
205
|
-
if (typeof stdObj.vendor !== "string")
|
|
206
|
-
return false;
|
|
207
|
-
const jsonSchema = stdObj.jsonSchema;
|
|
208
|
-
if (typeof jsonSchema !== "object" || jsonSchema === null)
|
|
209
|
-
return false;
|
|
210
|
-
const jsObj = jsonSchema;
|
|
211
|
-
return typeof jsObj.output === "function" && typeof jsObj.input === "function";
|
|
212
|
-
}
|
|
213
|
-
var WORKER_SCRIPT = `
|
|
214
|
-
const path = require('path');
|
|
215
|
-
const { pathToFileURL } = require('url');
|
|
216
|
-
|
|
217
|
-
// TypeBox detection: schemas have Symbol.for('TypeBox.Kind') and are JSON Schema
|
|
218
|
-
const TYPEBOX_KIND = Symbol.for('TypeBox.Kind');
|
|
219
|
-
|
|
220
|
-
function isTypeBoxSchema(obj) {
|
|
221
|
-
if (!obj || typeof obj !== 'object') return false;
|
|
222
|
-
// TypeBox schemas always have Kind symbol (Union, Object, String, etc.)
|
|
223
|
-
// Also check for common JSON Schema props to avoid false positives
|
|
224
|
-
if (!obj[TYPEBOX_KIND]) return false;
|
|
225
|
-
return typeof obj.type === 'string' || 'anyOf' in obj || 'oneOf' in obj || 'allOf' in obj;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function sanitizeTypeBoxSchema(schema) {
|
|
229
|
-
// JSON.stringify removes symbol keys, keeping only JSON Schema props
|
|
230
|
-
return JSON.parse(JSON.stringify(schema));
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async function extract() {
|
|
234
|
-
// With node -e, argv is: [node, arg1, arg2, ...]
|
|
235
|
-
// (the -e script is NOT in argv)
|
|
236
|
-
const [modulePath, optionsJson] = process.argv.slice(1);
|
|
237
|
-
const { target, libraryOptions } = JSON.parse(optionsJson || '{}');
|
|
238
|
-
|
|
239
|
-
try {
|
|
240
|
-
// Import the module using dynamic import (works with ESM and CJS)
|
|
241
|
-
const absPath = path.resolve(modulePath);
|
|
242
|
-
const mod = await import(pathToFileURL(absPath).href);
|
|
243
|
-
const results = [];
|
|
244
|
-
|
|
245
|
-
// Build exports map - handle both ESM and CJS (where exports are in mod.default)
|
|
246
|
-
const exports = {};
|
|
247
|
-
for (const [name, value] of Object.entries(mod)) {
|
|
248
|
-
if (name === 'default' && typeof value === 'object' && value !== null) {
|
|
249
|
-
// CJS module: spread default exports
|
|
250
|
-
Object.assign(exports, value);
|
|
251
|
-
} else if (name !== 'default') {
|
|
252
|
-
exports[name] = value;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Check each export
|
|
257
|
-
for (const [name, value] of Object.entries(exports)) {
|
|
258
|
-
if (name.startsWith('_')) continue;
|
|
259
|
-
if (typeof value !== 'object' || value === null) continue;
|
|
260
|
-
|
|
261
|
-
// Priority 1: Standard JSON Schema (Zod 4.2+, ArkType 2.1.28+, Valibot 1.2+)
|
|
262
|
-
const std = value['~standard'];
|
|
263
|
-
if (std && typeof std === 'object' && std.version === 1 && typeof std.vendor === 'string' && std.jsonSchema && typeof std.jsonSchema.output === 'function') {
|
|
264
|
-
try {
|
|
265
|
-
// Per spec: pass options object with target and optional libraryOptions
|
|
266
|
-
const options = { target: target || 'draft-2020-12', ...(libraryOptions && { libraryOptions }) };
|
|
267
|
-
const outputSchema = std.jsonSchema.output(options);
|
|
268
|
-
const inputSchema = typeof std.jsonSchema.input === 'function' ? std.jsonSchema.input(options) : undefined;
|
|
269
|
-
results.push({
|
|
270
|
-
exportName: name,
|
|
271
|
-
vendor: std.vendor,
|
|
272
|
-
outputSchema,
|
|
273
|
-
inputSchema
|
|
274
|
-
});
|
|
275
|
-
} catch (e) {
|
|
276
|
-
// Skip schemas that fail to extract
|
|
277
|
-
}
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Priority 2: TypeBox (schema IS JSON Schema)
|
|
282
|
-
if (isTypeBoxSchema(value)) {
|
|
283
|
-
try {
|
|
284
|
-
results.push({
|
|
285
|
-
exportName: name,
|
|
286
|
-
vendor: 'typebox',
|
|
287
|
-
outputSchema: sanitizeTypeBoxSchema(value)
|
|
288
|
-
});
|
|
289
|
-
} catch (e) {
|
|
290
|
-
// Skip schemas that fail to extract
|
|
291
|
-
}
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
console.log(JSON.stringify({ success: true, results }));
|
|
297
|
-
} catch (e) {
|
|
298
|
-
console.log(JSON.stringify({ success: false, error: e.message }));
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
extract();
|
|
303
|
-
`;
|
|
304
|
-
function resolveCompiledPath(tsPath, baseDir) {
|
|
305
|
-
const relativePath = path.relative(baseDir, tsPath);
|
|
306
|
-
const withoutExt = relativePath.replace(/\.tsx?$/, "");
|
|
307
|
-
const candidates = [
|
|
308
|
-
path.join(baseDir, `${withoutExt}.js`),
|
|
309
|
-
path.join(baseDir, "dist", `${withoutExt.replace(/^src\//, "")}.js`),
|
|
310
|
-
path.join(baseDir, "build", `${withoutExt.replace(/^src\//, "")}.js`),
|
|
311
|
-
path.join(baseDir, "lib", `${withoutExt.replace(/^src\//, "")}.js`)
|
|
312
|
-
];
|
|
313
|
-
for (const candidate of candidates) {
|
|
314
|
-
if (fs.existsSync(candidate)) {
|
|
315
|
-
return candidate;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
return null;
|
|
319
|
-
}
|
|
320
|
-
async function extractStandardSchemas(compiledJsPath, options = {}) {
|
|
321
|
-
const { timeout = 1e4, target = "draft-2020-12", libraryOptions } = options;
|
|
322
|
-
const result = {
|
|
323
|
-
schemas: new Map,
|
|
324
|
-
errors: []
|
|
325
|
-
};
|
|
326
|
-
if (!fs.existsSync(compiledJsPath)) {
|
|
327
|
-
result.errors.push(`Compiled JS not found: ${compiledJsPath}`);
|
|
328
|
-
return result;
|
|
329
|
-
}
|
|
330
|
-
const optionsJson = JSON.stringify({ target, libraryOptions });
|
|
331
|
-
return new Promise((resolve) => {
|
|
332
|
-
const child = spawn("node", ["-e", WORKER_SCRIPT, compiledJsPath, optionsJson], {
|
|
333
|
-
timeout,
|
|
334
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
335
|
-
});
|
|
336
|
-
let stdout = "";
|
|
337
|
-
let stderr = "";
|
|
338
|
-
child.stdout.on("data", (data) => {
|
|
339
|
-
stdout += data.toString();
|
|
340
|
-
});
|
|
341
|
-
child.stderr.on("data", (data) => {
|
|
342
|
-
stderr += data.toString();
|
|
343
|
-
});
|
|
344
|
-
child.on("close", (code) => {
|
|
345
|
-
if (code !== 0) {
|
|
346
|
-
result.errors.push(`Extraction process failed: ${stderr || `exit code ${code}`}`);
|
|
347
|
-
resolve(result);
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
try {
|
|
351
|
-
const parsed = JSON.parse(stdout);
|
|
352
|
-
if (!parsed.success) {
|
|
353
|
-
result.errors.push(`Extraction failed: ${parsed.error}`);
|
|
354
|
-
resolve(result);
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
for (const item of parsed.results) {
|
|
358
|
-
result.schemas.set(item.exportName, {
|
|
359
|
-
exportName: item.exportName,
|
|
360
|
-
vendor: item.vendor,
|
|
361
|
-
outputSchema: item.outputSchema,
|
|
362
|
-
inputSchema: item.inputSchema
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
} catch (e) {
|
|
366
|
-
result.errors.push(`Failed to parse extraction output: ${e}`);
|
|
367
|
-
}
|
|
368
|
-
resolve(result);
|
|
369
|
-
});
|
|
370
|
-
child.on("error", (err) => {
|
|
371
|
-
result.errors.push(`Subprocess error: ${err.message}`);
|
|
372
|
-
resolve(result);
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
async function extractStandardSchemasFromProject(entryFile, baseDir, options = {}) {
|
|
377
|
-
const compiledPath = resolveCompiledPath(entryFile, baseDir);
|
|
378
|
-
if (!compiledPath) {
|
|
379
|
-
return {
|
|
380
|
-
schemas: new Map,
|
|
381
|
-
errors: [`Could not find compiled JS for ${entryFile}. Build the project first.`]
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
return extractStandardSchemas(compiledPath, options);
|
|
385
|
-
}
|
|
386
196
|
// src/types/utils.ts
|
|
387
197
|
function isExported(node) {
|
|
388
198
|
const modifiers = node.modifiers;
|