@openpkg-ts/extract 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/bin/tspec.js +10 -2
- package/dist/shared/{chunk-rgx6dspw.js → chunk-yh8v9dbt.js} +323 -22
- package/dist/src/index.d.ts +126 -82
- package/dist/src/index.js +6 -192
- 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) {
|
|
@@ -1686,7 +1948,7 @@ async function extract(options) {
|
|
|
1686
1948
|
}
|
|
1687
1949
|
const meta = await getPackageMeta(entryFile, baseDir);
|
|
1688
1950
|
const types = ctx.typeRegistry.getAll();
|
|
1689
|
-
const forgottenExports = collectForgottenExports(exports, types, program, sourceFile);
|
|
1951
|
+
const forgottenExports = collectForgottenExports(exports, types, program, sourceFile, exportedIds);
|
|
1690
1952
|
for (const forgotten of forgottenExports) {
|
|
1691
1953
|
const refSummary = forgotten.referencedBy.slice(0, 3).map((r) => `${r.exportName} (${r.location})`).join(", ");
|
|
1692
1954
|
const moreRefs = forgotten.referencedBy.length > 3 ? ` +${forgotten.referencedBy.length - 3} more` : "";
|
|
@@ -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) {
|
|
@@ -1816,7 +2115,7 @@ function hasInternalTag(typeName, program, sourceFile) {
|
|
|
1816
2115
|
const jsTags = symbol.getJsDocTags();
|
|
1817
2116
|
return jsTags.some((tag) => tag.name === "internal");
|
|
1818
2117
|
}
|
|
1819
|
-
function collectForgottenExports(exports, types, program, sourceFile) {
|
|
2118
|
+
function collectForgottenExports(exports, types, program, sourceFile, exportedIds) {
|
|
1820
2119
|
const definedTypes = new Set(types.map((t) => t.id));
|
|
1821
2120
|
const referencedTypes = new Map;
|
|
1822
2121
|
for (const exp of exports) {
|
|
@@ -1843,6 +2142,8 @@ function collectForgottenExports(exports, types, program, sourceFile) {
|
|
|
1843
2142
|
continue;
|
|
1844
2143
|
if (hasInternalTag(typeName, program, sourceFile))
|
|
1845
2144
|
continue;
|
|
2145
|
+
if (exportedIds.has(typeName))
|
|
2146
|
+
continue;
|
|
1846
2147
|
const definedIn = findTypeDefinition(typeName, program, sourceFile);
|
|
1847
2148
|
const isExternal = isExternalType2(definedIn);
|
|
1848
2149
|
forgottenExports.push({
|
|
@@ -2034,7 +2335,7 @@ function createEmptySpec(entryFile, includeSchema) {
|
|
|
2034
2335
|
return {
|
|
2035
2336
|
...includeSchema ? { $schema: SCHEMA_URL } : {},
|
|
2036
2337
|
openpkg: SCHEMA_VERSION,
|
|
2037
|
-
meta: { name:
|
|
2338
|
+
meta: { name: path3.basename(entryFile, path3.extname(entryFile)) },
|
|
2038
2339
|
exports: [],
|
|
2039
2340
|
generation: {
|
|
2040
2341
|
generator: "@openpkg-ts/extract",
|
|
@@ -2043,18 +2344,18 @@ function createEmptySpec(entryFile, includeSchema) {
|
|
|
2043
2344
|
};
|
|
2044
2345
|
}
|
|
2045
2346
|
async function getPackageMeta(entryFile, baseDir) {
|
|
2046
|
-
const searchDir = baseDir ??
|
|
2047
|
-
const pkgPath =
|
|
2347
|
+
const searchDir = baseDir ?? path3.dirname(entryFile);
|
|
2348
|
+
const pkgPath = path3.join(searchDir, "package.json");
|
|
2048
2349
|
try {
|
|
2049
|
-
if (
|
|
2050
|
-
const pkg = JSON.parse(
|
|
2350
|
+
if (fs2.existsSync(pkgPath)) {
|
|
2351
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
2051
2352
|
return {
|
|
2052
|
-
name: pkg.name ??
|
|
2353
|
+
name: pkg.name ?? path3.basename(searchDir),
|
|
2053
2354
|
version: pkg.version,
|
|
2054
2355
|
description: pkg.description
|
|
2055
2356
|
};
|
|
2056
2357
|
}
|
|
2057
2358
|
} catch {}
|
|
2058
|
-
return { name:
|
|
2359
|
+
return { name: path3.basename(searchDir) };
|
|
2059
2360
|
}
|
|
2060
|
-
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,6 +125,106 @@ interface ForgottenExport {
|
|
|
112
125
|
fix?: string;
|
|
113
126
|
}
|
|
114
127
|
declare function extract(options: ExtractOptions): Promise<ExtractResult>;
|
|
128
|
+
/**
|
|
129
|
+
* Target version for JSON Schema generation.
|
|
130
|
+
* @see https://standardschema.dev/json-schema
|
|
131
|
+
*/
|
|
132
|
+
type StandardJSONSchemaTarget = "draft-2020-12" | "draft-07" | "openapi-3.0" | (string & {});
|
|
133
|
+
/**
|
|
134
|
+
* Options for JSON Schema generation methods.
|
|
135
|
+
*/
|
|
136
|
+
interface StandardJSONSchemaOptions {
|
|
137
|
+
/** Specifies the target version of the generated JSON Schema */
|
|
138
|
+
readonly target: StandardJSONSchemaTarget;
|
|
139
|
+
/** Vendor-specific parameters */
|
|
140
|
+
readonly libraryOptions?: Record<string, unknown>;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Standard JSON Schema v1 interface.
|
|
144
|
+
* @see https://standardschema.dev/json-schema
|
|
145
|
+
*/
|
|
146
|
+
interface StandardJSONSchemaV1<
|
|
147
|
+
Input = unknown,
|
|
148
|
+
Output = Input
|
|
149
|
+
> {
|
|
150
|
+
"~standard": {
|
|
151
|
+
/** The version number of the standard (always 1) */
|
|
152
|
+
version: 1;
|
|
153
|
+
/** The vendor name of the schema library */
|
|
154
|
+
vendor: string;
|
|
155
|
+
/** Inferred types (optional) */
|
|
156
|
+
types?: {
|
|
157
|
+
input: Input;
|
|
158
|
+
output: Output;
|
|
159
|
+
};
|
|
160
|
+
/** JSON Schema conversion methods */
|
|
161
|
+
jsonSchema: {
|
|
162
|
+
/** Converts input type to JSON Schema */
|
|
163
|
+
input: (options: StandardJSONSchemaOptions) => Record<string, unknown>;
|
|
164
|
+
/** Converts output type to JSON Schema */
|
|
165
|
+
output: (options: StandardJSONSchemaOptions) => Record<string, unknown>;
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Result of extracting Standard Schema from an export.
|
|
171
|
+
*/
|
|
172
|
+
interface StandardSchemaExtractionResult {
|
|
173
|
+
exportName: string;
|
|
174
|
+
vendor: string;
|
|
175
|
+
outputSchema: Record<string, unknown>;
|
|
176
|
+
inputSchema?: Record<string, unknown>;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Options for runtime Standard Schema extraction.
|
|
180
|
+
*/
|
|
181
|
+
interface ExtractStandardSchemasOptions {
|
|
182
|
+
/** Timeout in milliseconds (default: 10000) */
|
|
183
|
+
timeout?: number;
|
|
184
|
+
/** JSON Schema target version (default: 'draft-2020-12') */
|
|
185
|
+
target?: StandardJSONSchemaTarget;
|
|
186
|
+
/** Vendor-specific options to pass through */
|
|
187
|
+
libraryOptions?: Record<string, unknown>;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Result of Standard Schema extraction.
|
|
191
|
+
*/
|
|
192
|
+
interface StandardSchemaExtractionOutput {
|
|
193
|
+
schemas: Map<string, StandardSchemaExtractionResult>;
|
|
194
|
+
errors: string[];
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Check if an object implements StandardJSONSchemaV1.
|
|
198
|
+
* This is a static type guard - doesn't require runtime.
|
|
199
|
+
*/
|
|
200
|
+
declare function isStandardJSONSchema(obj: unknown): obj is StandardJSONSchemaV1;
|
|
201
|
+
/**
|
|
202
|
+
* Resolve compiled JS path from TypeScript source.
|
|
203
|
+
* Reads tsconfig.json for outDir and tries multiple output patterns.
|
|
204
|
+
* Supports .js, .mjs, and .cjs extensions.
|
|
205
|
+
*/
|
|
206
|
+
declare function resolveCompiledPath(tsPath: string, baseDir: string): string | null;
|
|
207
|
+
/**
|
|
208
|
+
* Extract Standard Schema JSON Schemas from a compiled JS module.
|
|
209
|
+
*
|
|
210
|
+
* **Security Note**: This executes the module in a subprocess.
|
|
211
|
+
* Only use with trusted code (user's own packages).
|
|
212
|
+
*
|
|
213
|
+
* @param compiledJsPath - Path to compiled .js file
|
|
214
|
+
* @param options - Extraction options
|
|
215
|
+
* @returns Extraction results with schemas and any errors
|
|
216
|
+
*/
|
|
217
|
+
declare function extractStandardSchemas(compiledJsPath: string, options?: ExtractStandardSchemasOptions): Promise<StandardSchemaExtractionOutput>;
|
|
218
|
+
/**
|
|
219
|
+
* Extract Standard Schema from a TypeScript project.
|
|
220
|
+
*
|
|
221
|
+
* Convenience function that resolves compiled JS and extracts schemas.
|
|
222
|
+
*
|
|
223
|
+
* @param entryFile - TypeScript entry file path
|
|
224
|
+
* @param baseDir - Project base directory
|
|
225
|
+
* @param options - Extraction options
|
|
226
|
+
*/
|
|
227
|
+
declare function extractStandardSchemasFromProject(entryFile: string, baseDir: string, options?: ExtractStandardSchemasOptions): Promise<StandardSchemaExtractionOutput>;
|
|
115
228
|
import ts4 from "typescript";
|
|
116
229
|
interface ProgramOptions {
|
|
117
230
|
entryFile: string;
|
|
@@ -179,93 +292,24 @@ declare const arktypeAdapter: SchemaAdapter;
|
|
|
179
292
|
declare const typeboxAdapter: SchemaAdapter;
|
|
180
293
|
declare const valibotAdapter: SchemaAdapter;
|
|
181
294
|
declare const zodAdapter: SchemaAdapter;
|
|
182
|
-
/**
|
|
183
|
-
* Standard JSON Schema v1 interface (minimal for detection).
|
|
184
|
-
*/
|
|
185
|
-
interface StandardJSONSchemaV1 {
|
|
186
|
-
"~standard": {
|
|
187
|
-
version: number;
|
|
188
|
-
vendor: string;
|
|
189
|
-
jsonSchema?: {
|
|
190
|
-
output: (target?: string) => Record<string, unknown>;
|
|
191
|
-
input?: (target?: string) => Record<string, unknown>;
|
|
192
|
-
};
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Result of extracting Standard Schema from an export.
|
|
197
|
-
*/
|
|
198
|
-
interface StandardSchemaExtractionResult {
|
|
199
|
-
exportName: string;
|
|
200
|
-
vendor: string;
|
|
201
|
-
outputSchema: Record<string, unknown>;
|
|
202
|
-
inputSchema?: Record<string, unknown>;
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Options for runtime Standard Schema extraction.
|
|
206
|
-
*/
|
|
207
|
-
interface ExtractStandardSchemasOptions {
|
|
208
|
-
/** Timeout in milliseconds (default: 10000) */
|
|
209
|
-
timeout?: number;
|
|
210
|
-
/** JSON Schema target version (default: 'draft-2020-12') */
|
|
211
|
-
target?: "draft-2020-12" | "draft-07" | "openapi-3.0";
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Result of Standard Schema extraction.
|
|
215
|
-
*/
|
|
216
|
-
interface StandardSchemaExtractionOutput {
|
|
217
|
-
schemas: Map<string, StandardSchemaExtractionResult>;
|
|
218
|
-
errors: string[];
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Check if an object implements StandardJSONSchemaV1.
|
|
222
|
-
* This is a static type guard - doesn't require runtime.
|
|
223
|
-
*/
|
|
224
|
-
declare function isStandardJSONSchema(obj: unknown): obj is StandardJSONSchemaV1;
|
|
225
|
-
/**
|
|
226
|
-
* Resolve compiled JS path from TypeScript source.
|
|
227
|
-
* Tries common output locations: dist/, build/, lib/, same dir.
|
|
228
|
-
*/
|
|
229
|
-
declare function resolveCompiledPath(tsPath: string, baseDir: string): string | null;
|
|
230
|
-
/**
|
|
231
|
-
* Extract Standard Schema JSON Schemas from a compiled JS module.
|
|
232
|
-
*
|
|
233
|
-
* **Security Note**: This executes the module in a subprocess.
|
|
234
|
-
* Only use with trusted code (user's own packages).
|
|
235
|
-
*
|
|
236
|
-
* @param compiledJsPath - Path to compiled .js file
|
|
237
|
-
* @param options - Extraction options
|
|
238
|
-
* @returns Extraction results with schemas and any errors
|
|
239
|
-
*/
|
|
240
|
-
declare function extractStandardSchemas(compiledJsPath: string, options?: ExtractStandardSchemasOptions): Promise<StandardSchemaExtractionOutput>;
|
|
241
|
-
/**
|
|
242
|
-
* Extract Standard Schema from a TypeScript project.
|
|
243
|
-
*
|
|
244
|
-
* Convenience function that resolves compiled JS and extracts schemas.
|
|
245
|
-
*
|
|
246
|
-
* @param entryFile - TypeScript entry file path
|
|
247
|
-
* @param baseDir - Project base directory
|
|
248
|
-
* @param options - Extraction options
|
|
249
|
-
*/
|
|
250
|
-
declare function extractStandardSchemasFromProject(entryFile: string, baseDir: string, options?: ExtractStandardSchemasOptions): Promise<StandardSchemaExtractionOutput>;
|
|
251
|
-
import { SpecExport } from "@openpkg-ts/spec";
|
|
252
|
-
import ts5 from "typescript";
|
|
253
|
-
declare function serializeClass(node: ts5.ClassDeclaration, ctx: SerializerContext): SpecExport | null;
|
|
254
295
|
import { SpecExport as SpecExport2 } from "@openpkg-ts/spec";
|
|
255
|
-
import
|
|
256
|
-
declare function
|
|
296
|
+
import ts5 from "typescript";
|
|
297
|
+
declare function serializeClass(node: ts5.ClassDeclaration, ctx: SerializerContext): SpecExport2 | null;
|
|
257
298
|
import { SpecExport as SpecExport3 } from "@openpkg-ts/spec";
|
|
258
|
-
import
|
|
259
|
-
declare function
|
|
299
|
+
import ts6 from "typescript";
|
|
300
|
+
declare function serializeEnum(node: ts6.EnumDeclaration, ctx: SerializerContext): SpecExport3 | null;
|
|
260
301
|
import { SpecExport as SpecExport4 } from "@openpkg-ts/spec";
|
|
261
|
-
import
|
|
262
|
-
declare function
|
|
302
|
+
import ts7 from "typescript";
|
|
303
|
+
declare function serializeFunctionExport(node: ts7.FunctionDeclaration | ts7.ArrowFunction, ctx: SerializerContext): SpecExport4 | null;
|
|
263
304
|
import { SpecExport as SpecExport5 } from "@openpkg-ts/spec";
|
|
264
|
-
import
|
|
265
|
-
declare function
|
|
305
|
+
import ts8 from "typescript";
|
|
306
|
+
declare function serializeInterface(node: ts8.InterfaceDeclaration, ctx: SerializerContext): SpecExport5 | null;
|
|
266
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";
|
|
267
311
|
import ts10 from "typescript";
|
|
268
|
-
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;
|
|
269
313
|
import { SpecSignatureParameter } from "@openpkg-ts/spec";
|
|
270
314
|
import ts11 from "typescript";
|
|
271
315
|
declare function extractParameters(signature: ts11.Signature, ctx: SerializerContext): SpecSignatureParameter[];
|
|
@@ -329,4 +373,4 @@ declare function findDiscriminatorProperty(unionTypes: ts12.Type[], checker: ts1
|
|
|
329
373
|
import ts13 from "typescript";
|
|
330
374
|
declare function isExported(node: ts13.Node): boolean;
|
|
331
375
|
declare function getNodeName(node: ts13.Node): string | undefined;
|
|
332
|
-
export { zodAdapter, withDescription, valibotAdapter, typeboxAdapter, serializeVariable, serializeTypeAlias, serializeInterface, serializeFunctionExport, serializeEnum, serializeClass, schemasAreEqual, schemaIsAny, resolveCompiledPath, registerReferencedTypes, registerAdapter, isTypeReference, isSymbolDeprecated, isStandardJSONSchema, isSchemaType, isPureRefSchema, isPrimitiveName, isExported, isBuiltinGeneric, isAnonymous, getSourceLocation, getParamDescription, getNonNullableType, getNodeName, getJSDocComment, findDiscriminatorProperty, findAdapter, extractTypeParameters, extractStandardSchemasFromProject, extractStandardSchemas, extractSchemaType, extractParameters, extract, deduplicateSchemas, createProgram, buildSchema, arktypeAdapter, TypeRegistry, TypeReference2 as TypeReference, StandardSchemaExtractionResult, StandardSchemaExtractionOutput, StandardJSONSchemaV1, SerializerContext, SchemaExtractionResult, SchemaAdapter, ProgramResult, ProgramOptions, ForgottenExport, ExtractStandardSchemasOptions, ExtractResult, ExtractOptions, Diagnostic, BUILTIN_TYPE_SCHEMAS };
|
|
376
|
+
export { zodAdapter, withDescription, valibotAdapter, typeboxAdapter, serializeVariable, serializeTypeAlias, serializeInterface, serializeFunctionExport, serializeEnum, serializeClass, schemasAreEqual, schemaIsAny, resolveCompiledPath, registerReferencedTypes, registerAdapter, isTypeReference, isSymbolDeprecated, isStandardJSONSchema, isSchemaType, isPureRefSchema, isPrimitiveName, isExported, isBuiltinGeneric, isAnonymous, getSourceLocation, getParamDescription, getNonNullableType, getNodeName, getJSDocComment, findDiscriminatorProperty, findAdapter, extractTypeParameters, extractStandardSchemasFromProject, extractStandardSchemas, extractSchemaType, extractParameters, extract, deduplicateSchemas, createProgram, buildSchema, arktypeAdapter, TypeRegistry, TypeReference2 as TypeReference, StandardSchemaExtractionResult, StandardSchemaExtractionOutput, StandardJSONSchemaV1, StandardJSONSchemaTarget, StandardJSONSchemaOptions, SerializerContext, SchemaExtractionResult, SchemaAdapter, ProgramResult, ProgramOptions, ForgottenExport, ExtractStandardSchemasOptions, ExtractResult, ExtractOptions, Diagnostic, BUILTIN_TYPE_SCHEMAS };
|
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,196 +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 (typeof stdObj.version !== "number")
|
|
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";
|
|
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, target] = process.argv.slice(1);
|
|
237
|
-
|
|
238
|
-
try {
|
|
239
|
-
// Import the module using dynamic import (works with ESM and CJS)
|
|
240
|
-
const absPath = path.resolve(modulePath);
|
|
241
|
-
const mod = await import(pathToFileURL(absPath).href);
|
|
242
|
-
const results = [];
|
|
243
|
-
|
|
244
|
-
// Build exports map - handle both ESM and CJS (where exports are in mod.default)
|
|
245
|
-
const exports = {};
|
|
246
|
-
for (const [name, value] of Object.entries(mod)) {
|
|
247
|
-
if (name === 'default' && typeof value === 'object' && value !== null) {
|
|
248
|
-
// CJS module: spread default exports
|
|
249
|
-
Object.assign(exports, value);
|
|
250
|
-
} else if (name !== 'default') {
|
|
251
|
-
exports[name] = value;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Check each export
|
|
256
|
-
for (const [name, value] of Object.entries(exports)) {
|
|
257
|
-
if (name.startsWith('_')) continue;
|
|
258
|
-
if (typeof value !== 'object' || value === null) continue;
|
|
259
|
-
|
|
260
|
-
// Priority 1: Standard Schema (Zod 4.2+, ArkType, etc.)
|
|
261
|
-
const std = value['~standard'];
|
|
262
|
-
if (std && typeof std === 'object' && typeof std.version === 'number' && typeof std.vendor === 'string' && std.jsonSchema && typeof std.jsonSchema.output === 'function') {
|
|
263
|
-
try {
|
|
264
|
-
const outputSchema = std.jsonSchema.output(target);
|
|
265
|
-
const inputSchema = std.jsonSchema.input ? std.jsonSchema.input(target) : undefined;
|
|
266
|
-
results.push({
|
|
267
|
-
exportName: name,
|
|
268
|
-
vendor: std.vendor,
|
|
269
|
-
outputSchema,
|
|
270
|
-
inputSchema
|
|
271
|
-
});
|
|
272
|
-
} catch (e) {
|
|
273
|
-
// Skip schemas that fail to extract
|
|
274
|
-
}
|
|
275
|
-
continue;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Priority 2: TypeBox (schema IS JSON Schema)
|
|
279
|
-
if (isTypeBoxSchema(value)) {
|
|
280
|
-
try {
|
|
281
|
-
results.push({
|
|
282
|
-
exportName: name,
|
|
283
|
-
vendor: 'typebox',
|
|
284
|
-
outputSchema: sanitizeTypeBoxSchema(value)
|
|
285
|
-
});
|
|
286
|
-
} catch (e) {
|
|
287
|
-
// Skip schemas that fail to extract
|
|
288
|
-
}
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
console.log(JSON.stringify({ success: true, results }));
|
|
294
|
-
} catch (e) {
|
|
295
|
-
console.log(JSON.stringify({ success: false, error: e.message }));
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
extract();
|
|
300
|
-
`;
|
|
301
|
-
function resolveCompiledPath(tsPath, baseDir) {
|
|
302
|
-
const relativePath = path.relative(baseDir, tsPath);
|
|
303
|
-
const withoutExt = relativePath.replace(/\.tsx?$/, "");
|
|
304
|
-
const candidates = [
|
|
305
|
-
path.join(baseDir, `${withoutExt}.js`),
|
|
306
|
-
path.join(baseDir, "dist", `${withoutExt.replace(/^src\//, "")}.js`),
|
|
307
|
-
path.join(baseDir, "build", `${withoutExt.replace(/^src\//, "")}.js`),
|
|
308
|
-
path.join(baseDir, "lib", `${withoutExt.replace(/^src\//, "")}.js`)
|
|
309
|
-
];
|
|
310
|
-
for (const candidate of candidates) {
|
|
311
|
-
if (fs.existsSync(candidate)) {
|
|
312
|
-
return candidate;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return null;
|
|
316
|
-
}
|
|
317
|
-
async function extractStandardSchemas(compiledJsPath, options = {}) {
|
|
318
|
-
const { timeout = 1e4, target = "draft-2020-12" } = options;
|
|
319
|
-
const result = {
|
|
320
|
-
schemas: new Map,
|
|
321
|
-
errors: []
|
|
322
|
-
};
|
|
323
|
-
if (!fs.existsSync(compiledJsPath)) {
|
|
324
|
-
result.errors.push(`Compiled JS not found: ${compiledJsPath}`);
|
|
325
|
-
return result;
|
|
326
|
-
}
|
|
327
|
-
return new Promise((resolve) => {
|
|
328
|
-
const child = spawn("node", ["-e", WORKER_SCRIPT, compiledJsPath, target], {
|
|
329
|
-
timeout,
|
|
330
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
331
|
-
});
|
|
332
|
-
let stdout = "";
|
|
333
|
-
let stderr = "";
|
|
334
|
-
child.stdout.on("data", (data) => {
|
|
335
|
-
stdout += data.toString();
|
|
336
|
-
});
|
|
337
|
-
child.stderr.on("data", (data) => {
|
|
338
|
-
stderr += data.toString();
|
|
339
|
-
});
|
|
340
|
-
child.on("close", (code) => {
|
|
341
|
-
if (code !== 0) {
|
|
342
|
-
result.errors.push(`Extraction process failed: ${stderr || `exit code ${code}`}`);
|
|
343
|
-
resolve(result);
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
try {
|
|
347
|
-
const parsed = JSON.parse(stdout);
|
|
348
|
-
if (!parsed.success) {
|
|
349
|
-
result.errors.push(`Extraction failed: ${parsed.error}`);
|
|
350
|
-
resolve(result);
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
for (const item of parsed.results) {
|
|
354
|
-
result.schemas.set(item.exportName, {
|
|
355
|
-
exportName: item.exportName,
|
|
356
|
-
vendor: item.vendor,
|
|
357
|
-
outputSchema: item.outputSchema,
|
|
358
|
-
inputSchema: item.inputSchema
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
} catch (e) {
|
|
362
|
-
result.errors.push(`Failed to parse extraction output: ${e}`);
|
|
363
|
-
}
|
|
364
|
-
resolve(result);
|
|
365
|
-
});
|
|
366
|
-
child.on("error", (err) => {
|
|
367
|
-
result.errors.push(`Subprocess error: ${err.message}`);
|
|
368
|
-
resolve(result);
|
|
369
|
-
});
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
async function extractStandardSchemasFromProject(entryFile, baseDir, options = {}) {
|
|
373
|
-
const compiledPath = resolveCompiledPath(entryFile, baseDir);
|
|
374
|
-
if (!compiledPath) {
|
|
375
|
-
return {
|
|
376
|
-
schemas: new Map,
|
|
377
|
-
errors: [`Could not find compiled JS for ${entryFile}. Build the project first.`]
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
return extractStandardSchemas(compiledPath, options);
|
|
381
|
-
}
|
|
382
196
|
// src/types/utils.ts
|
|
383
197
|
function isExported(node) {
|
|
384
198
|
const modifiers = node.modifiers;
|