@openpkg-ts/extract 0.1.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 +74 -0
- package/dist/shared/chunk-esvc8n1x.js +403 -0
- package/dist/src/index.d.ts +123 -0
- package/dist/src/index.js +139 -0
- package/package.json +55 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
extract
|
|
4
|
+
} from "../shared/chunk-esvc8n1x.js";
|
|
5
|
+
|
|
6
|
+
// src/cli/spec.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { normalize, validateSpec } from "@openpkg-ts/spec";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
function createProgram() {
|
|
12
|
+
const program = new Command("tspec").description("Extract TypeScript package API to OpenPkg spec").argument("[entry]", "Entry point file").option("-o, --output <file>", "Output file", "openpkg.json").option("--max-depth <n>", "Max type depth", "20").option("--skip-resolve", "Skip external type resolution").option("--runtime", "Enable Standard Schema runtime extraction").action(async (entry, options) => {
|
|
13
|
+
const entryFile = entry || findEntryPoint(process.cwd());
|
|
14
|
+
if (!entryFile) {
|
|
15
|
+
console.error("No entry point found. Please specify an entry file.");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
console.log(`Extracting from: ${entryFile}`);
|
|
19
|
+
const result = await extract({
|
|
20
|
+
entryFile: path.resolve(entryFile),
|
|
21
|
+
maxTypeDepth: parseInt(options.maxDepth),
|
|
22
|
+
resolveExternalTypes: !options.skipResolve,
|
|
23
|
+
schemaExtraction: options.runtime ? "hybrid" : "static"
|
|
24
|
+
});
|
|
25
|
+
for (const diag of result.diagnostics) {
|
|
26
|
+
const prefix = diag.severity === "error" ? "✗" : diag.severity === "warning" ? "⚠" : "ℹ";
|
|
27
|
+
console.log(`${prefix} ${diag.message}`);
|
|
28
|
+
}
|
|
29
|
+
const normalized = normalize(result.spec);
|
|
30
|
+
const validation = validateSpec(normalized);
|
|
31
|
+
if (!validation.ok) {
|
|
32
|
+
console.error("Validation failed:");
|
|
33
|
+
for (const err of validation.errors) {
|
|
34
|
+
console.error(` - ${err.instancePath}: ${err.message}`);
|
|
35
|
+
}
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
fs.writeFileSync(options.output, JSON.stringify(normalized, null, 2));
|
|
39
|
+
console.log(`Generated ${options.output}`);
|
|
40
|
+
console.log(` ${normalized.exports.length} exports`);
|
|
41
|
+
console.log(` ${normalized.types?.length || 0} types`);
|
|
42
|
+
});
|
|
43
|
+
return program;
|
|
44
|
+
}
|
|
45
|
+
function findEntryPoint(cwd) {
|
|
46
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
47
|
+
if (fs.existsSync(pkgPath)) {
|
|
48
|
+
try {
|
|
49
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
50
|
+
if (pkg.types)
|
|
51
|
+
return path.join(cwd, pkg.types);
|
|
52
|
+
if (pkg.typings)
|
|
53
|
+
return path.join(cwd, pkg.typings);
|
|
54
|
+
if (pkg.exports?.["."]?.types)
|
|
55
|
+
return path.join(cwd, pkg.exports["."].types);
|
|
56
|
+
if (pkg.main) {
|
|
57
|
+
const mainTs = pkg.main.replace(/\.js$/, ".ts");
|
|
58
|
+
if (fs.existsSync(path.join(cwd, mainTs)))
|
|
59
|
+
return path.join(cwd, mainTs);
|
|
60
|
+
}
|
|
61
|
+
} catch {}
|
|
62
|
+
}
|
|
63
|
+
const fallbacks = ["src/index.ts", "index.ts", "lib/index.ts"];
|
|
64
|
+
for (const fallback of fallbacks) {
|
|
65
|
+
const fullPath = path.join(cwd, fallback);
|
|
66
|
+
if (fs.existsSync(fullPath))
|
|
67
|
+
return fullPath;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// bin/tspec.ts
|
|
73
|
+
var program = createProgram();
|
|
74
|
+
program.parse();
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
// src/compiler/program.ts
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
var DEFAULT_COMPILER_OPTIONS = {
|
|
5
|
+
target: ts.ScriptTarget.Latest,
|
|
6
|
+
module: ts.ModuleKind.CommonJS,
|
|
7
|
+
lib: ["lib.es2021.d.ts"],
|
|
8
|
+
declaration: true,
|
|
9
|
+
moduleResolution: ts.ModuleResolutionKind.NodeJs
|
|
10
|
+
};
|
|
11
|
+
function createProgram({
|
|
12
|
+
entryFile,
|
|
13
|
+
baseDir = path.dirname(entryFile),
|
|
14
|
+
content
|
|
15
|
+
}) {
|
|
16
|
+
const configPath = ts.findConfigFile(baseDir, ts.sys.fileExists, "tsconfig.json");
|
|
17
|
+
let compilerOptions = { ...DEFAULT_COMPILER_OPTIONS };
|
|
18
|
+
if (configPath) {
|
|
19
|
+
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
20
|
+
const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.dirname(configPath));
|
|
21
|
+
compilerOptions = { ...compilerOptions, ...parsedConfig.options };
|
|
22
|
+
}
|
|
23
|
+
const allowJsVal = compilerOptions.allowJs;
|
|
24
|
+
if (typeof allowJsVal === "boolean" && allowJsVal) {
|
|
25
|
+
compilerOptions = { ...compilerOptions, allowJs: false, checkJs: false };
|
|
26
|
+
}
|
|
27
|
+
const compilerHost = ts.createCompilerHost(compilerOptions, true);
|
|
28
|
+
let inMemorySource;
|
|
29
|
+
if (content !== undefined) {
|
|
30
|
+
inMemorySource = ts.createSourceFile(entryFile, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
31
|
+
const originalGetSourceFile = compilerHost.getSourceFile.bind(compilerHost);
|
|
32
|
+
compilerHost.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
|
33
|
+
if (fileName === entryFile) {
|
|
34
|
+
return inMemorySource;
|
|
35
|
+
}
|
|
36
|
+
return originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const program = ts.createProgram([entryFile], compilerOptions, compilerHost);
|
|
40
|
+
const sourceFile = inMemorySource ?? program.getSourceFile(entryFile);
|
|
41
|
+
return {
|
|
42
|
+
program,
|
|
43
|
+
compilerHost,
|
|
44
|
+
compilerOptions,
|
|
45
|
+
sourceFile,
|
|
46
|
+
configPath
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/ast/registry.ts
|
|
51
|
+
class TypeRegistry {
|
|
52
|
+
types = new Map;
|
|
53
|
+
add(type) {
|
|
54
|
+
this.types.set(type.id, type);
|
|
55
|
+
}
|
|
56
|
+
get(id) {
|
|
57
|
+
return this.types.get(id);
|
|
58
|
+
}
|
|
59
|
+
has(id) {
|
|
60
|
+
return this.types.has(id);
|
|
61
|
+
}
|
|
62
|
+
getAll() {
|
|
63
|
+
return Array.from(this.types.values());
|
|
64
|
+
}
|
|
65
|
+
registerFromSymbol(symbol, checker) {
|
|
66
|
+
const name = symbol.getName();
|
|
67
|
+
if (this.has(name))
|
|
68
|
+
return this.get(name);
|
|
69
|
+
const type = {
|
|
70
|
+
id: name,
|
|
71
|
+
name,
|
|
72
|
+
kind: "type"
|
|
73
|
+
};
|
|
74
|
+
this.add(type);
|
|
75
|
+
return type;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/ast/utils.ts
|
|
80
|
+
import ts2 from "typescript";
|
|
81
|
+
function getJSDocComment(node) {
|
|
82
|
+
const jsDocTags = ts2.getJSDocTags(node);
|
|
83
|
+
const tags = jsDocTags.map((tag) => ({
|
|
84
|
+
name: tag.tagName.text,
|
|
85
|
+
text: typeof tag.comment === "string" ? tag.comment : ts2.getTextOfJSDocComment(tag.comment) ?? ""
|
|
86
|
+
}));
|
|
87
|
+
const jsDocComments = node.jsDoc;
|
|
88
|
+
let description;
|
|
89
|
+
if (jsDocComments && jsDocComments.length > 0) {
|
|
90
|
+
const firstDoc = jsDocComments[0];
|
|
91
|
+
if (firstDoc.comment) {
|
|
92
|
+
description = typeof firstDoc.comment === "string" ? firstDoc.comment : ts2.getTextOfJSDocComment(firstDoc.comment);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { description, tags };
|
|
96
|
+
}
|
|
97
|
+
function getSourceLocation(node, sourceFile) {
|
|
98
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
99
|
+
return {
|
|
100
|
+
file: sourceFile.fileName,
|
|
101
|
+
line: line + 1
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/types/parameters.ts
|
|
106
|
+
function extractParameters(signature, checker) {
|
|
107
|
+
return signature.getParameters().map((param) => {
|
|
108
|
+
const type = checker.getTypeOfSymbolAtLocation(param, param.valueDeclaration);
|
|
109
|
+
return {
|
|
110
|
+
name: param.getName(),
|
|
111
|
+
schema: { type: checker.typeToString(type) },
|
|
112
|
+
required: !(param.flags & 16777216)
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/serializers/functions.ts
|
|
118
|
+
function serializeFunctionExport(node, ctx) {
|
|
119
|
+
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
|
|
120
|
+
const name = symbol?.getName() ?? node.name?.getText();
|
|
121
|
+
if (!name)
|
|
122
|
+
return null;
|
|
123
|
+
const declSourceFile = node.getSourceFile();
|
|
124
|
+
const { description, tags } = getJSDocComment(node);
|
|
125
|
+
const source = getSourceLocation(node, declSourceFile);
|
|
126
|
+
const type = ctx.typeChecker.getTypeAtLocation(node);
|
|
127
|
+
const callSignatures = type.getCallSignatures();
|
|
128
|
+
const signatures = callSignatures.map((sig) => {
|
|
129
|
+
const params = extractParameters(sig, ctx.typeChecker);
|
|
130
|
+
const returnType = ctx.typeChecker.getReturnTypeOfSignature(sig);
|
|
131
|
+
return {
|
|
132
|
+
parameters: params,
|
|
133
|
+
returns: {
|
|
134
|
+
schema: { type: ctx.typeChecker.typeToString(returnType) }
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
id: name,
|
|
140
|
+
name,
|
|
141
|
+
kind: "function",
|
|
142
|
+
description,
|
|
143
|
+
tags,
|
|
144
|
+
source,
|
|
145
|
+
signatures
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/serializers/classes.ts
|
|
150
|
+
function serializeClass(node, ctx) {
|
|
151
|
+
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
|
|
152
|
+
const name = symbol?.getName() ?? node.name?.getText();
|
|
153
|
+
if (!name)
|
|
154
|
+
return null;
|
|
155
|
+
const declSourceFile = node.getSourceFile();
|
|
156
|
+
const { description, tags } = getJSDocComment(node);
|
|
157
|
+
const source = getSourceLocation(node, declSourceFile);
|
|
158
|
+
return {
|
|
159
|
+
id: name,
|
|
160
|
+
name,
|
|
161
|
+
kind: "class",
|
|
162
|
+
description,
|
|
163
|
+
tags,
|
|
164
|
+
source,
|
|
165
|
+
members: []
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/serializers/interfaces.ts
|
|
170
|
+
function serializeInterface(node, ctx) {
|
|
171
|
+
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
|
|
172
|
+
const name = symbol?.getName() ?? node.name?.getText();
|
|
173
|
+
if (!name)
|
|
174
|
+
return null;
|
|
175
|
+
const declSourceFile = node.getSourceFile();
|
|
176
|
+
const { description, tags } = getJSDocComment(node);
|
|
177
|
+
const source = getSourceLocation(node, declSourceFile);
|
|
178
|
+
return {
|
|
179
|
+
id: name,
|
|
180
|
+
name,
|
|
181
|
+
kind: "interface",
|
|
182
|
+
description,
|
|
183
|
+
tags,
|
|
184
|
+
source,
|
|
185
|
+
members: []
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/serializers/type-aliases.ts
|
|
190
|
+
function serializeTypeAlias(node, ctx) {
|
|
191
|
+
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
|
|
192
|
+
const name = symbol?.getName() ?? node.name?.getText();
|
|
193
|
+
if (!name)
|
|
194
|
+
return null;
|
|
195
|
+
const declSourceFile = node.getSourceFile();
|
|
196
|
+
const { description, tags } = getJSDocComment(node);
|
|
197
|
+
const source = getSourceLocation(node, declSourceFile);
|
|
198
|
+
const type = ctx.typeChecker.getTypeAtLocation(node);
|
|
199
|
+
const typeString = ctx.typeChecker.typeToString(type);
|
|
200
|
+
return {
|
|
201
|
+
id: name,
|
|
202
|
+
name,
|
|
203
|
+
kind: "type",
|
|
204
|
+
description,
|
|
205
|
+
tags,
|
|
206
|
+
source,
|
|
207
|
+
...typeString !== name ? { type: typeString } : {}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/serializers/enums.ts
|
|
212
|
+
function serializeEnum(node, ctx) {
|
|
213
|
+
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
|
|
214
|
+
const name = symbol?.getName() ?? node.name?.getText();
|
|
215
|
+
if (!name)
|
|
216
|
+
return null;
|
|
217
|
+
const declSourceFile = node.getSourceFile();
|
|
218
|
+
const { description, tags } = getJSDocComment(node);
|
|
219
|
+
const source = getSourceLocation(node, declSourceFile);
|
|
220
|
+
const members = node.members.map((member) => {
|
|
221
|
+
const memberSymbol = ctx.typeChecker.getSymbolAtLocation(member.name);
|
|
222
|
+
const memberName = memberSymbol?.getName() ?? member.name.getText();
|
|
223
|
+
return {
|
|
224
|
+
id: memberName,
|
|
225
|
+
name: memberName,
|
|
226
|
+
kind: "enum-member"
|
|
227
|
+
};
|
|
228
|
+
});
|
|
229
|
+
return {
|
|
230
|
+
id: name,
|
|
231
|
+
name,
|
|
232
|
+
kind: "enum",
|
|
233
|
+
description,
|
|
234
|
+
tags,
|
|
235
|
+
source,
|
|
236
|
+
members
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/serializers/variables.ts
|
|
241
|
+
function serializeVariable(node, statement, ctx) {
|
|
242
|
+
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name);
|
|
243
|
+
const name = symbol?.getName() ?? node.name.getText();
|
|
244
|
+
if (!name)
|
|
245
|
+
return null;
|
|
246
|
+
const declSourceFile = node.getSourceFile();
|
|
247
|
+
const { description, tags } = getJSDocComment(statement);
|
|
248
|
+
const source = getSourceLocation(node, declSourceFile);
|
|
249
|
+
const type = ctx.typeChecker.getTypeAtLocation(node);
|
|
250
|
+
const typeString = ctx.typeChecker.typeToString(type);
|
|
251
|
+
return {
|
|
252
|
+
id: name,
|
|
253
|
+
name,
|
|
254
|
+
kind: "variable",
|
|
255
|
+
description,
|
|
256
|
+
tags,
|
|
257
|
+
source,
|
|
258
|
+
...typeString && typeString !== name ? { type: typeString } : {}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/builder/spec-builder.ts
|
|
263
|
+
import ts3 from "typescript";
|
|
264
|
+
import { SCHEMA_VERSION } from "@openpkg-ts/spec";
|
|
265
|
+
|
|
266
|
+
// src/serializers/context.ts
|
|
267
|
+
function createContext(program, sourceFile, options = {}) {
|
|
268
|
+
return {
|
|
269
|
+
typeChecker: program.getTypeChecker(),
|
|
270
|
+
program,
|
|
271
|
+
sourceFile,
|
|
272
|
+
maxTypeDepth: options.maxTypeDepth ?? 20,
|
|
273
|
+
resolveExternalTypes: options.resolveExternalTypes ?? true,
|
|
274
|
+
typeRegistry: new TypeRegistry
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/builder/spec-builder.ts
|
|
279
|
+
import * as path2 from "node:path";
|
|
280
|
+
import * as fs from "node:fs";
|
|
281
|
+
async function extract(options) {
|
|
282
|
+
const { entryFile, baseDir, content, maxTypeDepth, resolveExternalTypes } = options;
|
|
283
|
+
const diagnostics = [];
|
|
284
|
+
const exports = [];
|
|
285
|
+
const result = createProgram({ entryFile, baseDir, content });
|
|
286
|
+
const { program, sourceFile } = result;
|
|
287
|
+
if (!sourceFile) {
|
|
288
|
+
return {
|
|
289
|
+
spec: createEmptySpec(entryFile),
|
|
290
|
+
diagnostics: [{ message: `Could not load source file: ${entryFile}`, severity: "error" }]
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const ctx = createContext(program, sourceFile, { maxTypeDepth, resolveExternalTypes });
|
|
294
|
+
const typeChecker = program.getTypeChecker();
|
|
295
|
+
const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
|
|
296
|
+
if (!moduleSymbol) {
|
|
297
|
+
return {
|
|
298
|
+
spec: createEmptySpec(entryFile),
|
|
299
|
+
diagnostics: [{ message: "Could not get module symbol", severity: "warning" }]
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
const exportedSymbols = typeChecker.getExportsOfModule(moduleSymbol);
|
|
303
|
+
for (const symbol of exportedSymbols) {
|
|
304
|
+
try {
|
|
305
|
+
const { declaration, targetSymbol } = resolveExportTarget(symbol, typeChecker);
|
|
306
|
+
if (!declaration)
|
|
307
|
+
continue;
|
|
308
|
+
const exportName = symbol.getName();
|
|
309
|
+
const exp = serializeDeclaration(declaration, targetSymbol, exportName, ctx);
|
|
310
|
+
if (exp)
|
|
311
|
+
exports.push(exp);
|
|
312
|
+
} catch (err) {
|
|
313
|
+
diagnostics.push({
|
|
314
|
+
message: `Failed to serialize ${symbol.getName()}: ${err}`,
|
|
315
|
+
severity: "warning"
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
const meta = await getPackageMeta(entryFile, baseDir);
|
|
320
|
+
const spec = {
|
|
321
|
+
openpkg: SCHEMA_VERSION,
|
|
322
|
+
meta,
|
|
323
|
+
exports,
|
|
324
|
+
types: ctx.typeRegistry.getAll(),
|
|
325
|
+
generation: {
|
|
326
|
+
generator: "@openpkg-ts/extract",
|
|
327
|
+
timestamp: new Date().toISOString()
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
return { spec, diagnostics };
|
|
331
|
+
}
|
|
332
|
+
function resolveExportTarget(symbol, checker) {
|
|
333
|
+
let targetSymbol = symbol;
|
|
334
|
+
if (symbol.flags & ts3.SymbolFlags.Alias) {
|
|
335
|
+
const aliasTarget = checker.getAliasedSymbol(symbol);
|
|
336
|
+
if (aliasTarget && aliasTarget !== symbol) {
|
|
337
|
+
targetSymbol = aliasTarget;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const declarations = targetSymbol.declarations ?? [];
|
|
341
|
+
const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts3.SyntaxKind.ExportSpecifier) || declarations[0];
|
|
342
|
+
return { declaration, targetSymbol };
|
|
343
|
+
}
|
|
344
|
+
function serializeDeclaration(declaration, symbol, exportName, ctx) {
|
|
345
|
+
let result = null;
|
|
346
|
+
if (ts3.isFunctionDeclaration(declaration)) {
|
|
347
|
+
result = serializeFunctionExport(declaration, ctx);
|
|
348
|
+
} else if (ts3.isClassDeclaration(declaration)) {
|
|
349
|
+
result = serializeClass(declaration, ctx);
|
|
350
|
+
} else if (ts3.isInterfaceDeclaration(declaration)) {
|
|
351
|
+
result = serializeInterface(declaration, ctx);
|
|
352
|
+
} else if (ts3.isTypeAliasDeclaration(declaration)) {
|
|
353
|
+
result = serializeTypeAlias(declaration, ctx);
|
|
354
|
+
} else if (ts3.isEnumDeclaration(declaration)) {
|
|
355
|
+
result = serializeEnum(declaration, ctx);
|
|
356
|
+
} else if (ts3.isVariableDeclaration(declaration)) {
|
|
357
|
+
const varStatement = declaration.parent?.parent;
|
|
358
|
+
if (varStatement && ts3.isVariableStatement(varStatement)) {
|
|
359
|
+
result = serializeVariable(declaration, varStatement, ctx);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (result) {
|
|
363
|
+
result = withExportName(result, exportName);
|
|
364
|
+
}
|
|
365
|
+
return result;
|
|
366
|
+
}
|
|
367
|
+
function withExportName(entry, exportName) {
|
|
368
|
+
if (entry.name === exportName) {
|
|
369
|
+
return entry;
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
...entry,
|
|
373
|
+
id: exportName,
|
|
374
|
+
name: entry.name
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function createEmptySpec(entryFile) {
|
|
378
|
+
return {
|
|
379
|
+
openpkg: SCHEMA_VERSION,
|
|
380
|
+
meta: { name: path2.basename(entryFile, path2.extname(entryFile)) },
|
|
381
|
+
exports: [],
|
|
382
|
+
generation: {
|
|
383
|
+
generator: "@openpkg-ts/extract",
|
|
384
|
+
timestamp: new Date().toISOString()
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
async function getPackageMeta(entryFile, baseDir) {
|
|
389
|
+
const searchDir = baseDir ?? path2.dirname(entryFile);
|
|
390
|
+
const pkgPath = path2.join(searchDir, "package.json");
|
|
391
|
+
try {
|
|
392
|
+
if (fs.existsSync(pkgPath)) {
|
|
393
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
394
|
+
return {
|
|
395
|
+
name: pkg.name ?? path2.basename(searchDir),
|
|
396
|
+
version: pkg.version,
|
|
397
|
+
description: pkg.description
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
} catch {}
|
|
401
|
+
return { name: path2.basename(searchDir) };
|
|
402
|
+
}
|
|
403
|
+
export { createProgram, TypeRegistry, getJSDocComment, getSourceLocation, extractParameters, serializeFunctionExport, serializeClass, serializeInterface, serializeTypeAlias, serializeEnum, serializeVariable, extract };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { OpenPkg } from "@openpkg-ts/spec";
|
|
2
|
+
interface ExtractOptions {
|
|
3
|
+
entryFile: string;
|
|
4
|
+
baseDir?: string;
|
|
5
|
+
content?: string;
|
|
6
|
+
maxTypeDepth?: number;
|
|
7
|
+
resolveExternalTypes?: boolean;
|
|
8
|
+
schemaExtraction?: "static" | "hybrid";
|
|
9
|
+
}
|
|
10
|
+
interface ExtractResult {
|
|
11
|
+
spec: OpenPkg;
|
|
12
|
+
diagnostics: Diagnostic[];
|
|
13
|
+
}
|
|
14
|
+
interface Diagnostic {
|
|
15
|
+
message: string;
|
|
16
|
+
severity: "error" | "warning" | "info";
|
|
17
|
+
location?: {
|
|
18
|
+
file?: string;
|
|
19
|
+
line?: number;
|
|
20
|
+
column?: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
interface SerializerContext {
|
|
24
|
+
typeChecker: import("typescript").TypeChecker;
|
|
25
|
+
program: import("typescript").Program;
|
|
26
|
+
sourceFile: import("typescript").SourceFile;
|
|
27
|
+
maxTypeDepth: number;
|
|
28
|
+
resolveExternalTypes: boolean;
|
|
29
|
+
}
|
|
30
|
+
declare function extract(options: ExtractOptions): Promise<ExtractResult>;
|
|
31
|
+
import ts from "typescript";
|
|
32
|
+
import { SpecSchema } from "@openpkg-ts/spec";
|
|
33
|
+
type SchemaAdapter = {
|
|
34
|
+
name: string;
|
|
35
|
+
detect: (node: ts.Node, checker: ts.TypeChecker) => boolean;
|
|
36
|
+
extract: (node: ts.Node, checker: ts.TypeChecker) => SpecSchema | null;
|
|
37
|
+
};
|
|
38
|
+
declare function registerAdapter(adapter: SchemaAdapter): void;
|
|
39
|
+
declare function findAdapter(node: ts.Node, checker: ts.TypeChecker): SchemaAdapter | undefined;
|
|
40
|
+
declare function isSchemaType(node: ts.Node, checker: ts.TypeChecker): boolean;
|
|
41
|
+
declare function extractSchemaType(node: ts.Node, checker: ts.TypeChecker): SpecSchema | null;
|
|
42
|
+
declare const zodAdapter: SchemaAdapter;
|
|
43
|
+
declare const typeboxAdapter: SchemaAdapter;
|
|
44
|
+
declare const arktypeAdapter: SchemaAdapter;
|
|
45
|
+
declare const valibotAdapter: SchemaAdapter;
|
|
46
|
+
import ts2 from "typescript";
|
|
47
|
+
import { SpecSchema as SpecSchema2 } from "@openpkg-ts/spec";
|
|
48
|
+
interface StandardSchemaResult {
|
|
49
|
+
schema: SpecSchema2;
|
|
50
|
+
vendor: string;
|
|
51
|
+
}
|
|
52
|
+
declare function extractStandardSchemas(_program: ts2.Program, _entryFile: string): StandardSchemaResult[];
|
|
53
|
+
import ts3 from "typescript";
|
|
54
|
+
declare function formatTypeReference(type: ts3.Type, checker: ts3.TypeChecker): string;
|
|
55
|
+
declare function collectReferencedTypes(type: ts3.Type, checker: ts3.TypeChecker, visited?: Set<string>): string[];
|
|
56
|
+
import ts4 from "typescript";
|
|
57
|
+
declare function isExported(node: ts4.Node): boolean;
|
|
58
|
+
declare function getNodeName(node: ts4.Node): string | undefined;
|
|
59
|
+
import ts5 from "typescript";
|
|
60
|
+
import { SpecSignatureParameter } from "@openpkg-ts/spec";
|
|
61
|
+
declare function extractParameters(signature: ts5.Signature, checker: ts5.TypeChecker): SpecSignatureParameter[];
|
|
62
|
+
import ts6 from "typescript";
|
|
63
|
+
import { SpecSchema as SpecSchema3 } from "@openpkg-ts/spec";
|
|
64
|
+
declare function buildSchema(type: ts6.Type, checker: ts6.TypeChecker, depth?: number): SpecSchema3;
|
|
65
|
+
import ts7 from "typescript";
|
|
66
|
+
import { SpecSource, SpecTag } from "@openpkg-ts/spec";
|
|
67
|
+
declare function getJSDocComment(node: ts7.Node): {
|
|
68
|
+
description?: string;
|
|
69
|
+
tags: SpecTag[];
|
|
70
|
+
};
|
|
71
|
+
declare function getSourceLocation(node: ts7.Node, sourceFile: ts7.SourceFile): SpecSource;
|
|
72
|
+
import ts8 from "typescript";
|
|
73
|
+
import { SpecType } from "@openpkg-ts/spec";
|
|
74
|
+
declare class TypeRegistry {
|
|
75
|
+
private types;
|
|
76
|
+
add(type: SpecType): void;
|
|
77
|
+
get(id: string): SpecType | undefined;
|
|
78
|
+
has(id: string): boolean;
|
|
79
|
+
getAll(): SpecType[];
|
|
80
|
+
registerFromSymbol(symbol: ts8.Symbol, checker: ts8.TypeChecker): SpecType | undefined;
|
|
81
|
+
}
|
|
82
|
+
import ts10 from "typescript";
|
|
83
|
+
import { SpecExport } from "@openpkg-ts/spec";
|
|
84
|
+
import ts9 from "typescript";
|
|
85
|
+
interface SerializerContext2 {
|
|
86
|
+
typeChecker: ts9.TypeChecker;
|
|
87
|
+
program: ts9.Program;
|
|
88
|
+
sourceFile: ts9.SourceFile;
|
|
89
|
+
maxTypeDepth: number;
|
|
90
|
+
resolveExternalTypes: boolean;
|
|
91
|
+
typeRegistry: TypeRegistry;
|
|
92
|
+
}
|
|
93
|
+
declare function serializeFunctionExport(node: ts10.FunctionDeclaration | ts10.ArrowFunction, ctx: SerializerContext2): SpecExport | null;
|
|
94
|
+
import ts11 from "typescript";
|
|
95
|
+
import { SpecExport as SpecExport2 } from "@openpkg-ts/spec";
|
|
96
|
+
declare function serializeClass(node: ts11.ClassDeclaration, ctx: SerializerContext2): SpecExport2 | null;
|
|
97
|
+
import ts12 from "typescript";
|
|
98
|
+
import { SpecExport as SpecExport3 } from "@openpkg-ts/spec";
|
|
99
|
+
declare function serializeInterface(node: ts12.InterfaceDeclaration, ctx: SerializerContext2): SpecExport3 | null;
|
|
100
|
+
import ts13 from "typescript";
|
|
101
|
+
import { SpecExport as SpecExport4 } from "@openpkg-ts/spec";
|
|
102
|
+
declare function serializeTypeAlias(node: ts13.TypeAliasDeclaration, ctx: SerializerContext2): SpecExport4 | null;
|
|
103
|
+
import ts14 from "typescript";
|
|
104
|
+
import { SpecExport as SpecExport5 } from "@openpkg-ts/spec";
|
|
105
|
+
declare function serializeEnum(node: ts14.EnumDeclaration, ctx: SerializerContext2): SpecExport5 | null;
|
|
106
|
+
import ts15 from "typescript";
|
|
107
|
+
import { SpecExport as SpecExport6 } from "@openpkg-ts/spec";
|
|
108
|
+
declare function serializeVariable(node: ts15.VariableDeclaration, statement: ts15.VariableStatement, ctx: SerializerContext2): SpecExport6 | null;
|
|
109
|
+
import ts16 from "typescript";
|
|
110
|
+
interface ProgramOptions {
|
|
111
|
+
entryFile: string;
|
|
112
|
+
baseDir?: string;
|
|
113
|
+
content?: string;
|
|
114
|
+
}
|
|
115
|
+
interface ProgramResult {
|
|
116
|
+
program: ts16.Program;
|
|
117
|
+
compilerHost: ts16.CompilerHost;
|
|
118
|
+
compilerOptions: ts16.CompilerOptions;
|
|
119
|
+
sourceFile?: ts16.SourceFile;
|
|
120
|
+
configPath?: string;
|
|
121
|
+
}
|
|
122
|
+
declare function createProgram({ entryFile, baseDir, content }: ProgramOptions): ProgramResult;
|
|
123
|
+
export { zodAdapter, valibotAdapter, typeboxAdapter, serializeVariable, serializeTypeAlias, serializeInterface, serializeFunctionExport, serializeEnum, serializeClass, registerAdapter, isSchemaType, isExported, getSourceLocation, getNodeName, getJSDocComment, formatTypeReference, findAdapter, extractStandardSchemas, extractSchemaType, extractParameters, extract, createProgram, collectReferencedTypes, buildSchema, arktypeAdapter, TypeRegistry, StandardSchemaResult, SerializerContext, SchemaAdapter, ProgramResult, ProgramOptions, ExtractResult, ExtractOptions, Diagnostic };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TypeRegistry,
|
|
3
|
+
createProgram,
|
|
4
|
+
extract,
|
|
5
|
+
extractParameters,
|
|
6
|
+
getJSDocComment,
|
|
7
|
+
getSourceLocation,
|
|
8
|
+
serializeClass,
|
|
9
|
+
serializeEnum,
|
|
10
|
+
serializeFunctionExport,
|
|
11
|
+
serializeInterface,
|
|
12
|
+
serializeTypeAlias,
|
|
13
|
+
serializeVariable
|
|
14
|
+
} from "../shared/chunk-esvc8n1x.js";
|
|
15
|
+
// src/schema/adapters/zod.ts
|
|
16
|
+
var zodAdapter = {
|
|
17
|
+
name: "zod",
|
|
18
|
+
detect: () => false,
|
|
19
|
+
extract: () => null
|
|
20
|
+
};
|
|
21
|
+
// src/schema/adapters/typebox.ts
|
|
22
|
+
var typeboxAdapter = {
|
|
23
|
+
name: "typebox",
|
|
24
|
+
detect: () => false,
|
|
25
|
+
extract: () => null
|
|
26
|
+
};
|
|
27
|
+
// src/schema/adapters/arktype.ts
|
|
28
|
+
var arktypeAdapter = {
|
|
29
|
+
name: "arktype",
|
|
30
|
+
detect: () => false,
|
|
31
|
+
extract: () => null
|
|
32
|
+
};
|
|
33
|
+
// src/schema/adapters/valibot.ts
|
|
34
|
+
var valibotAdapter = {
|
|
35
|
+
name: "valibot",
|
|
36
|
+
detect: () => false,
|
|
37
|
+
extract: () => null
|
|
38
|
+
};
|
|
39
|
+
// src/schema/registry.ts
|
|
40
|
+
var adapters = [];
|
|
41
|
+
function registerAdapter(adapter) {
|
|
42
|
+
adapters.push(adapter);
|
|
43
|
+
}
|
|
44
|
+
function findAdapter(node, checker) {
|
|
45
|
+
return adapters.find((a) => a.detect(node, checker));
|
|
46
|
+
}
|
|
47
|
+
function isSchemaType(node, checker) {
|
|
48
|
+
return adapters.some((a) => a.detect(node, checker));
|
|
49
|
+
}
|
|
50
|
+
function extractSchemaType(node, checker) {
|
|
51
|
+
const adapter = findAdapter(node, checker);
|
|
52
|
+
return adapter?.extract(node, checker) ?? null;
|
|
53
|
+
}
|
|
54
|
+
// src/schema/standard-schema.ts
|
|
55
|
+
function extractStandardSchemas(_program, _entryFile) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
// src/types/formatter.ts
|
|
59
|
+
function formatTypeReference(type, checker) {
|
|
60
|
+
return checker.typeToString(type);
|
|
61
|
+
}
|
|
62
|
+
function collectReferencedTypes(type, checker, visited = new Set) {
|
|
63
|
+
const symbol = type.getSymbol();
|
|
64
|
+
if (!symbol)
|
|
65
|
+
return [];
|
|
66
|
+
const name = symbol.getName();
|
|
67
|
+
if (visited.has(name))
|
|
68
|
+
return [];
|
|
69
|
+
visited.add(name);
|
|
70
|
+
return [name];
|
|
71
|
+
}
|
|
72
|
+
// src/types/utils.ts
|
|
73
|
+
function isExported(node) {
|
|
74
|
+
const modifiers = node.modifiers;
|
|
75
|
+
if (!modifiers)
|
|
76
|
+
return false;
|
|
77
|
+
return modifiers.some((m) => m.kind === 95);
|
|
78
|
+
}
|
|
79
|
+
function getNodeName(node) {
|
|
80
|
+
if ("name" in node && node.name) {
|
|
81
|
+
const name = node.name;
|
|
82
|
+
return name.text;
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// src/types/schema-builder.ts
|
|
87
|
+
function buildSchema(type, checker, depth = 0) {
|
|
88
|
+
if (depth > 10) {
|
|
89
|
+
return { type: checker.typeToString(type) };
|
|
90
|
+
}
|
|
91
|
+
const typeString = checker.typeToString(type);
|
|
92
|
+
if (type.flags & 4)
|
|
93
|
+
return { type: "string" };
|
|
94
|
+
if (type.flags & 8)
|
|
95
|
+
return { type: "number" };
|
|
96
|
+
if (type.flags & 16)
|
|
97
|
+
return { type: "boolean" };
|
|
98
|
+
if (type.flags & 32768)
|
|
99
|
+
return { type: "undefined" };
|
|
100
|
+
if (type.flags & 65536)
|
|
101
|
+
return { type: "null" };
|
|
102
|
+
if (type.flags & 16384)
|
|
103
|
+
return { type: "void" };
|
|
104
|
+
if (type.flags & 1)
|
|
105
|
+
return { type: "any" };
|
|
106
|
+
if (type.flags & 2)
|
|
107
|
+
return { type: "unknown" };
|
|
108
|
+
if (type.flags & 131072)
|
|
109
|
+
return { type: "never" };
|
|
110
|
+
return { type: typeString };
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
zodAdapter,
|
|
114
|
+
valibotAdapter,
|
|
115
|
+
typeboxAdapter,
|
|
116
|
+
serializeVariable,
|
|
117
|
+
serializeTypeAlias,
|
|
118
|
+
serializeInterface,
|
|
119
|
+
serializeFunctionExport,
|
|
120
|
+
serializeEnum,
|
|
121
|
+
serializeClass,
|
|
122
|
+
registerAdapter,
|
|
123
|
+
isSchemaType,
|
|
124
|
+
isExported,
|
|
125
|
+
getSourceLocation,
|
|
126
|
+
getNodeName,
|
|
127
|
+
getJSDocComment,
|
|
128
|
+
formatTypeReference,
|
|
129
|
+
findAdapter,
|
|
130
|
+
extractStandardSchemas,
|
|
131
|
+
extractSchemaType,
|
|
132
|
+
extractParameters,
|
|
133
|
+
extract,
|
|
134
|
+
createProgram,
|
|
135
|
+
collectReferencedTypes,
|
|
136
|
+
buildSchema,
|
|
137
|
+
arktypeAdapter,
|
|
138
|
+
TypeRegistry
|
|
139
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openpkg-ts/extract",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript export extraction to OpenPkg spec",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"openpkg",
|
|
7
|
+
"typescript",
|
|
8
|
+
"api-extractor",
|
|
9
|
+
"documentation",
|
|
10
|
+
"type-extraction"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/doccov/doccov#readme",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/doccov/doccov.git",
|
|
16
|
+
"directory": "packages/extract"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Ryan Waits",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"bin": {
|
|
24
|
+
"tspec": "./dist/bin/tspec.js"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "bunup",
|
|
37
|
+
"dev": "bunup --watch",
|
|
38
|
+
"lint": "biome check src/",
|
|
39
|
+
"lint:fix": "biome check --write src/",
|
|
40
|
+
"format": "biome format --write src/"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@openpkg-ts/spec": "workspace:*",
|
|
44
|
+
"commander": "^12.0.0",
|
|
45
|
+
"typescript": "^5.0.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/bun": "latest",
|
|
49
|
+
"@types/node": "^20.0.0",
|
|
50
|
+
"bunup": "latest"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
}
|
|
55
|
+
}
|