@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  extract
4
- } from "../shared/chunk-rej3ws8m.js";
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).print();
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 intrinsicName = type.intrinsicName;
168
- return { type: "boolean", enum: [intrinsicName === "true"] };
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.jsDoc;
708
+ const jsDocComments = ts3.getJSDocCommentsAndTags(node).filter(ts3.isJSDoc);
706
709
  let description;
707
- if (jsDocComments && jsDocComments.length > 0) {
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.length === 1 ? names[0] : 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/builder/spec-builder.ts
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
- const exports = [];
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: path2.basename(entryFile, path2.extname(entryFile)) },
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 ?? path2.dirname(entryFile);
2049
- const pkgPath = path2.join(searchDir, "package.json");
2566
+ const searchDir = baseDir ?? path3.dirname(entryFile);
2567
+ const pkgPath = path3.join(searchDir, "package.json");
2050
2568
  try {
2051
- if (fs.existsSync(pkgPath)) {
2052
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
2569
+ if (fs2.existsSync(pkgPath)) {
2570
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
2053
2571
  return {
2054
- name: pkg.name ?? path2.basename(searchDir),
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: path2.basename(searchDir) };
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 };
@@ -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
- * Tries common output locations: dist/, build/, lib/, same dir.
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
- * Convenience function that resolves compiled JS and extracts schemas.
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?: ExtractStandardSchemasOptions): Promise<StandardSchemaExtractionOutput>;
281
- import { SpecExport } from "@openpkg-ts/spec";
282
- import ts5 from "typescript";
283
- declare function serializeClass(node: ts5.ClassDeclaration, ctx: SerializerContext): SpecExport | null;
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 ts6 from "typescript";
286
- declare function serializeEnum(node: ts6.EnumDeclaration, ctx: SerializerContext): SpecExport2 | null;
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 ts7 from "typescript";
289
- declare function serializeFunctionExport(node: ts7.FunctionDeclaration | ts7.ArrowFunction, ctx: SerializerContext): SpecExport3 | null;
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 ts8 from "typescript";
292
- declare function serializeInterface(node: ts8.InterfaceDeclaration, ctx: SerializerContext): SpecExport4 | null;
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 ts9 from "typescript";
295
- declare function serializeTypeAlias(node: ts9.TypeAliasDeclaration, ctx: SerializerContext): SpecExport5 | null;
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): SpecExport6 | null;
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-rej3ws8m.js";
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpkg-ts/extract",
3
- "version": "0.20.0",
3
+ "version": "0.22.0",
4
4
  "description": "TypeScript export extraction to OpenPkg spec",
5
5
  "keywords": [
6
6
  "openpkg",