@openpkg-ts/extract 0.20.0 → 0.22.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 +11 -2
- package/dist/shared/{chunk-rej3ws8m.js → chunk-nymjpc96.js} +538 -20
- package/dist/src/index.d.ts +152 -83
- package/dist/src/index.js +10 -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-nymjpc96.js";
|
|
5
5
|
|
|
6
6
|
// src/cli/spec.ts
|
|
7
7
|
import * as fs from "node:fs";
|
|
@@ -451,13 +451,22 @@ 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, method } = result.runtimeSchemas;
|
|
456
|
+
const via = method ? ` via ${method}` : "";
|
|
457
|
+
console.log(`ℹ Runtime schemas: ${merged}/${extracted} merged (${vendors.join(", ")})${via}`);
|
|
458
|
+
}
|
|
454
459
|
for (const diag of result.diagnostics) {
|
|
455
460
|
if (diag.severity === "info" && !options.verbose)
|
|
456
461
|
continue;
|
|
457
462
|
const prefix2 = diag.severity === "error" ? "✗" : diag.severity === "warning" ? "⚠" : "ℹ";
|
|
458
463
|
console.log(`${prefix2} ${diag.message}`);
|
|
459
464
|
}
|
|
460
|
-
summary().addKeyValue("Exports", normalized.exports.length).addKeyValue("Types", normalized.types?.length || 0)
|
|
465
|
+
const sum = summary().addKeyValue("Exports", normalized.exports.length).addKeyValue("Types", normalized.types?.length || 0);
|
|
466
|
+
if (result.runtimeSchemas) {
|
|
467
|
+
sum.addKeyValue("Runtime Schemas", result.runtimeSchemas.merged);
|
|
468
|
+
}
|
|
469
|
+
sum.print();
|
|
461
470
|
});
|
|
462
471
|
return program;
|
|
463
472
|
}
|
|
@@ -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,467 @@ function serializeVariable(node, statement, ctx) {
|
|
|
1519
1522
|
};
|
|
1520
1523
|
}
|
|
1521
1524
|
|
|
1522
|
-
// src/
|
|
1525
|
+
// src/schema/standard-schema.ts
|
|
1526
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
1523
1527
|
import * as fs from "node:fs";
|
|
1528
|
+
import * as os from "node:os";
|
|
1524
1529
|
import * as path2 from "node:path";
|
|
1530
|
+
function isStandardJSONSchema(obj) {
|
|
1531
|
+
if (typeof obj !== "object" || obj === null)
|
|
1532
|
+
return false;
|
|
1533
|
+
const std = obj["~standard"];
|
|
1534
|
+
if (typeof std !== "object" || std === null)
|
|
1535
|
+
return false;
|
|
1536
|
+
const stdObj = std;
|
|
1537
|
+
if (stdObj.version !== 1)
|
|
1538
|
+
return false;
|
|
1539
|
+
if (typeof stdObj.vendor !== "string")
|
|
1540
|
+
return false;
|
|
1541
|
+
const jsonSchema = stdObj.jsonSchema;
|
|
1542
|
+
if (typeof jsonSchema !== "object" || jsonSchema === null)
|
|
1543
|
+
return false;
|
|
1544
|
+
const jsObj = jsonSchema;
|
|
1545
|
+
return typeof jsObj.output === "function" && typeof jsObj.input === "function";
|
|
1546
|
+
}
|
|
1547
|
+
var cachedRuntime;
|
|
1548
|
+
function commandExists(cmd) {
|
|
1549
|
+
try {
|
|
1550
|
+
const result = spawnSync(process.platform === "win32" ? "where" : "which", [cmd], {
|
|
1551
|
+
stdio: "ignore"
|
|
1552
|
+
});
|
|
1553
|
+
return result.status === 0;
|
|
1554
|
+
} catch {
|
|
1555
|
+
return false;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
function detectTsRuntime() {
|
|
1559
|
+
if (cachedRuntime !== undefined) {
|
|
1560
|
+
return cachedRuntime;
|
|
1561
|
+
}
|
|
1562
|
+
const nodeVersion = parseInt(process.versions.node.split(".")[0], 10);
|
|
1563
|
+
if (nodeVersion >= 22) {
|
|
1564
|
+
cachedRuntime = {
|
|
1565
|
+
cmd: "node",
|
|
1566
|
+
args: ["--experimental-strip-types", "--no-warnings"],
|
|
1567
|
+
name: "node (native)"
|
|
1568
|
+
};
|
|
1569
|
+
return cachedRuntime;
|
|
1570
|
+
}
|
|
1571
|
+
if (commandExists("bun")) {
|
|
1572
|
+
cachedRuntime = {
|
|
1573
|
+
cmd: "bun",
|
|
1574
|
+
args: ["run"],
|
|
1575
|
+
name: "bun"
|
|
1576
|
+
};
|
|
1577
|
+
return cachedRuntime;
|
|
1578
|
+
}
|
|
1579
|
+
if (commandExists("tsx")) {
|
|
1580
|
+
cachedRuntime = {
|
|
1581
|
+
cmd: "tsx",
|
|
1582
|
+
args: [],
|
|
1583
|
+
name: "tsx"
|
|
1584
|
+
};
|
|
1585
|
+
return cachedRuntime;
|
|
1586
|
+
}
|
|
1587
|
+
if (commandExists("ts-node")) {
|
|
1588
|
+
cachedRuntime = {
|
|
1589
|
+
cmd: "ts-node",
|
|
1590
|
+
args: ["--transpile-only"],
|
|
1591
|
+
name: "ts-node"
|
|
1592
|
+
};
|
|
1593
|
+
return cachedRuntime;
|
|
1594
|
+
}
|
|
1595
|
+
cachedRuntime = null;
|
|
1596
|
+
return null;
|
|
1597
|
+
}
|
|
1598
|
+
var WORKER_SCRIPT = `
|
|
1599
|
+
const path = require('path');
|
|
1600
|
+
const { pathToFileURL } = require('url');
|
|
1601
|
+
|
|
1602
|
+
// TypeBox detection: schemas have Symbol.for('TypeBox.Kind') and are JSON Schema
|
|
1603
|
+
const TYPEBOX_KIND = Symbol.for('TypeBox.Kind');
|
|
1604
|
+
|
|
1605
|
+
function isTypeBoxSchema(obj) {
|
|
1606
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
1607
|
+
// TypeBox schemas always have Kind symbol (Union, Object, String, etc.)
|
|
1608
|
+
// Also check for common JSON Schema props to avoid false positives
|
|
1609
|
+
if (!obj[TYPEBOX_KIND]) return false;
|
|
1610
|
+
return typeof obj.type === 'string' || 'anyOf' in obj || 'oneOf' in obj || 'allOf' in obj;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
function sanitizeTypeBoxSchema(schema) {
|
|
1614
|
+
// JSON.stringify removes symbol keys, keeping only JSON Schema props
|
|
1615
|
+
return JSON.parse(JSON.stringify(schema));
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
async function extract() {
|
|
1619
|
+
// With node -e, argv is: [node, arg1, arg2, ...]
|
|
1620
|
+
// (the -e script is NOT in argv)
|
|
1621
|
+
const [modulePath, optionsJson] = process.argv.slice(1);
|
|
1622
|
+
const { target, libraryOptions } = JSON.parse(optionsJson || '{}');
|
|
1623
|
+
|
|
1624
|
+
try {
|
|
1625
|
+
// Import the module using dynamic import (works with ESM and CJS)
|
|
1626
|
+
const absPath = path.resolve(modulePath);
|
|
1627
|
+
const mod = await import(pathToFileURL(absPath).href);
|
|
1628
|
+
const results = [];
|
|
1629
|
+
|
|
1630
|
+
// Build exports map - handle both ESM and CJS (where exports are in mod.default)
|
|
1631
|
+
const exports = {};
|
|
1632
|
+
for (const [name, value] of Object.entries(mod)) {
|
|
1633
|
+
if (name === 'default' && typeof value === 'object' && value !== null) {
|
|
1634
|
+
// CJS module: spread default exports
|
|
1635
|
+
Object.assign(exports, value);
|
|
1636
|
+
} else if (name !== 'default') {
|
|
1637
|
+
exports[name] = value;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// Check each export
|
|
1642
|
+
for (const [name, value] of Object.entries(exports)) {
|
|
1643
|
+
if (name.startsWith('_')) continue;
|
|
1644
|
+
if (typeof value !== 'object' || value === null) continue;
|
|
1645
|
+
|
|
1646
|
+
// Priority 1: Standard JSON Schema (Zod 4.2+, ArkType 2.1.28+, Valibot 1.2+)
|
|
1647
|
+
const std = value['~standard'];
|
|
1648
|
+
if (std && typeof std === 'object' && std.version === 1 && typeof std.vendor === 'string' && std.jsonSchema && typeof std.jsonSchema.output === 'function') {
|
|
1649
|
+
try {
|
|
1650
|
+
// Per spec: pass options object with target and optional libraryOptions
|
|
1651
|
+
const options = { target: target || 'draft-2020-12', ...(libraryOptions && { libraryOptions }) };
|
|
1652
|
+
const outputSchema = std.jsonSchema.output(options);
|
|
1653
|
+
const inputSchema = typeof std.jsonSchema.input === 'function' ? std.jsonSchema.input(options) : undefined;
|
|
1654
|
+
results.push({
|
|
1655
|
+
exportName: name,
|
|
1656
|
+
vendor: std.vendor,
|
|
1657
|
+
outputSchema,
|
|
1658
|
+
inputSchema
|
|
1659
|
+
});
|
|
1660
|
+
} catch (e) {
|
|
1661
|
+
// Skip schemas that fail to extract
|
|
1662
|
+
}
|
|
1663
|
+
continue;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// Priority 2: TypeBox (schema IS JSON Schema)
|
|
1667
|
+
if (isTypeBoxSchema(value)) {
|
|
1668
|
+
try {
|
|
1669
|
+
results.push({
|
|
1670
|
+
exportName: name,
|
|
1671
|
+
vendor: 'typebox',
|
|
1672
|
+
outputSchema: sanitizeTypeBoxSchema(value)
|
|
1673
|
+
});
|
|
1674
|
+
} catch (e) {
|
|
1675
|
+
// Skip schemas that fail to extract
|
|
1676
|
+
}
|
|
1677
|
+
continue;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
console.log(JSON.stringify({ success: true, results }));
|
|
1682
|
+
} catch (e) {
|
|
1683
|
+
console.log(JSON.stringify({ success: false, error: e.message }));
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
extract();
|
|
1688
|
+
`;
|
|
1689
|
+
var TS_WORKER_SCRIPT = `
|
|
1690
|
+
import * as path from 'path';
|
|
1691
|
+
import { pathToFileURL } from 'url';
|
|
1692
|
+
|
|
1693
|
+
// TypeBox detection
|
|
1694
|
+
const TYPEBOX_KIND = Symbol.for('TypeBox.Kind');
|
|
1695
|
+
|
|
1696
|
+
function isTypeBoxSchema(obj: unknown): boolean {
|
|
1697
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
1698
|
+
const o = obj as Record<string | symbol, unknown>;
|
|
1699
|
+
if (!o[TYPEBOX_KIND]) return false;
|
|
1700
|
+
return typeof o.type === 'string' || 'anyOf' in o || 'oneOf' in o || 'allOf' in o;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
function sanitizeTypeBoxSchema(schema: unknown): unknown {
|
|
1704
|
+
return JSON.parse(JSON.stringify(schema));
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
async function extract() {
|
|
1708
|
+
const [,, modulePath, optionsJson] = process.argv;
|
|
1709
|
+
const { target, libraryOptions } = JSON.parse(optionsJson || '{}');
|
|
1710
|
+
|
|
1711
|
+
try {
|
|
1712
|
+
const absPath = path.resolve(modulePath);
|
|
1713
|
+
const mod = await import(pathToFileURL(absPath).href);
|
|
1714
|
+
const results: Array<{exportName: string; vendor: string; outputSchema: unknown; inputSchema?: unknown}> = [];
|
|
1715
|
+
|
|
1716
|
+
// Build exports map
|
|
1717
|
+
const exports: Record<string, unknown> = {};
|
|
1718
|
+
for (const [name, value] of Object.entries(mod)) {
|
|
1719
|
+
if (name === 'default' && typeof value === 'object' && value !== null) {
|
|
1720
|
+
Object.assign(exports, value);
|
|
1721
|
+
} else if (name !== 'default') {
|
|
1722
|
+
exports[name] = value;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// Check each export
|
|
1727
|
+
for (const [name, value] of Object.entries(exports)) {
|
|
1728
|
+
if (name.startsWith('_')) continue;
|
|
1729
|
+
if (typeof value !== 'object' || value === null) continue;
|
|
1730
|
+
|
|
1731
|
+
const v = value as Record<string, unknown>;
|
|
1732
|
+
const std = v['~standard'] as Record<string, unknown> | undefined;
|
|
1733
|
+
|
|
1734
|
+
// Standard JSON Schema
|
|
1735
|
+
if (std && typeof std === 'object' && std.version === 1 && typeof std.vendor === 'string') {
|
|
1736
|
+
const jsonSchema = std.jsonSchema as Record<string, unknown> | undefined;
|
|
1737
|
+
if (jsonSchema && typeof jsonSchema.output === 'function') {
|
|
1738
|
+
try {
|
|
1739
|
+
const options = { target: target || 'draft-2020-12', ...(libraryOptions && { libraryOptions }) };
|
|
1740
|
+
const outputSchema = (jsonSchema.output as Function)(options);
|
|
1741
|
+
const inputSchema = typeof jsonSchema.input === 'function' ? (jsonSchema.input as Function)(options) : undefined;
|
|
1742
|
+
results.push({ exportName: name, vendor: std.vendor as string, outputSchema, inputSchema });
|
|
1743
|
+
} catch {}
|
|
1744
|
+
continue;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
// TypeBox
|
|
1749
|
+
if (isTypeBoxSchema(value)) {
|
|
1750
|
+
try {
|
|
1751
|
+
results.push({ exportName: name, vendor: 'typebox', outputSchema: sanitizeTypeBoxSchema(value) });
|
|
1752
|
+
} catch {}
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
console.log(JSON.stringify({ success: true, results }));
|
|
1757
|
+
} catch (e) {
|
|
1758
|
+
console.log(JSON.stringify({ success: false, error: (e as Error).message }));
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
extract();
|
|
1763
|
+
`;
|
|
1764
|
+
async function extractStandardSchemasFromTs(tsFilePath, options = {}) {
|
|
1765
|
+
const { timeout = 1e4, target = "draft-2020-12", libraryOptions } = options;
|
|
1766
|
+
const result = {
|
|
1767
|
+
schemas: new Map,
|
|
1768
|
+
errors: []
|
|
1769
|
+
};
|
|
1770
|
+
const runtime = detectTsRuntime();
|
|
1771
|
+
if (!runtime) {
|
|
1772
|
+
result.errors.push("No TypeScript runtime available. Install bun, tsx, or ts-node, or use Node 22+.");
|
|
1773
|
+
return result;
|
|
1774
|
+
}
|
|
1775
|
+
if (!fs.existsSync(tsFilePath)) {
|
|
1776
|
+
result.errors.push(`TypeScript file not found: ${tsFilePath}`);
|
|
1777
|
+
return result;
|
|
1778
|
+
}
|
|
1779
|
+
const tempDir = os.tmpdir();
|
|
1780
|
+
const workerPath = path2.join(tempDir, `openpkg-extract-worker-${Date.now()}.ts`);
|
|
1781
|
+
try {
|
|
1782
|
+
fs.writeFileSync(workerPath, TS_WORKER_SCRIPT);
|
|
1783
|
+
const optionsJson = JSON.stringify({ target, libraryOptions });
|
|
1784
|
+
const args = [...runtime.args, workerPath, tsFilePath, optionsJson];
|
|
1785
|
+
return await new Promise((resolve) => {
|
|
1786
|
+
const child = spawn(runtime.cmd, args, {
|
|
1787
|
+
timeout,
|
|
1788
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1789
|
+
cwd: path2.dirname(tsFilePath)
|
|
1790
|
+
});
|
|
1791
|
+
let stdout = "";
|
|
1792
|
+
let stderr = "";
|
|
1793
|
+
child.stdout.on("data", (data) => {
|
|
1794
|
+
stdout += data.toString();
|
|
1795
|
+
});
|
|
1796
|
+
child.stderr.on("data", (data) => {
|
|
1797
|
+
stderr += data.toString();
|
|
1798
|
+
});
|
|
1799
|
+
child.on("close", (code) => {
|
|
1800
|
+
try {
|
|
1801
|
+
fs.unlinkSync(workerPath);
|
|
1802
|
+
} catch {}
|
|
1803
|
+
if (code !== 0) {
|
|
1804
|
+
result.errors.push(`Extraction failed (${runtime.name}): ${stderr || `exit code ${code}`}`);
|
|
1805
|
+
resolve(result);
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
try {
|
|
1809
|
+
const parsed = JSON.parse(stdout);
|
|
1810
|
+
if (!parsed.success) {
|
|
1811
|
+
result.errors.push(`Extraction failed: ${parsed.error}`);
|
|
1812
|
+
resolve(result);
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
for (const item of parsed.results) {
|
|
1816
|
+
result.schemas.set(item.exportName, {
|
|
1817
|
+
exportName: item.exportName,
|
|
1818
|
+
vendor: item.vendor,
|
|
1819
|
+
outputSchema: item.outputSchema,
|
|
1820
|
+
inputSchema: item.inputSchema
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
} catch (e) {
|
|
1824
|
+
result.errors.push(`Failed to parse extraction output: ${e}`);
|
|
1825
|
+
}
|
|
1826
|
+
resolve(result);
|
|
1827
|
+
});
|
|
1828
|
+
child.on("error", (err) => {
|
|
1829
|
+
try {
|
|
1830
|
+
fs.unlinkSync(workerPath);
|
|
1831
|
+
} catch {}
|
|
1832
|
+
result.errors.push(`Subprocess error: ${err.message}`);
|
|
1833
|
+
resolve(result);
|
|
1834
|
+
});
|
|
1835
|
+
});
|
|
1836
|
+
} catch (e) {
|
|
1837
|
+
try {
|
|
1838
|
+
fs.unlinkSync(workerPath);
|
|
1839
|
+
} catch {}
|
|
1840
|
+
result.errors.push(`Failed to create worker script: ${e}`);
|
|
1841
|
+
return result;
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
function readTsconfigOutDir(baseDir) {
|
|
1845
|
+
const tsconfigPath = path2.join(baseDir, "tsconfig.json");
|
|
1846
|
+
try {
|
|
1847
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
1848
|
+
return null;
|
|
1849
|
+
}
|
|
1850
|
+
const content = fs.readFileSync(tsconfigPath, "utf-8");
|
|
1851
|
+
const stripped = content.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
|
1852
|
+
const tsconfig = JSON.parse(stripped);
|
|
1853
|
+
if (tsconfig.compilerOptions?.outDir) {
|
|
1854
|
+
return tsconfig.compilerOptions.outDir.replace(/^\.\//, "");
|
|
1855
|
+
}
|
|
1856
|
+
} catch {}
|
|
1857
|
+
return null;
|
|
1858
|
+
}
|
|
1859
|
+
function resolveCompiledPath(tsPath, baseDir) {
|
|
1860
|
+
const relativePath = path2.relative(baseDir, tsPath);
|
|
1861
|
+
const withoutExt = relativePath.replace(/\.tsx?$/, "");
|
|
1862
|
+
const srcPrefix = withoutExt.replace(/^src\//, "");
|
|
1863
|
+
const tsconfigOutDir = readTsconfigOutDir(baseDir);
|
|
1864
|
+
const extensions = [".js", ".mjs", ".cjs"];
|
|
1865
|
+
const candidates = [];
|
|
1866
|
+
if (tsconfigOutDir) {
|
|
1867
|
+
for (const ext of extensions) {
|
|
1868
|
+
candidates.push(path2.join(baseDir, tsconfigOutDir, `${srcPrefix}${ext}`));
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
const commonOutDirs = ["dist", "build", "lib", "out"];
|
|
1872
|
+
for (const outDir of commonOutDirs) {
|
|
1873
|
+
if (outDir === tsconfigOutDir)
|
|
1874
|
+
continue;
|
|
1875
|
+
for (const ext of extensions) {
|
|
1876
|
+
candidates.push(path2.join(baseDir, outDir, `${srcPrefix}${ext}`));
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
for (const ext of extensions) {
|
|
1880
|
+
candidates.push(path2.join(baseDir, `${withoutExt}${ext}`));
|
|
1881
|
+
}
|
|
1882
|
+
const workspaceMatch = baseDir.match(/^(.+\/packages\/[^/]+)$/);
|
|
1883
|
+
if (workspaceMatch) {
|
|
1884
|
+
const pkgRoot = workspaceMatch[1];
|
|
1885
|
+
for (const ext of extensions) {
|
|
1886
|
+
candidates.push(path2.join(pkgRoot, "dist", `${srcPrefix}${ext}`));
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
for (const candidate of candidates) {
|
|
1890
|
+
if (fs.existsSync(candidate)) {
|
|
1891
|
+
return candidate;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
return null;
|
|
1895
|
+
}
|
|
1896
|
+
async function extractStandardSchemas(compiledJsPath, options = {}) {
|
|
1897
|
+
const { timeout = 1e4, target = "draft-2020-12", libraryOptions } = options;
|
|
1898
|
+
const result = {
|
|
1899
|
+
schemas: new Map,
|
|
1900
|
+
errors: []
|
|
1901
|
+
};
|
|
1902
|
+
if (!fs.existsSync(compiledJsPath)) {
|
|
1903
|
+
result.errors.push(`Compiled JS not found: ${compiledJsPath}`);
|
|
1904
|
+
return result;
|
|
1905
|
+
}
|
|
1906
|
+
const optionsJson = JSON.stringify({ target, libraryOptions });
|
|
1907
|
+
return new Promise((resolve) => {
|
|
1908
|
+
const child = spawn("node", ["-e", WORKER_SCRIPT, compiledJsPath, optionsJson], {
|
|
1909
|
+
timeout,
|
|
1910
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1911
|
+
});
|
|
1912
|
+
let stdout = "";
|
|
1913
|
+
let stderr = "";
|
|
1914
|
+
child.stdout.on("data", (data) => {
|
|
1915
|
+
stdout += data.toString();
|
|
1916
|
+
});
|
|
1917
|
+
child.stderr.on("data", (data) => {
|
|
1918
|
+
stderr += data.toString();
|
|
1919
|
+
});
|
|
1920
|
+
child.on("close", (code) => {
|
|
1921
|
+
if (code !== 0) {
|
|
1922
|
+
result.errors.push(`Extraction process failed: ${stderr || `exit code ${code}`}`);
|
|
1923
|
+
resolve(result);
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1926
|
+
try {
|
|
1927
|
+
const parsed = JSON.parse(stdout);
|
|
1928
|
+
if (!parsed.success) {
|
|
1929
|
+
result.errors.push(`Extraction failed: ${parsed.error}`);
|
|
1930
|
+
resolve(result);
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
for (const item of parsed.results) {
|
|
1934
|
+
result.schemas.set(item.exportName, {
|
|
1935
|
+
exportName: item.exportName,
|
|
1936
|
+
vendor: item.vendor,
|
|
1937
|
+
outputSchema: item.outputSchema,
|
|
1938
|
+
inputSchema: item.inputSchema
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
} catch (e) {
|
|
1942
|
+
result.errors.push(`Failed to parse extraction output: ${e}`);
|
|
1943
|
+
}
|
|
1944
|
+
resolve(result);
|
|
1945
|
+
});
|
|
1946
|
+
child.on("error", (err) => {
|
|
1947
|
+
result.errors.push(`Subprocess error: ${err.message}`);
|
|
1948
|
+
resolve(result);
|
|
1949
|
+
});
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
async function extractStandardSchemasFromProject(entryFile, baseDir, options = {}) {
|
|
1953
|
+
const { preferDirectTs, ...extractOptions } = options;
|
|
1954
|
+
const isTypeScript = /\.tsx?$/.test(entryFile);
|
|
1955
|
+
if (!preferDirectTs) {
|
|
1956
|
+
const compiledPath = resolveCompiledPath(entryFile, baseDir);
|
|
1957
|
+
if (compiledPath) {
|
|
1958
|
+
const result = await extractStandardSchemas(compiledPath, extractOptions);
|
|
1959
|
+
return {
|
|
1960
|
+
...result,
|
|
1961
|
+
info: { method: "compiled", path: compiledPath }
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
if (isTypeScript) {
|
|
1966
|
+
const runtime2 = detectTsRuntime();
|
|
1967
|
+
if (runtime2) {
|
|
1968
|
+
const result = await extractStandardSchemasFromTs(entryFile, extractOptions);
|
|
1969
|
+
return {
|
|
1970
|
+
...result,
|
|
1971
|
+
info: { method: "direct-ts", runtime: runtime2.name, path: entryFile }
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
const runtime = detectTsRuntime();
|
|
1976
|
+
const hint = isTypeScript && !runtime ? " Install bun, tsx, or ts-node for direct TS execution." : "";
|
|
1977
|
+
return {
|
|
1978
|
+
schemas: new Map,
|
|
1979
|
+
errors: [`Could not find compiled JS for ${entryFile}.${hint}`]
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
// src/builder/spec-builder.ts
|
|
1984
|
+
import * as fs2 from "node:fs";
|
|
1985
|
+
import * as path3 from "node:path";
|
|
1525
1986
|
import { SCHEMA_URL, SCHEMA_VERSION } from "@openpkg-ts/spec";
|
|
1526
1987
|
import ts8 from "typescript";
|
|
1527
1988
|
|
|
@@ -1541,6 +2002,34 @@ function createContext(program, sourceFile, options = {}) {
|
|
|
1541
2002
|
};
|
|
1542
2003
|
}
|
|
1543
2004
|
|
|
2005
|
+
// src/builder/schema-merger.ts
|
|
2006
|
+
function mergeRuntimeSchemas(staticExports, runtimeSchemas) {
|
|
2007
|
+
let merged = 0;
|
|
2008
|
+
const exports = staticExports.map((exp) => {
|
|
2009
|
+
const runtime = runtimeSchemas.get(exp.name);
|
|
2010
|
+
if (!runtime)
|
|
2011
|
+
return exp;
|
|
2012
|
+
merged++;
|
|
2013
|
+
const mergedExport = {
|
|
2014
|
+
...exp,
|
|
2015
|
+
schema: runtime.outputSchema,
|
|
2016
|
+
tags: [
|
|
2017
|
+
...exp.tags || [],
|
|
2018
|
+
{ name: "vendor", text: runtime.vendor },
|
|
2019
|
+
{ name: "schema-source", text: "standard-json-schema" }
|
|
2020
|
+
]
|
|
2021
|
+
};
|
|
2022
|
+
if (runtime.inputSchema && JSON.stringify(runtime.inputSchema) !== JSON.stringify(runtime.outputSchema)) {
|
|
2023
|
+
mergedExport.flags = {
|
|
2024
|
+
...mergedExport.flags,
|
|
2025
|
+
inputSchema: runtime.inputSchema
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
return mergedExport;
|
|
2029
|
+
});
|
|
2030
|
+
return { merged, exports };
|
|
2031
|
+
}
|
|
2032
|
+
|
|
1544
2033
|
// src/builder/spec-builder.ts
|
|
1545
2034
|
var BUILTIN_TYPES2 = new Set([
|
|
1546
2035
|
"Array",
|
|
@@ -1638,7 +2127,7 @@ async function extract(options) {
|
|
|
1638
2127
|
ignore
|
|
1639
2128
|
} = options;
|
|
1640
2129
|
const diagnostics = [];
|
|
1641
|
-
|
|
2130
|
+
let exports = [];
|
|
1642
2131
|
const result = createProgram({ entryFile, baseDir, content });
|
|
1643
2132
|
const { program, sourceFile } = result;
|
|
1644
2133
|
if (!sourceFile) {
|
|
@@ -1715,6 +2204,33 @@ async function extract(options) {
|
|
|
1715
2204
|
code: "EXTERNAL_TYPES"
|
|
1716
2205
|
});
|
|
1717
2206
|
}
|
|
2207
|
+
let runtimeMetadata;
|
|
2208
|
+
if (options.schemaExtraction === "hybrid") {
|
|
2209
|
+
const projectBaseDir = baseDir || path3.dirname(entryFile);
|
|
2210
|
+
const runtimeResult = await extractStandardSchemasFromProject(entryFile, projectBaseDir, {
|
|
2211
|
+
target: options.schemaTarget || "draft-2020-12",
|
|
2212
|
+
timeout: 15000
|
|
2213
|
+
});
|
|
2214
|
+
if (runtimeResult.schemas.size > 0) {
|
|
2215
|
+
const mergeResult = mergeRuntimeSchemas(exports, runtimeResult.schemas);
|
|
2216
|
+
exports = mergeResult.exports;
|
|
2217
|
+
const method = runtimeResult.info?.method === "direct-ts" ? `direct-ts (${runtimeResult.info.runtime})` : "compiled";
|
|
2218
|
+
runtimeMetadata = {
|
|
2219
|
+
extracted: runtimeResult.schemas.size,
|
|
2220
|
+
merged: mergeResult.merged,
|
|
2221
|
+
vendors: [...new Set([...runtimeResult.schemas.values()].map((s) => s.vendor))],
|
|
2222
|
+
errors: runtimeResult.errors,
|
|
2223
|
+
method
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
for (const error of runtimeResult.errors) {
|
|
2227
|
+
diagnostics.push({
|
|
2228
|
+
message: `Runtime schema extraction: ${error}`,
|
|
2229
|
+
severity: "warning",
|
|
2230
|
+
code: "RUNTIME_SCHEMA_ERROR"
|
|
2231
|
+
});
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
1718
2234
|
const spec = {
|
|
1719
2235
|
...includeSchema ? { $schema: SCHEMA_URL } : {},
|
|
1720
2236
|
openpkg: SCHEMA_VERSION,
|
|
@@ -1723,14 +2239,16 @@ async function extract(options) {
|
|
|
1723
2239
|
types,
|
|
1724
2240
|
generation: {
|
|
1725
2241
|
generator: "@openpkg-ts/extract",
|
|
1726
|
-
timestamp: new Date().toISOString()
|
|
2242
|
+
timestamp: new Date().toISOString(),
|
|
2243
|
+
...options.schemaExtraction === "hybrid" ? { schemaExtraction: "hybrid" } : {}
|
|
1727
2244
|
}
|
|
1728
2245
|
};
|
|
1729
2246
|
const internalForgotten = forgottenExports.filter((f) => !f.isExternal);
|
|
1730
2247
|
return {
|
|
1731
2248
|
spec,
|
|
1732
2249
|
diagnostics,
|
|
1733
|
-
...internalForgotten.length > 0 ? { forgottenExports: internalForgotten } : {}
|
|
2250
|
+
...internalForgotten.length > 0 ? { forgottenExports: internalForgotten } : {},
|
|
2251
|
+
...runtimeMetadata ? { runtimeSchemas: runtimeMetadata } : {}
|
|
1734
2252
|
};
|
|
1735
2253
|
}
|
|
1736
2254
|
function collectAllRefsWithContext(obj, refs, state) {
|
|
@@ -2036,7 +2554,7 @@ function createEmptySpec(entryFile, includeSchema) {
|
|
|
2036
2554
|
return {
|
|
2037
2555
|
...includeSchema ? { $schema: SCHEMA_URL } : {},
|
|
2038
2556
|
openpkg: SCHEMA_VERSION,
|
|
2039
|
-
meta: { name:
|
|
2557
|
+
meta: { name: path3.basename(entryFile, path3.extname(entryFile)) },
|
|
2040
2558
|
exports: [],
|
|
2041
2559
|
generation: {
|
|
2042
2560
|
generator: "@openpkg-ts/extract",
|
|
@@ -2045,18 +2563,18 @@ function createEmptySpec(entryFile, includeSchema) {
|
|
|
2045
2563
|
};
|
|
2046
2564
|
}
|
|
2047
2565
|
async function getPackageMeta(entryFile, baseDir) {
|
|
2048
|
-
const searchDir = baseDir ??
|
|
2049
|
-
const pkgPath =
|
|
2566
|
+
const searchDir = baseDir ?? path3.dirname(entryFile);
|
|
2567
|
+
const pkgPath = path3.join(searchDir, "package.json");
|
|
2050
2568
|
try {
|
|
2051
|
-
if (
|
|
2052
|
-
const pkg = JSON.parse(
|
|
2569
|
+
if (fs2.existsSync(pkgPath)) {
|
|
2570
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
2053
2571
|
return {
|
|
2054
|
-
name: pkg.name ??
|
|
2572
|
+
name: pkg.name ?? path3.basename(searchDir),
|
|
2055
2573
|
version: pkg.version,
|
|
2056
2574
|
description: pkg.description
|
|
2057
2575
|
};
|
|
2058
2576
|
}
|
|
2059
2577
|
} catch {}
|
|
2060
|
-
return { name:
|
|
2578
|
+
return { name: path3.basename(searchDir) };
|
|
2061
2579
|
}
|
|
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 };
|
|
2580
|
+
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, detectTsRuntime, extractStandardSchemasFromTs, 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,19 @@ 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
|
+
/** Extraction method used: 'compiled' or 'direct-ts (runtime)' */
|
|
100
|
+
method?: string;
|
|
101
|
+
};
|
|
87
102
|
}
|
|
88
103
|
interface Diagnostic {
|
|
89
104
|
message: string;
|
|
@@ -112,73 +127,6 @@ interface ForgottenExport {
|
|
|
112
127
|
fix?: string;
|
|
113
128
|
}
|
|
114
129
|
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
130
|
/**
|
|
183
131
|
* Target version for JSON Schema generation.
|
|
184
132
|
* @see https://standardschema.dev/json-schema
|
|
@@ -253,8 +201,35 @@ interface StandardSchemaExtractionOutput {
|
|
|
253
201
|
*/
|
|
254
202
|
declare function isStandardJSONSchema(obj: unknown): obj is StandardJSONSchemaV1;
|
|
255
203
|
/**
|
|
204
|
+
* TypeScript runtime configuration.
|
|
205
|
+
*/
|
|
206
|
+
interface TsRuntime {
|
|
207
|
+
/** Command to execute */
|
|
208
|
+
cmd: string;
|
|
209
|
+
/** Arguments to pass before the script path */
|
|
210
|
+
args: string[];
|
|
211
|
+
/** Human-readable name */
|
|
212
|
+
name: string;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Detect available TypeScript runtime.
|
|
216
|
+
* Checks in order: Node 22+ native, bun, tsx, ts-node.
|
|
217
|
+
* Returns null if no TS runtime is available.
|
|
218
|
+
*/
|
|
219
|
+
declare function detectTsRuntime(): TsRuntime | null;
|
|
220
|
+
/**
|
|
221
|
+
* Extract Standard Schema from a TypeScript file directly.
|
|
222
|
+
* Uses detected TS runtime (bun, tsx, ts-node, or node 22+).
|
|
223
|
+
*
|
|
224
|
+
* @param tsFilePath - Path to TypeScript file
|
|
225
|
+
* @param options - Extraction options
|
|
226
|
+
* @returns Extraction results
|
|
227
|
+
*/
|
|
228
|
+
declare function extractStandardSchemasFromTs(tsFilePath: string, options?: ExtractStandardSchemasOptions): Promise<StandardSchemaExtractionOutput>;
|
|
229
|
+
/**
|
|
256
230
|
* Resolve compiled JS path from TypeScript source.
|
|
257
|
-
*
|
|
231
|
+
* Reads tsconfig.json for outDir and tries multiple output patterns.
|
|
232
|
+
* Supports .js, .mjs, and .cjs extensions.
|
|
258
233
|
*/
|
|
259
234
|
declare function resolveCompiledPath(tsPath: string, baseDir: string): string | null;
|
|
260
235
|
/**
|
|
@@ -269,33 +244,127 @@ declare function resolveCompiledPath(tsPath: string, baseDir: string): string |
|
|
|
269
244
|
*/
|
|
270
245
|
declare function extractStandardSchemas(compiledJsPath: string, options?: ExtractStandardSchemasOptions): Promise<StandardSchemaExtractionOutput>;
|
|
271
246
|
/**
|
|
247
|
+
* Result info from extractStandardSchemasFromProject
|
|
248
|
+
*/
|
|
249
|
+
interface ProjectExtractionInfo {
|
|
250
|
+
/** How schemas were extracted */
|
|
251
|
+
method: "compiled" | "direct-ts";
|
|
252
|
+
/** Runtime used (for direct-ts) */
|
|
253
|
+
runtime?: string;
|
|
254
|
+
/** Path that was used */
|
|
255
|
+
path: string;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Extended options for project extraction
|
|
259
|
+
*/
|
|
260
|
+
interface ExtractFromProjectOptions extends ExtractStandardSchemasOptions {
|
|
261
|
+
/** Prefer direct TS execution even if compiled JS exists */
|
|
262
|
+
preferDirectTs?: boolean;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Extended result for project extraction
|
|
266
|
+
*/
|
|
267
|
+
interface ProjectExtractionOutput extends StandardSchemaExtractionOutput {
|
|
268
|
+
/** Info about how extraction was performed */
|
|
269
|
+
info?: ProjectExtractionInfo;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
272
|
* Extract Standard Schema from a TypeScript project.
|
|
273
273
|
*
|
|
274
|
-
*
|
|
274
|
+
* Tries in order:
|
|
275
|
+
* 1. Compiled JS (if found)
|
|
276
|
+
* 2. Direct TypeScript execution (if TS runtime available)
|
|
275
277
|
*
|
|
276
278
|
* @param entryFile - TypeScript entry file path
|
|
277
279
|
* @param baseDir - Project base directory
|
|
278
280
|
* @param options - Extraction options
|
|
279
281
|
*/
|
|
280
|
-
declare function extractStandardSchemasFromProject(entryFile: string, baseDir: string, options?:
|
|
281
|
-
import
|
|
282
|
-
|
|
283
|
-
|
|
282
|
+
declare function extractStandardSchemasFromProject(entryFile: string, baseDir: string, options?: ExtractFromProjectOptions): Promise<ProjectExtractionOutput>;
|
|
283
|
+
import ts4 from "typescript";
|
|
284
|
+
interface ProgramOptions {
|
|
285
|
+
entryFile: string;
|
|
286
|
+
baseDir?: string;
|
|
287
|
+
content?: string;
|
|
288
|
+
}
|
|
289
|
+
interface ProgramResult {
|
|
290
|
+
program: ts4.Program;
|
|
291
|
+
compilerHost: ts4.CompilerHost;
|
|
292
|
+
compilerOptions: ts4.CompilerOptions;
|
|
293
|
+
sourceFile?: ts4.SourceFile;
|
|
294
|
+
configPath?: string;
|
|
295
|
+
}
|
|
296
|
+
declare function createProgram({ entryFile, baseDir, content }: ProgramOptions): ProgramResult;
|
|
297
|
+
import * as TS from "typescript";
|
|
298
|
+
/**
|
|
299
|
+
* A schema adapter can detect and extract output types from a specific
|
|
300
|
+
* schema validation library.
|
|
301
|
+
*/
|
|
302
|
+
interface SchemaAdapter {
|
|
303
|
+
/** Unique identifier for this adapter */
|
|
304
|
+
readonly id: string;
|
|
305
|
+
/** npm package name(s) this adapter handles */
|
|
306
|
+
readonly packages: readonly string[];
|
|
307
|
+
/**
|
|
308
|
+
* Check if a type matches this adapter's schema library.
|
|
309
|
+
* Should be fast - called for every export.
|
|
310
|
+
*/
|
|
311
|
+
matches(type: TS.Type, checker: TS.TypeChecker): boolean;
|
|
312
|
+
/**
|
|
313
|
+
* Extract the output type from a schema type.
|
|
314
|
+
* Returns null if extraction fails.
|
|
315
|
+
*/
|
|
316
|
+
extractOutputType(type: TS.Type, checker: TS.TypeChecker): TS.Type | null;
|
|
317
|
+
/**
|
|
318
|
+
* Extract the input type from a schema type (optional).
|
|
319
|
+
* Useful for transforms where input differs from output.
|
|
320
|
+
*/
|
|
321
|
+
extractInputType?(type: TS.Type, checker: TS.TypeChecker): TS.Type | null;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Result of schema type extraction
|
|
325
|
+
*/
|
|
326
|
+
interface SchemaExtractionResult {
|
|
327
|
+
/** The adapter that matched */
|
|
328
|
+
adapter: SchemaAdapter;
|
|
329
|
+
/** The extracted output type */
|
|
330
|
+
outputType: TS.Type;
|
|
331
|
+
/** The extracted input type (if different from output) */
|
|
332
|
+
inputType?: TS.Type;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Utility: Check if type is an object type reference (has type arguments)
|
|
336
|
+
*/
|
|
337
|
+
declare function isTypeReference(type: TS.Type): type is TS.TypeReference;
|
|
338
|
+
/**
|
|
339
|
+
* Utility: Remove undefined/null from a union type
|
|
340
|
+
*/
|
|
341
|
+
declare function getNonNullableType(type: TS.Type): TS.Type;
|
|
342
|
+
declare function registerAdapter(adapter: SchemaAdapter): void;
|
|
343
|
+
declare function findAdapter(type: TS.Type, checker: TS.TypeChecker): SchemaAdapter | undefined;
|
|
344
|
+
declare function isSchemaType(type: TS.Type, checker: TS.TypeChecker): boolean;
|
|
345
|
+
declare function extractSchemaType(type: TS.Type, checker: TS.TypeChecker): SchemaExtractionResult | null;
|
|
346
|
+
declare const arktypeAdapter: SchemaAdapter;
|
|
347
|
+
declare const typeboxAdapter: SchemaAdapter;
|
|
348
|
+
declare const valibotAdapter: SchemaAdapter;
|
|
349
|
+
declare const zodAdapter: SchemaAdapter;
|
|
284
350
|
import { SpecExport as SpecExport2 } from "@openpkg-ts/spec";
|
|
285
|
-
import
|
|
286
|
-
declare function
|
|
351
|
+
import ts5 from "typescript";
|
|
352
|
+
declare function serializeClass(node: ts5.ClassDeclaration, ctx: SerializerContext): SpecExport2 | null;
|
|
287
353
|
import { SpecExport as SpecExport3 } from "@openpkg-ts/spec";
|
|
288
|
-
import
|
|
289
|
-
declare function
|
|
354
|
+
import ts6 from "typescript";
|
|
355
|
+
declare function serializeEnum(node: ts6.EnumDeclaration, ctx: SerializerContext): SpecExport3 | null;
|
|
290
356
|
import { SpecExport as SpecExport4 } from "@openpkg-ts/spec";
|
|
291
|
-
import
|
|
292
|
-
declare function
|
|
357
|
+
import ts7 from "typescript";
|
|
358
|
+
declare function serializeFunctionExport(node: ts7.FunctionDeclaration | ts7.ArrowFunction, ctx: SerializerContext): SpecExport4 | null;
|
|
293
359
|
import { SpecExport as SpecExport5 } from "@openpkg-ts/spec";
|
|
294
|
-
import
|
|
295
|
-
declare function
|
|
360
|
+
import ts8 from "typescript";
|
|
361
|
+
declare function serializeInterface(node: ts8.InterfaceDeclaration, ctx: SerializerContext): SpecExport5 | null;
|
|
296
362
|
import { SpecExport as SpecExport6 } from "@openpkg-ts/spec";
|
|
363
|
+
import ts9 from "typescript";
|
|
364
|
+
declare function serializeTypeAlias(node: ts9.TypeAliasDeclaration, ctx: SerializerContext): SpecExport6 | null;
|
|
365
|
+
import { SpecExport as SpecExport7 } from "@openpkg-ts/spec";
|
|
297
366
|
import ts10 from "typescript";
|
|
298
|
-
declare function serializeVariable(node: ts10.VariableDeclaration, statement: ts10.VariableStatement, ctx: SerializerContext):
|
|
367
|
+
declare function serializeVariable(node: ts10.VariableDeclaration, statement: ts10.VariableStatement, ctx: SerializerContext): SpecExport7 | null;
|
|
299
368
|
import { SpecSignatureParameter } from "@openpkg-ts/spec";
|
|
300
369
|
import ts11 from "typescript";
|
|
301
370
|
declare function extractParameters(signature: ts11.Signature, ctx: SerializerContext): SpecSignatureParameter[];
|
|
@@ -359,4 +428,4 @@ declare function findDiscriminatorProperty(unionTypes: ts12.Type[], checker: ts1
|
|
|
359
428
|
import ts13 from "typescript";
|
|
360
429
|
declare function isExported(node: ts13.Node): boolean;
|
|
361
430
|
declare function getNodeName(node: ts13.Node): string | undefined;
|
|
362
|
-
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 };
|
|
431
|
+
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, extractStandardSchemasFromTs, extractStandardSchemasFromProject, extractStandardSchemas, extractSchemaType, extractParameters, extract, detectTsRuntime, deduplicateSchemas, createProgram, buildSchema, arktypeAdapter, TypeRegistry, TypeReference2 as TypeReference, TsRuntime, StandardSchemaExtractionResult, StandardSchemaExtractionOutput, StandardJSONSchemaV1, StandardJSONSchemaTarget, StandardJSONSchemaOptions, SerializerContext, SchemaExtractionResult, SchemaAdapter, ProjectExtractionOutput, ProjectExtractionInfo, ProgramResult, ProgramOptions, ForgottenExport, ExtractStandardSchemasOptions, ExtractResult, ExtractOptions, ExtractFromProjectOptions, Diagnostic, BUILTIN_TYPE_SCHEMAS };
|
package/dist/src/index.js
CHANGED
|
@@ -4,8 +4,12 @@ import {
|
|
|
4
4
|
buildSchema,
|
|
5
5
|
createProgram,
|
|
6
6
|
deduplicateSchemas,
|
|
7
|
+
detectTsRuntime,
|
|
7
8
|
extract,
|
|
8
9
|
extractParameters,
|
|
10
|
+
extractStandardSchemas,
|
|
11
|
+
extractStandardSchemasFromProject,
|
|
12
|
+
extractStandardSchemasFromTs,
|
|
9
13
|
extractTypeParameters,
|
|
10
14
|
findDiscriminatorProperty,
|
|
11
15
|
getJSDocComment,
|
|
@@ -15,8 +19,10 @@ import {
|
|
|
15
19
|
isBuiltinGeneric,
|
|
16
20
|
isPrimitiveName,
|
|
17
21
|
isPureRefSchema,
|
|
22
|
+
isStandardJSONSchema,
|
|
18
23
|
isSymbolDeprecated,
|
|
19
24
|
registerReferencedTypes,
|
|
25
|
+
resolveCompiledPath,
|
|
20
26
|
schemaIsAny,
|
|
21
27
|
schemasAreEqual,
|
|
22
28
|
serializeClass,
|
|
@@ -26,7 +32,7 @@ import {
|
|
|
26
32
|
serializeTypeAlias,
|
|
27
33
|
serializeVariable,
|
|
28
34
|
withDescription
|
|
29
|
-
} from "../shared/chunk-
|
|
35
|
+
} from "../shared/chunk-nymjpc96.js";
|
|
30
36
|
// src/schema/registry.ts
|
|
31
37
|
function isTypeReference(type) {
|
|
32
38
|
return !!(type.flags & 524288 && type.objectFlags && type.objectFlags & 4);
|
|
@@ -57,7 +63,7 @@ function extractSchemaType(type, checker) {
|
|
|
57
63
|
const outputType = adapter.extractOutputType(type, checker);
|
|
58
64
|
if (!outputType)
|
|
59
65
|
return null;
|
|
60
|
-
const inputType = adapter.extractInputType?.(type, checker);
|
|
66
|
+
const inputType = adapter.extractInputType?.(type, checker) ?? undefined;
|
|
61
67
|
return {
|
|
62
68
|
adapter,
|
|
63
69
|
outputType,
|
|
@@ -189,200 +195,6 @@ registerAdapter(zodAdapter);
|
|
|
189
195
|
registerAdapter(valibotAdapter);
|
|
190
196
|
registerAdapter(arktypeAdapter);
|
|
191
197
|
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
198
|
// src/types/utils.ts
|
|
387
199
|
function isExported(node) {
|
|
388
200
|
const modifiers = node.modifiers;
|
|
@@ -430,11 +242,13 @@ export {
|
|
|
430
242
|
findDiscriminatorProperty,
|
|
431
243
|
findAdapter,
|
|
432
244
|
extractTypeParameters,
|
|
245
|
+
extractStandardSchemasFromTs,
|
|
433
246
|
extractStandardSchemasFromProject,
|
|
434
247
|
extractStandardSchemas,
|
|
435
248
|
extractSchemaType,
|
|
436
249
|
extractParameters,
|
|
437
250
|
extract,
|
|
251
|
+
detectTsRuntime,
|
|
438
252
|
deduplicateSchemas,
|
|
439
253
|
createProgram,
|
|
440
254
|
buildSchema,
|