shokupan 0.4.5 → 0.6.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/README.md +10 -9
- package/dist/analysis/openapi-analyzer.d.ts +0 -4
- package/dist/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/dist/context.d.ts +30 -8
- package/dist/index.cjs +692 -461
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +635 -426
- package/dist/index.js.map +1 -1
- package/dist/json-parser-B3dnQmCC.js +35 -0
- package/dist/json-parser-B3dnQmCC.js.map +1 -0
- package/dist/json-parser-COdZ0fqY.cjs +35 -0
- package/dist/json-parser-COdZ0fqY.cjs.map +1 -0
- package/dist/{openapi-analyzer-D9YB3IkV.cjs → openapi-analyzer-Bei1sVWp.cjs} +63 -49
- package/dist/openapi-analyzer-Bei1sVWp.cjs.map +1 -0
- package/dist/{openapi-analyzer-BtIaHIfe.js → openapi-analyzer-Ce_7JxZh.js} +63 -49
- package/dist/openapi-analyzer-Ce_7JxZh.js.map +1 -0
- package/dist/plugins/scalar.d.ts +1 -1
- package/dist/router.d.ts +33 -22
- package/dist/{server-adapter-BWrEJbKL.js → server-adapter-0xH174zz.js} +4 -2
- package/dist/server-adapter-0xH174zz.js.map +1 -0
- package/dist/{server-adapter-fVKP60e0.cjs → server-adapter-DFhwlK8e.cjs} +4 -2
- package/dist/server-adapter-DFhwlK8e.cjs.map +1 -0
- package/dist/shokupan.d.ts +4 -8
- package/dist/types.d.ts +32 -3
- package/dist/util/datastore.d.ts +6 -0
- package/dist/util/json-parser.d.ts +12 -0
- package/dist/util/plugin-deps.d.ts +25 -0
- package/package.json +74 -14
- package/dist/benchmarking/advanced-cases/elysia.d.ts +0 -1
- package/dist/benchmarking/advanced-cases/express.d.ts +0 -1
- package/dist/benchmarking/advanced-cases/fastify.d.ts +0 -1
- package/dist/benchmarking/advanced-cases/hapi.d.ts +0 -1
- package/dist/benchmarking/advanced-cases/hono.d.ts +0 -1
- package/dist/benchmarking/advanced-cases/koa.d.ts +0 -1
- package/dist/benchmarking/advanced-cases/nest.d.ts +0 -1
- package/dist/benchmarking/advanced-cases/shokupan.d.ts +0 -1
- package/dist/benchmarking/advanced-data.d.ts +0 -33
- package/dist/benchmarking/advanced-runner.d.ts +0 -1
- package/dist/benchmarking/advanced-worker.d.ts +0 -0
- package/dist/benchmarking/cases/elysia.d.ts +0 -1
- package/dist/benchmarking/cases/express.d.ts +0 -1
- package/dist/benchmarking/cases/fastify.d.ts +0 -1
- package/dist/benchmarking/cases/hapi.d.ts +0 -1
- package/dist/benchmarking/cases/hono.d.ts +0 -1
- package/dist/benchmarking/cases/koa.d.ts +0 -1
- package/dist/benchmarking/cases/nest.d.ts +0 -1
- package/dist/benchmarking/cases/shokupan.d.ts +0 -1
- package/dist/benchmarking/data.d.ts +0 -15
- package/dist/benchmarking/quick_bench.d.ts +0 -1
- package/dist/benchmarking/runner.d.ts +0 -1
- package/dist/benchmarking/worker.d.ts +0 -0
- package/dist/buntest.d.ts +0 -1
- package/dist/openapi-analyzer-BtIaHIfe.js.map +0 -1
- package/dist/openapi-analyzer-D9YB3IkV.cjs.map +0 -1
- package/dist/server-adapter-BWrEJbKL.js.map +0 -1
- package/dist/server-adapter-fVKP60e0.cjs.map +0 -1
|
@@ -31,21 +31,18 @@ class OpenAPIAnalyzer {
|
|
|
31
31
|
async scanDirectory(dir) {
|
|
32
32
|
try {
|
|
33
33
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
34
|
-
for (
|
|
34
|
+
for (let i = 0; i < entries.length; i++) {
|
|
35
|
+
const entry = entries[i];
|
|
35
36
|
const fullPath = path.join(dir, entry.name);
|
|
36
37
|
if (entry.isDirectory()) {
|
|
37
|
-
if (
|
|
38
|
+
if (["node_modules", ".git", "dist"].includes(entry.name)) {
|
|
38
39
|
continue;
|
|
39
40
|
}
|
|
40
41
|
await this.scanDirectory(fullPath);
|
|
41
42
|
} else {
|
|
42
43
|
const ext = path.extname(entry.name);
|
|
43
|
-
if (
|
|
44
|
-
this.files.push({ path: fullPath, type:
|
|
45
|
-
} else if (ext === ".js") {
|
|
46
|
-
this.files.push({ path: fullPath, type: "js" });
|
|
47
|
-
} else if (ext === ".map") {
|
|
48
|
-
this.files.push({ path: fullPath, type: "map" });
|
|
44
|
+
if ([".ts", ".tsx", ".cts", ".dts", ".mts", ".js", ".jsx", ".mjs", ".cjs", ".map"].includes(ext)) {
|
|
45
|
+
this.files.push({ path: fullPath, type: ext.slice(1) });
|
|
49
46
|
}
|
|
50
47
|
}
|
|
51
48
|
}
|
|
@@ -61,7 +58,8 @@ class OpenAPIAnalyzer {
|
|
|
61
58
|
async processSourceMaps() {
|
|
62
59
|
const jsFiles = this.files.filter((f) => f.type === "js");
|
|
63
60
|
const mapFiles = this.files.filter((f) => f.type === "map");
|
|
64
|
-
for (
|
|
61
|
+
for (let i = 0; i < jsFiles.length; i++) {
|
|
62
|
+
const jsFile = jsFiles[i];
|
|
65
63
|
const mapFile = mapFiles.find((m) => m.path === jsFile.path + ".map");
|
|
66
64
|
if (mapFile && !this.files.some((f) => f.path === jsFile.path.replace(/\.js$/, ".ts"))) {
|
|
67
65
|
console.log(`Note: Found ${jsFile.path} with source map but no .ts file. Will parse JS directly.`);
|
|
@@ -99,10 +97,17 @@ class OpenAPIAnalyzer {
|
|
|
99
97
|
async findApplications() {
|
|
100
98
|
if (!this.program) return;
|
|
101
99
|
const typeChecker = this.program.getTypeChecker();
|
|
102
|
-
for (
|
|
100
|
+
for (let i = 0; i < this.program.getSourceFiles().length; i++) {
|
|
101
|
+
const sourceFile = this.program.getSourceFiles()[i];
|
|
103
102
|
if (sourceFile.fileName.includes("node_modules")) continue;
|
|
104
103
|
if (sourceFile.isDeclarationFile) continue;
|
|
105
|
-
|
|
104
|
+
const isTestEnv = this.rootDir.includes("/test/") || this.rootDir.includes("/tests/") || this.rootDir.includes("/fixtures/");
|
|
105
|
+
const isFixtureFile = sourceFile.fileName.includes("/fixtures/");
|
|
106
|
+
if (!isTestEnv && !isFixtureFile) {
|
|
107
|
+
if (sourceFile.fileName.includes("/test/") || sourceFile.fileName.includes("/tests/")) continue;
|
|
108
|
+
if (sourceFile.fileName.includes("/base_test/")) continue;
|
|
109
|
+
if (sourceFile.fileName.includes(".test.ts") || sourceFile.fileName.includes(".spec.ts")) continue;
|
|
110
|
+
}
|
|
106
111
|
ts.forEachChild(sourceFile, (node) => {
|
|
107
112
|
this.visitNode(node, sourceFile, typeChecker);
|
|
108
113
|
});
|
|
@@ -115,8 +120,9 @@ class OpenAPIAnalyzer {
|
|
|
115
120
|
if (ts.isClassDeclaration(node)) {
|
|
116
121
|
let isController = false;
|
|
117
122
|
let className = node.name?.getText(sourceFile);
|
|
118
|
-
|
|
119
|
-
|
|
123
|
+
const decorators = node.decorators || node.modifiers?.filter((m) => ts.isDecorator(m));
|
|
124
|
+
if (decorators) {
|
|
125
|
+
const controllerDecorator = decorators.find((d) => {
|
|
120
126
|
const expr = d.expression;
|
|
121
127
|
if (ts.isCallExpression(expr)) {
|
|
122
128
|
const identifier = expr.expression.getText(sourceFile);
|
|
@@ -135,7 +141,7 @@ class OpenAPIAnalyzer {
|
|
|
135
141
|
const expr = d.expression;
|
|
136
142
|
if (ts.isCallExpression(expr)) {
|
|
137
143
|
const identifier = expr.expression.getText(sourceFile);
|
|
138
|
-
return ["
|
|
144
|
+
return ["get", "post", "put", "delete", "patch", "options", "head"].includes(identifier.toLowerCase());
|
|
139
145
|
}
|
|
140
146
|
return false;
|
|
141
147
|
});
|
|
@@ -180,7 +186,8 @@ class OpenAPIAnalyzer {
|
|
|
180
186
|
*/
|
|
181
187
|
async extractRoutes() {
|
|
182
188
|
if (!this.program) return;
|
|
183
|
-
for (
|
|
189
|
+
for (let i = 0; i < this.applications.length; i++) {
|
|
190
|
+
const app = this.applications[i];
|
|
184
191
|
const sourceFile = this.program.getSourceFile(app.filePath);
|
|
185
192
|
if (!sourceFile) continue;
|
|
186
193
|
this.extractRoutesFromFile(app, sourceFile);
|
|
@@ -191,7 +198,8 @@ class OpenAPIAnalyzer {
|
|
|
191
198
|
*/
|
|
192
199
|
extractRoutesFromController(app, classNode, sourceFile) {
|
|
193
200
|
const methods = classNode.members.filter((m) => ts.isMethodDeclaration(m) || m.kind === 175);
|
|
194
|
-
for (
|
|
201
|
+
for (let i = 0; i < methods.length; i++) {
|
|
202
|
+
const method = methods[i];
|
|
195
203
|
const methodNode = method;
|
|
196
204
|
if (!methodNode.decorators && !methodNode.modifiers) continue;
|
|
197
205
|
const decorators = methodNode.decorators || methodNode.modifiers?.filter((m) => ts.isDecorator(m));
|
|
@@ -200,7 +208,7 @@ class OpenAPIAnalyzer {
|
|
|
200
208
|
const expr = d.expression;
|
|
201
209
|
if (ts.isCallExpression(expr)) {
|
|
202
210
|
const identifier = expr.expression.getText(sourceFile);
|
|
203
|
-
return ["
|
|
211
|
+
return ["get", "post", "put", "delete", "patch", "options", "head"].includes(identifier.toLowerCase());
|
|
204
212
|
}
|
|
205
213
|
return false;
|
|
206
214
|
});
|
|
@@ -244,7 +252,7 @@ class OpenAPIAnalyzer {
|
|
|
244
252
|
const objName = expr.expression.getText(sourceFile);
|
|
245
253
|
const methodName = expr.name.getText(sourceFile);
|
|
246
254
|
if (objName === app.name) {
|
|
247
|
-
if (["get", "post", "put", "delete", "patch", "options", "head"].includes(methodName)) {
|
|
255
|
+
if (["get", "post", "put", "delete", "patch", "options", "head"].includes(methodName.toLowerCase())) {
|
|
248
256
|
const route = this.extractRouteFromCall(node, sourceFile, methodName.toUpperCase());
|
|
249
257
|
if (route) {
|
|
250
258
|
app.routes.push(route);
|
|
@@ -279,7 +287,8 @@ class OpenAPIAnalyzer {
|
|
|
279
287
|
if (args.length >= 3 && ts.isObjectLiteralExpression(args[1])) {
|
|
280
288
|
const metaObj = args[1];
|
|
281
289
|
this.convertExpressionToSchema(metaObj, sourceFile, /* @__PURE__ */ new Map());
|
|
282
|
-
for (
|
|
290
|
+
for (let i = 0; i < metaObj.properties.length; i++) {
|
|
291
|
+
const prop = metaObj.properties[i];
|
|
283
292
|
if (ts.isPropertyAssignment(prop) && prop.name) {
|
|
284
293
|
const name = prop.name.getText(sourceFile);
|
|
285
294
|
const val = prop.initializer;
|
|
@@ -314,6 +323,7 @@ class OpenAPIAnalyzer {
|
|
|
314
323
|
const requestTypes = {};
|
|
315
324
|
let responseType;
|
|
316
325
|
let responseSchema;
|
|
326
|
+
let hasExplicitReturnType = false;
|
|
317
327
|
const scope = /* @__PURE__ */ new Map();
|
|
318
328
|
if (ts.isFunctionLike(handler)) {
|
|
319
329
|
handler.parameters.forEach((param) => {
|
|
@@ -325,6 +335,14 @@ class OpenAPIAnalyzer {
|
|
|
325
335
|
}
|
|
326
336
|
}
|
|
327
337
|
});
|
|
338
|
+
if (handler.type) {
|
|
339
|
+
const returnSchema = this.convertTypeNodeToSchema(handler.type, sourceFile);
|
|
340
|
+
if (returnSchema) {
|
|
341
|
+
responseSchema = returnSchema;
|
|
342
|
+
responseType = returnSchema.type;
|
|
343
|
+
hasExplicitReturnType = true;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
328
346
|
}
|
|
329
347
|
const analyzeReturnExpression = (expr) => {
|
|
330
348
|
let node = expr;
|
|
@@ -347,7 +365,7 @@ class OpenAPIAnalyzer {
|
|
|
347
365
|
}
|
|
348
366
|
}
|
|
349
367
|
}
|
|
350
|
-
if (!responseSchema || responseSchema.type === "object") {
|
|
368
|
+
if (!hasExplicitReturnType && (!responseSchema || responseSchema.type === "object")) {
|
|
351
369
|
const schema = this.convertExpressionToSchema(node, sourceFile, scope);
|
|
352
370
|
if (schema && (schema.type !== "object" || Object.keys(schema.properties || {}).length > 0)) {
|
|
353
371
|
responseSchema = schema;
|
|
@@ -428,7 +446,8 @@ class OpenAPIAnalyzer {
|
|
|
428
446
|
properties: {},
|
|
429
447
|
required: []
|
|
430
448
|
};
|
|
431
|
-
for (
|
|
449
|
+
for (let i = 0; i < node.properties.length; i++) {
|
|
450
|
+
const prop = node.properties[i];
|
|
432
451
|
if (ts.isPropertyAssignment(prop)) {
|
|
433
452
|
const name = prop.name.getText(sourceFile);
|
|
434
453
|
const valueSchema = this.convertExpressionToSchema(prop.initializer, sourceFile, scope);
|
|
@@ -511,7 +530,8 @@ class OpenAPIAnalyzer {
|
|
|
511
530
|
properties: {},
|
|
512
531
|
required: []
|
|
513
532
|
};
|
|
514
|
-
for (
|
|
533
|
+
for (let i = 0; i < literal.members.length; i++) {
|
|
534
|
+
const member = literal.members[i];
|
|
515
535
|
if (ts.isPropertySignature(member) && member.type) {
|
|
516
536
|
const name = member.name.getText(sourceFile);
|
|
517
537
|
const propSchema = this.convertTypeNodeToSchema(member.type, sourceFile);
|
|
@@ -537,12 +557,15 @@ class OpenAPIAnalyzer {
|
|
|
537
557
|
case ts.SyntaxKind.TypeReference: {
|
|
538
558
|
const typeRef = typeNode;
|
|
539
559
|
const typeName = typeRef.typeName.getText(sourceFile);
|
|
540
|
-
if (typeName === "Array" && typeRef.typeArguments
|
|
560
|
+
if (typeName === "Array" && typeRef.typeArguments?.length > 0) {
|
|
541
561
|
return {
|
|
542
562
|
type: "array",
|
|
543
563
|
items: this.convertTypeNodeToSchema(typeRef.typeArguments[0], sourceFile)
|
|
544
564
|
};
|
|
545
565
|
}
|
|
566
|
+
if (typeName === "Promise" && typeRef.typeArguments?.length > 0) {
|
|
567
|
+
return this.convertTypeNodeToSchema(typeRef.typeArguments[0], sourceFile);
|
|
568
|
+
}
|
|
546
569
|
return { type: "object", description: `Ref: ${typeName}` };
|
|
547
570
|
}
|
|
548
571
|
default:
|
|
@@ -579,14 +602,16 @@ class OpenAPIAnalyzer {
|
|
|
579
602
|
imports.push(node);
|
|
580
603
|
}
|
|
581
604
|
});
|
|
582
|
-
for (
|
|
605
|
+
for (let i = 0; i < imports.length; i++) {
|
|
606
|
+
const imp = imports[i];
|
|
583
607
|
const moduleSpecifier = imp.moduleSpecifier;
|
|
584
608
|
if (ts.isStringLiteral(moduleSpecifier)) {
|
|
585
609
|
const modulePath = moduleSpecifier.text;
|
|
586
610
|
if (!modulePath.startsWith(".") && !modulePath.startsWith("/")) {
|
|
587
611
|
const namedBindings = imp.importClause?.namedBindings;
|
|
588
612
|
if (namedBindings && ts.isNamedImports(namedBindings)) {
|
|
589
|
-
for (
|
|
613
|
+
for (let j = 0; j < namedBindings.elements.length; j++) {
|
|
614
|
+
const element = namedBindings.elements[j];
|
|
590
615
|
if (element.name.text === identifier) {
|
|
591
616
|
const version = this.getPackageVersion(modulePath);
|
|
592
617
|
return {
|
|
@@ -623,7 +648,8 @@ class OpenAPIAnalyzer {
|
|
|
623
648
|
generateOpenAPISpec() {
|
|
624
649
|
const paths = {};
|
|
625
650
|
const collectRoutes = (app, prefix = "") => {
|
|
626
|
-
for (
|
|
651
|
+
for (let i = 0; i < app.routes.length; i++) {
|
|
652
|
+
const route = app.routes[i];
|
|
627
653
|
const cleanPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
628
654
|
const cleanPath = route.path.startsWith("/") ? route.path : "/" + route.path;
|
|
629
655
|
const fullPath = cleanPrefix + cleanPath || "/";
|
|
@@ -677,7 +703,9 @@ class OpenAPIAnalyzer {
|
|
|
677
703
|
}
|
|
678
704
|
const parameters = [];
|
|
679
705
|
if (route.requestTypes?.query) {
|
|
680
|
-
|
|
706
|
+
const entries = Object.entries(route.requestTypes.query);
|
|
707
|
+
for (let i2 = 0; i2 < entries.length; i2++) {
|
|
708
|
+
const [key] = entries[i2];
|
|
681
709
|
parameters.push({
|
|
682
710
|
name: key,
|
|
683
711
|
in: "query",
|
|
@@ -686,7 +714,9 @@ class OpenAPIAnalyzer {
|
|
|
686
714
|
}
|
|
687
715
|
}
|
|
688
716
|
if (route.requestTypes?.params) {
|
|
689
|
-
|
|
717
|
+
const entries = Object.entries(route.requestTypes.params);
|
|
718
|
+
for (let i2 = 0; i2 < entries.length; i2++) {
|
|
719
|
+
const [key] = entries[i2];
|
|
690
720
|
parameters.push({
|
|
691
721
|
name: key,
|
|
692
722
|
in: "path",
|
|
@@ -715,7 +745,8 @@ class OpenAPIAnalyzer {
|
|
|
715
745
|
}
|
|
716
746
|
paths[pathKey][method] = operation;
|
|
717
747
|
}
|
|
718
|
-
for (
|
|
748
|
+
for (let i = 0; i < app.mounted.length; i++) {
|
|
749
|
+
const mount = app.mounted[i];
|
|
719
750
|
const mountedApp = this.applications.find((a) => a.name === mount.target || a.className === mount.target);
|
|
720
751
|
if (mountedApp) {
|
|
721
752
|
if (mountedApp === app) continue;
|
|
@@ -726,7 +757,8 @@ class OpenAPIAnalyzer {
|
|
|
726
757
|
}
|
|
727
758
|
}
|
|
728
759
|
};
|
|
729
|
-
for (
|
|
760
|
+
for (let i = 0; i < this.applications.length; i++) {
|
|
761
|
+
const app = this.applications[i];
|
|
730
762
|
const isMounted = this.applications.some(
|
|
731
763
|
(parent) => parent.mounted.some((m) => m.target === app.name || m.target === app.className)
|
|
732
764
|
);
|
|
@@ -747,24 +779,6 @@ class OpenAPIAnalyzer {
|
|
|
747
779
|
}
|
|
748
780
|
};
|
|
749
781
|
}
|
|
750
|
-
/**
|
|
751
|
-
* Convert a type string to an OpenAPI schema
|
|
752
|
-
*/
|
|
753
|
-
typeToSchema(type) {
|
|
754
|
-
switch (type) {
|
|
755
|
-
case "string":
|
|
756
|
-
return { type: "string" };
|
|
757
|
-
case "number":
|
|
758
|
-
return { type: "number" };
|
|
759
|
-
case "boolean":
|
|
760
|
-
return { type: "boolean" };
|
|
761
|
-
case "array":
|
|
762
|
-
return { type: "array", items: {} };
|
|
763
|
-
case "object":
|
|
764
|
-
default:
|
|
765
|
-
return { type: "object" };
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
782
|
}
|
|
769
783
|
async function analyzeDirectory(directory) {
|
|
770
784
|
const analyzer = new OpenAPIAnalyzer(directory);
|
|
@@ -774,4 +788,4 @@ export {
|
|
|
774
788
|
OpenAPIAnalyzer,
|
|
775
789
|
analyzeDirectory
|
|
776
790
|
};
|
|
777
|
-
//# sourceMappingURL=openapi-analyzer-
|
|
791
|
+
//# sourceMappingURL=openapi-analyzer-Ce_7JxZh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi-analyzer-Ce_7JxZh.js","sources":["../src/analysis/openapi-analyzer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport ts from 'typescript';\n\n/**\n * File information collected during scan\n */\ninterface CollectedFile {\n path: string;\n type: 'ts' | 'tsx' | 'cts' | 'dts' | 'mts' | 'js' | 'jsx' | 'mjs' | 'cjs' | 'map';\n content?: string;\n}\n\n/**\n * Route information extracted from AST\n */\nexport interface RouteInfo {\n method: string;\n path: string;\n handlerName?: string;\n handlerSource?: string;\n requestTypes?: {\n body?: any;\n query?: Record<string, string>;\n params?: Record<string, string>;\n headers?: Record<string, string>;\n };\n responseType?: string;\n responseSchema?: any;\n summary?: string;\n description?: string;\n tags?: string[];\n operationId?: string;\n}\n\n/**\n * Dependency information\n */\ninterface DependencyInfo {\n packageName: string;\n version?: string;\n importPath: string;\n isExternal: boolean;\n}\n\n/**\n * Application/Router instance found in code\n */\nexport interface ApplicationInstance {\n name: string;\n filePath: string;\n className: 'Shokupan' | 'ShokupanRouter' | 'Controller';\n routes: RouteInfo[];\n mounted: MountInfo[];\n}\n\ninterface MountInfo {\n prefix: string;\n target: string; // Controller/Router name or file path\n dependency?: DependencyInfo;\n}\n\n/**\n * Main analyzer class\n */\nexport class OpenAPIAnalyzer {\n private files: CollectedFile[] = [];\n private applications: ApplicationInstance[] = [];\n private program?: ts.Program;\n\n private entrypoint?: string;\n\n constructor(private rootDir: string, entrypoint?: string) {\n if (entrypoint) {\n this.entrypoint = path.resolve(entrypoint);\n }\n }\n\n /**\n * Main analysis entry point\n */\n /**\n * Main analysis entry point\n */\n public async analyze(): Promise<{ applications: ApplicationInstance[]; }> {\n // console.log(`Analyzing directory: ${this.rootDir}`);\n\n // Step 1: Parse TypeScript files (which might involve scanning or using entrypoint)\n await this.parseTypeScriptFiles();\n\n // Step 2: Process source maps if needed\n await this.processSourceMaps();\n\n // Step 3: Find Shokupan applications\n await this.findApplications();\n\n // Step 4: Extract route information\n await this.extractRoutes();\n\n // Return the raw application data for further processing\n return { applications: this.applications };\n }\n\n /**\n * Recursively scan directory for TypeScript/JavaScript files\n */\n private async scanDirectory(dir: string): Promise<void> {\n try {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const fullPath = path.join(dir, entry.name);\n\n // Skip node_modules for source files (we'll handle deps separately)\n if (entry.isDirectory()) {\n if ([\"node_modules\", \".git\", \"dist\"].includes(entry.name)) {\n continue;\n }\n await this.scanDirectory(fullPath);\n } else {\n const ext = path.extname(entry.name);\n if ([\".ts\", \".tsx\", \".cts\", \".dts\", \".mts\", \".js\", \".jsx\", \".mjs\", \".cjs\", \".map\"].includes(ext)) {\n this.files.push({ path: fullPath, type: ext.slice(1) as any });\n }\n }\n }\n } catch (error: any) {\n // Silently skip directories that don't exist or can't be read\n if (error.code !== 'ENOENT' && error.code !== 'EACCES') {\n throw error;\n }\n }\n }\n\n /**\n * Process source maps to reconstruct TypeScript\n */\n private async processSourceMaps(): Promise<void> {\n // Find JS files that have corresponding .map files\n const jsFiles = this.files.filter(f => f.type === 'js');\n const mapFiles = this.files.filter(f => f.type === 'map');\n\n for (let i = 0; i < jsFiles.length; i++) {\n const jsFile = jsFiles[i];\n const mapFile = mapFiles.find(m => m.path === jsFile.path + '.map');\n\n if (mapFile && !this.files.some(f => f.path === jsFile.path.replace(/\\.js$/, '.ts'))) {\n // We have .js + .map but no .ts file\n // For now, we'll just parse the JS file directly\n // Full source map reconstruction would require the 'source-map' library\n console.log(`Note: Found ${jsFile.path} with source map but no .ts file. Will parse JS directly.`);\n }\n }\n }\n\n /**\n * Parse TypeScript files and create AST\n */\n private async parseTypeScriptFiles(): Promise<void> {\n let fileNames: string[] = [];\n\n if (this.entrypoint) {\n // If entrypoint is provided, let TypeScript resolve dependencies\n fileNames = [this.entrypoint];\n // console.log(`[Analyzer] Using entrypoint: ${this.entrypoint}`);\n } else {\n // Otherwise, scan the directory manually\n await this.scanDirectory(this.rootDir);\n const tsFiles = this.files.filter(f => f.type === 'ts' || f.type === 'js');\n fileNames = tsFiles.map(f => f.path);\n // console.log(`[Analyzer] Scanning directory, found ${fileNames.length} files`);\n }\n\n // Create TypeScript program\n this.program = ts.createProgram(fileNames, {\n target: ts.ScriptTarget.ESNext,\n module: ts.ModuleKind.ESNext,\n allowJs: true,\n moduleResolution: ts.ModuleResolutionKind.Node10,\n rootDir: this.rootDir,\n skipLibCheck: true,\n skipDefaultLibCheck: true,\n });\n\n // If using entrypoint, update this.files with what TS found in the project\n if (this.entrypoint) {\n this.files = this.program.getSourceFiles()\n .filter(sf => !sf.fileName.includes('node_modules'))\n .map(sf => ({ path: sf.fileName, type: sf.fileName.endsWith('.js') ? 'js' : 'ts' }));\n }\n }\n\n /**\n * Find all Shokupan/ShokupanRouter instances\n */\n private async findApplications(): Promise<void> {\n if (!this.program) return;\n\n const typeChecker = this.program.getTypeChecker();\n\n for (let i = 0; i < this.program.getSourceFiles().length; i++) {\n const sourceFile = this.program.getSourceFiles()[i];\n // Skip node_modules and declaration files/tests\n if (sourceFile.fileName.includes('node_modules')) continue;\n if (sourceFile.isDeclarationFile) continue;\n\n // Allow analyzing test files if we are pointing explicitly to a test directory (fixtures)\n // OR if the file is in a fixtures directory (used for OpenAPI spec generation from test apps)\n const isTestEnv = this.rootDir.includes('/test/') || this.rootDir.includes('/tests/') || this.rootDir.includes('/fixtures/');\n const isFixtureFile = sourceFile.fileName.includes('/fixtures/');\n\n if (!isTestEnv && !isFixtureFile) {\n if (sourceFile.fileName.includes('/test/') || sourceFile.fileName.includes('/tests/')) continue;\n if (sourceFile.fileName.includes('/base_test/')) continue;\n if (sourceFile.fileName.includes('.test.ts') || sourceFile.fileName.includes('.spec.ts')) continue;\n }\n\n ts.forEachChild(sourceFile, (node) => {\n this.visitNode(node, sourceFile, typeChecker);\n });\n }\n }\n\n /**\n * Visit AST node to find application instances\n */\n private visitNode(node: ts.Node, sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker): void {\n // Look for: class FooController ... @Controller(...)\n if (ts.isClassDeclaration(node)) {\n // Check for @Controller decorator\n let isController = false;\n let className = node.name?.getText(sourceFile);\n\n const decorators: ts.Decorator[] = (node as any).decorators || node.modifiers?.filter((m: any) => ts.isDecorator(m));\n\n if (decorators) {\n const controllerDecorator = decorators.find((d: any) => {\n const expr = d.expression;\n if (ts.isCallExpression(expr)) {\n const identifier = expr.expression.getText(sourceFile);\n return identifier === 'Controller';\n }\n return false;\n });\n if (controllerDecorator) isController = true;\n }\n\n // Fallback: Check for method decorators (@Get, @Post, etc.)\n if (!isController) {\n const hasRouteDecorators = node.members.some(m => {\n // Trust 175 as MethodDeclaration if TS matches mostly, or just check members\n if (ts.isMethodDeclaration(m) || m.kind === 175 || m.kind === 170 || m.kind === 171) {\n const decs = (m as any).decorators || (m as any).modifiers?.filter((mod: any) => ts.isDecorator(mod));\n if (decs) {\n return decs.some((d: any) => {\n const expr = d.expression;\n if (ts.isCallExpression(expr)) {\n const identifier = expr.expression.getText(sourceFile);\n return ['get', 'post', 'put', 'delete', 'patch', 'options', 'head'].includes(identifier.toLowerCase());\n }\n return false;\n });\n }\n }\n return false;\n });\n if (hasRouteDecorators) {\n isController = true;\n }\n }\n\n if (isController && className) {\n this.applications.push({\n name: className,\n filePath: sourceFile.fileName,\n className: 'Controller',\n routes: [],\n mounted: []\n });\n }\n }\n\n // Look for: new Shokupan() or new ShokupanRouter()\n if (ts.isVariableDeclaration(node) && node.initializer) {\n if (ts.isNewExpression(node.initializer)) {\n const expr = node.initializer;\n const className = expr.expression.getText(sourceFile);\n\n if (className === 'Shokupan' || className === 'ShokupanRouter') {\n const varName = node.name.getText(sourceFile);\n\n this.applications.push({\n name: varName,\n filePath: sourceFile.fileName,\n className: className as 'Shokupan' | 'ShokupanRouter',\n routes: [],\n mounted: []\n });\n }\n }\n }\n\n // Recursively visit children\n ts.forEachChild(node, (child) => this.visitNode(child, sourceFile, typeChecker));\n }\n\n /**\n * Extract route information from applications\n */\n private async extractRoutes(): Promise<void> {\n if (!this.program) return;\n\n for (let i = 0; i < this.applications.length; i++) {\n const app = this.applications[i];\n const sourceFile = this.program.getSourceFile(app.filePath);\n if (!sourceFile) continue;\n\n this.extractRoutesFromFile(app, sourceFile);\n }\n }\n\n /**\n * Extract routes from a Controller class\n */\n private extractRoutesFromController(app: ApplicationInstance, classNode: ts.ClassDeclaration, sourceFile: ts.SourceFile): void {\n const methods = classNode.members.filter(m => ts.isMethodDeclaration(m) || m.kind === 175);\n\n for (let i = 0; i < methods.length; i++) {\n const method = methods[i];\n const methodNode = method as any; // Cast to any to access decorators in newer/older TS mix\n if (!methodNode.decorators && !methodNode.modifiers) continue;\n\n const decorators = methodNode.decorators || methodNode.modifiers?.filter((m: any) => ts.isDecorator(m));\n if (!decorators) continue;\n\n // Find route decorators: @Get, @Post, etc.\n const routeDecorator = decorators.find((d: any) => {\n const expr = d.expression;\n if (ts.isCallExpression(expr)) {\n const identifier = expr.expression.getText(sourceFile);\n return ['get', 'post', 'put', 'delete', 'patch', 'options', 'head'].includes(identifier.toLowerCase());\n }\n return false;\n });\n\n if (routeDecorator && ts.isCallExpression(routeDecorator.expression)) {\n const decoratorName = routeDecorator.expression.expression.getText(sourceFile);\n const httpMethod = decoratorName.toUpperCase();\n let routePath = '/';\n\n // Get path\n const pathArg = routeDecorator.expression.arguments[0];\n if (pathArg && ts.isStringLiteral(pathArg)) {\n routePath = pathArg.text;\n }\n\n // Normalize path params: /users/:id -> /users/{id}\n routePath = routePath.replace(/:([a-zA-Z0-9_]+)/g, '{$1}');\n\n // Handler Name (Class.method)\n const handlerName = `${app.name}.${methodNode.name.getText(sourceFile)}`;\n\n // Analyze the method body\n const analysis = this.analyzeHandler(methodNode, sourceFile);\n\n app.routes.push({\n method: httpMethod,\n path: routePath,\n handlerName: handlerName,\n handlerSource: methodNode.getText(sourceFile),\n requestTypes: analysis.requestTypes,\n responseType: analysis.responseType,\n responseSchema: analysis.responseSchema\n });\n }\n }\n };\n\n /**\n * Extract routes from a specific file\n */\n private extractRoutesFromFile(app: ApplicationInstance, sourceFile: ts.SourceFile): void {\n if (app.className === 'Controller') {\n const classNode = sourceFile.statements.find(s => ts.isClassDeclaration(s) && s.name?.getText(sourceFile) === app.name) as ts.ClassDeclaration;\n if (classNode) {\n this.extractRoutesFromController(app, classNode, sourceFile);\n }\n } else {\n // Existing logic for app/router instances\n const visit = (node: ts.Node) => {\n // ... (rest of the existing logic)\n // Look for method calls: app.get(...), app.post(...), app.mount(...)\n if (ts.isCallExpression(node)) {\n const expr = node.expression;\n\n if (ts.isPropertyAccessExpression(expr)) {\n const objName = expr.expression.getText(sourceFile);\n const methodName = expr.name.getText(sourceFile);\n\n // Check if this is our application instance\n if (objName === app.name) {\n if (['get', 'post', 'put', 'delete', 'patch', 'options', 'head'].includes(methodName.toLowerCase())) {\n // Extract route info\n const route = this.extractRouteFromCall(node, sourceFile, methodName.toUpperCase());\n if (route) {\n app.routes.push(route);\n }\n } else if (methodName === 'mount') {\n // Extract mount info\n const mount = this.extractMountFromCall(node, sourceFile);\n if (mount) {\n app.mounted.push(mount);\n }\n }\n }\n }\n }\n\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n }\n }\n\n /**\n * Extract route information from a route call (e.g., app.get('/path', handler))\n */\n private extractRouteFromCall(node: ts.CallExpression, sourceFile: ts.SourceFile, method: string): RouteInfo | null {\n const args = node.arguments;\n\n if (args.length < 2) return null;\n\n const pathArg = args[0];\n let routePath = '/';\n\n if (ts.isStringLiteral(pathArg)) {\n routePath = pathArg.text;\n }\n\n // Normalize path params: /users/:id -> /users/{id}\n // This ensures matching with runtime-generated keys\n const normalizedPath = routePath.replace(/:([a-zA-Z0-9_]+)/g, '{$1}');\n\n let metadata: any = {};\n\n // Check for metadata argument (3 args: path, metadata, handler)\n if (args.length >= 3 && ts.isObjectLiteralExpression(args[1])) {\n const metaObj = args[1];\n // Extract summary, description, tags, etc.\n const rawMeta = this.convertExpressionToSchema(metaObj, sourceFile, new Map());\n\n // convertExpressionToSchema returns a schema-like object { type: 'object', properties: {...} }\n // But we want the actual values if they are literals.\n // convertExpressionToSchema is designed for SCHEMAS, not values.\n // We need a simpler value extractor or just parse the props directly for this specific case.\n\n for (let i = 0; i < metaObj.properties.length; i++) {\n const prop = metaObj.properties[i];\n if (ts.isPropertyAssignment(prop) && prop.name) {\n const name = prop.name.getText(sourceFile);\n const val = prop.initializer;\n\n if (ts.isStringLiteral(val)) {\n metadata[name] = val.text;\n } else if (ts.isArrayLiteralExpression(val) && name === 'tags') {\n metadata.tags = val.elements\n .filter(e => ts.isStringLiteral(e))\n .map(e => (e as ts.StringLiteral).text);\n } else if (name === 'operationId' && ts.isStringLiteral(val)) {\n metadata.operationId = val.text;\n }\n }\n }\n }\n\n // Extract handler information\n const handlerArg = args[args.length - 1];\n const handlerInfo = this.analyzeHandler(handlerArg, sourceFile);\n\n return {\n method,\n path: normalizedPath,\n handlerName: handlerArg.getText(sourceFile).substring(0, 50), // Truncate for display\n handlerSource: handlerArg.getText(sourceFile),\n requestTypes: handlerInfo.requestTypes,\n responseType: handlerInfo.responseType,\n responseSchema: handlerInfo.responseSchema,\n ...metadata\n };\n }\n\n /**\n * Analyze a route handler to extract type information\n */\n private analyzeHandler(handler: ts.Node, sourceFile: ts.SourceFile): {\n requestTypes?: RouteInfo['requestTypes'];\n responseType?: string;\n responseSchema?: any;\n } {\n const requestTypes: RouteInfo['requestTypes'] = {};\n let responseType: string | undefined;\n let responseSchema: any | undefined;\n let hasExplicitReturnType = false;\n\n // Simple scope to track variable types (name -> schema)\n const scope = new Map<string, any>();\n\n // Pre-populate scope with function parameters\n if (ts.isFunctionLike(handler)) {\n handler.parameters.forEach(param => {\n if (ts.isIdentifier(param.name) && param.type) {\n const paramName = param.name.getText(sourceFile);\n // Resolving TypeReference for parameters (e.g. User) would require partial type checker or expanded scope logic\n // For now, we rely on basic types\n const paramType = this.convertTypeNodeToSchema(param.type, sourceFile);\n if (paramType) {\n scope.set(paramName, paramType);\n }\n }\n });\n\n // Check for explicit return type annotation\n if (handler.type) {\n const returnSchema = this.convertTypeNodeToSchema(handler.type, sourceFile);\n if (returnSchema) {\n responseSchema = returnSchema;\n responseType = returnSchema.type;\n hasExplicitReturnType = true;\n }\n }\n }\n\n // Helper to analyze an expression that is being returned (either explicitly or implicitly)\n const analyzeReturnExpression = (expr: ts.Expression) => {\n let node = expr;\n // Unwrap await\n if (ts.isAwaitExpression(node)) {\n node = node.expression;\n }\n\n\n // Case 1: return ctx.json(...) or ctx.text(...)\n if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {\n const callObj = node.expression.expression.getText(sourceFile);\n const callProp = node.expression.name.getText(sourceFile);\n\n if (callObj === 'ctx' || callObj.endsWith('.ctx')) {\n if (callProp === 'json') {\n if (node.arguments.length > 0) {\n responseSchema = this.convertExpressionToSchema(node.arguments[0], sourceFile, scope);\n responseType = 'object';\n }\n return;\n }\n else if (callProp === 'text') {\n responseType = 'string';\n return;\n }\n }\n }\n\n // Case 2: Direct object return\n // Only use this if we haven't found a better schema yet, or if it looks specific\n // And if we don't have an explicit return type\n if (!hasExplicitReturnType && (!responseSchema || responseSchema.type === 'object')) {\n const schema = this.convertExpressionToSchema(node, sourceFile, scope);\n if (schema && (schema.type !== 'object' || Object.keys(schema.properties || {}).length > 0)) {\n responseSchema = schema;\n responseType = schema.type;\n }\n }\n\n // Fallback to text matching if schema inference failed and we still don't have a type\n if (!responseSchema && !responseType) {\n const returnText = node.getText(sourceFile);\n if (returnText.startsWith('{')) {\n responseType = 'object';\n } else if (returnText.startsWith('[')) {\n responseType = 'array';\n } else if (returnText.startsWith('\"') || returnText.startsWith(\"'\")) {\n responseType = 'string';\n }\n }\n };\n\n // Handle arrow functions\n let body: ts.Block | undefined;\n // Also check for Kind 175 (Method/Accessor in some TS versions)\n if (ts.isArrowFunction(handler) || ts.isFunctionExpression(handler) || ts.isMethodDeclaration(handler) || handler.kind === 175) {\n // TS method has .body which is FunctionBody (Block) or undefined\n body = (handler as any).body;\n\n // Visit the handler body to find ctx usage\n const visit = (node: ts.Node) => {\n // Check for type assertions on ctx.body()\n if (ts.isAsExpression(node)) {\n if (this.isCtxBodyCall(node.expression, sourceFile)) {\n const schema = this.convertTypeNodeToSchema(node.type, sourceFile);\n if (schema) {\n requestTypes.body = schema;\n\n // Track variables assigned to this body\n if (ts.isVariableDeclaration(node.parent)) {\n const varName = node.parent.name.getText(sourceFile);\n scope.set(varName, schema);\n }\n }\n }\n }\n\n // Look for ctx usage\n if (ts.isPropertyAccessExpression(node)) {\n const objText = node.expression.getText(sourceFile);\n const propText = node.name.getText(sourceFile);\n\n if (objText === 'ctx' || objText.endsWith('.ctx')) {\n if (propText === 'body') {\n if (!requestTypes.body) {\n requestTypes.body = { type: 'object' };\n }\n } else if (propText === 'query') {\n if (!requestTypes.query) requestTypes.query = {};\n } else if (propText === 'params') {\n if (!requestTypes.params) requestTypes.params = {};\n } else if (propText === 'headers') {\n if (!requestTypes.headers) requestTypes.headers = {};\n }\n }\n }\n\n // Explicit Return\n if (ts.isReturnStatement(node) && node.expression) {\n analyzeReturnExpression(node.expression);\n }\n\n // Implicit Return (Concise Arrow Function)\n // e.g. (ctx) => ctx.json(...) or .then(res => ctx.json(res))\n if (ts.isArrowFunction(node) && !ts.isBlock(node.body)) {\n analyzeReturnExpression(node.body as ts.Expression);\n }\n\n // Implicit Return call (e.g. ctx.json(...) as a statement without return)\n if (ts.isExpressionStatement(node)) {\n analyzeReturnExpression(node.expression);\n }\n\n ts.forEachChild(node, visit);\n };\n\n if (ts.isBlock(body)) {\n ts.forEachChild(body, visit);\n } else {\n // Main handler is an implicit return: app.get('/', (ctx) => ctx.json(...))\n analyzeReturnExpression(body);\n // Also verify children (e.g. if body is a CallExpression, verify args??)\n // analyzeReturnExpression analyzes the expression itself.\n // But we ALSO want to visit children to find other usages (request types)\n // or nested arrow functions in a call chain (e.g. chaining .then())\n ts.forEachChild(body, visit);\n }\n }\n\n return { requestTypes, responseType, responseSchema };\n }\n\n /**\n * Convert an Expression node to an OpenAPI schema (best effort)\n */\n private convertExpressionToSchema(node: ts.Expression, sourceFile: ts.SourceFile, scope: Map<string, any>): any {\n // Object Literal: { a: 1, b: \"text\" }\n if (ts.isObjectLiteralExpression(node)) {\n const schema: any = {\n type: 'object',\n properties: {},\n required: []\n };\n\n for (let i = 0; i < node.properties.length; i++) {\n const prop = node.properties[i];\n if (ts.isPropertyAssignment(prop)) {\n const name = prop.name.getText(sourceFile);\n const valueSchema = this.convertExpressionToSchema(prop.initializer, sourceFile, scope);\n\n schema.properties[name] = valueSchema;\n schema.required.push(name); // Properties in literal return are required\n }\n else if (ts.isShorthandPropertyAssignment(prop)) {\n const name = prop.name.getText(sourceFile);\n // Check scope for variable\n const scopedSchema = scope.get(name);\n schema.properties[name] = scopedSchema || { type: 'object' };\n schema.required.push(name);\n }\n }\n if (schema.required.length === 0) {\n delete schema.required;\n }\n return schema;\n }\n\n // Array Literal: [1, 2]\n if (ts.isArrayLiteralExpression(node)) {\n const schema: any = { type: 'array' };\n if (node.elements.length > 0) {\n // Infer item type from first element\n schema.items = this.convertExpressionToSchema(node.elements[0], sourceFile, scope);\n } else {\n schema.items = {};\n }\n return schema;\n }\n\n // Conditional (Ternary) Expression: cond ? trueVal : falseVal\n if (ts.isConditionalExpression(node)) {\n const trueSchema = this.convertExpressionToSchema(node.whenTrue, sourceFile, scope);\n // const falseSchema = this.convertExpressionToSchema(node.whenFalse, sourceFile, scope);\n\n // Simplified: return true branch schema. Ideally we'd do oneOf\n return trueSchema;\n }\n\n // Template Expression: `Hello ${name}`\n if (ts.isTemplateExpression(node)) {\n return { type: 'string' };\n }\n\n // Identifier (Variable reference)\n if (ts.isIdentifier(node)) {\n const name = node.getText(sourceFile);\n const scopedSchema = scope.get(name);\n if (scopedSchema) return scopedSchema;\n return { type: 'object' }; // Unknown reference\n }\n\n // Literals\n if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return { type: 'string' };\n if (ts.isNumericLiteral(node)) return { type: 'number' };\n if (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) return { type: 'boolean' };\n\n // Unknown\n return { type: 'object' };\n }\n\n /**\n * Check if an expression is a call to ctx.body()\n */\n private isCtxBodyCall(node: ts.Expression, sourceFile: ts.SourceFile): boolean {\n // Handle await ctx.body()\n if (ts.isAwaitExpression(node)) {\n return this.isCtxBodyCall(node.expression, sourceFile);\n }\n\n // Handle ctx.body()\n if (ts.isCallExpression(node)) {\n // Check expression: ctx.body\n if (ts.isPropertyAccessExpression(node.expression)) {\n const objText = node.expression.expression.getText(sourceFile);\n const propText = node.expression.name.getText(sourceFile);\n return (objText === 'ctx' || objText.endsWith('.ctx')) && propText === 'body';\n }\n }\n\n return false;\n }\n\n /**\n * Convert a TypeScript TypeNode to an OpenAPI schema\n */\n private convertTypeNodeToSchema(typeNode: ts.TypeNode, sourceFile: ts.SourceFile): any {\n switch (typeNode.kind) {\n case ts.SyntaxKind.StringKeyword:\n return { type: 'string' };\n case ts.SyntaxKind.NumberKeyword:\n return { type: 'number' };\n case ts.SyntaxKind.BooleanKeyword:\n return { type: 'boolean' };\n case ts.SyntaxKind.AnyKeyword:\n case ts.SyntaxKind.UnknownKeyword:\n return {}; // Any/Unknown -> empty schema (accepts anything)\n\n case ts.SyntaxKind.TypeLiteral: {\n const literal = typeNode as ts.TypeLiteralNode;\n const schema: any = {\n type: 'object',\n properties: {},\n required: []\n };\n\n for (let i = 0; i < literal.members.length; i++) {\n const member = literal.members[i];\n if (ts.isPropertySignature(member) && member.type) {\n const name = member.name.getText(sourceFile);\n const propSchema = this.convertTypeNodeToSchema(member.type, sourceFile);\n\n schema.properties[name] = propSchema;\n\n // Property is required unless it has a question token\n if (!member.questionToken) {\n schema.required.push(name);\n }\n }\n }\n\n if (schema.required.length === 0) {\n delete schema.required;\n }\n\n return schema;\n }\n\n case ts.SyntaxKind.ArrayType: {\n const arrayType = typeNode as ts.ArrayTypeNode;\n return {\n type: 'array',\n items: this.convertTypeNodeToSchema(arrayType.elementType, sourceFile)\n };\n }\n\n // Handle Type References (e.g. Array<string>)\n case ts.SyntaxKind.TypeReference: {\n const typeRef = typeNode as ts.TypeReferenceNode;\n const typeName = typeRef.typeName.getText(sourceFile);\n\n if (typeName === 'Array' && typeRef.typeArguments?.length > 0) {\n return {\n type: 'array',\n items: this.convertTypeNodeToSchema(typeRef.typeArguments[0], sourceFile)\n };\n }\n\n if (typeName === 'Promise' && typeRef.typeArguments?.length > 0) {\n return this.convertTypeNodeToSchema(typeRef.typeArguments[0], sourceFile);\n }\n\n // For other references, we default to string or object as fallback\n // A fuller implementation would resolve the reference\n return { type: 'object', description: `Ref: ${typeName}` };\n }\n\n default:\n return { type: 'object' };\n }\n }\n\n /**\n * Extract mount information from mount call\n */\n private extractMountFromCall(node: ts.CallExpression, sourceFile: ts.SourceFile): MountInfo | null {\n const args = node.arguments;\n\n if (args.length < 2) return null;\n\n const pathArg = args[0];\n const targetArg = args[1];\n\n let prefix = '/';\n if (ts.isStringLiteral(pathArg)) {\n prefix = pathArg.text;\n }\n\n const target = targetArg.getText(sourceFile);\n\n // Check if target is from node_modules\n const dependency = this.checkIfExternalDependency(target, sourceFile);\n\n return {\n prefix,\n target,\n dependency\n };\n }\n\n /**\n * Check if a reference is to an external dependency\n */\n private checkIfExternalDependency(identifier: string, sourceFile: ts.SourceFile): DependencyInfo | undefined {\n // This is a simplified check - in a full implementation, \n // we'd track imports and resolve them\n\n // For now, check if there's an import statement for this identifier\n const imports: ts.ImportDeclaration[] = [];\n\n ts.forEachChild(sourceFile, (node) => {\n if (ts.isImportDeclaration(node)) {\n imports.push(node);\n }\n });\n\n for (let i = 0; i < imports.length; i++) {\n const imp = imports[i];\n const moduleSpecifier = imp.moduleSpecifier;\n if (ts.isStringLiteral(moduleSpecifier)) {\n const modulePath = moduleSpecifier.text;\n\n // Check if it's a node_modules import (no relative path)\n if (!modulePath.startsWith('.') && !modulePath.startsWith('/')) {\n const namedBindings = imp.importClause?.namedBindings;\n\n if (namedBindings && ts.isNamedImports(namedBindings)) {\n for (let j = 0; j < namedBindings.elements.length; j++) {\n const element = namedBindings.elements[j];\n if (element.name.text === identifier) {\n // Try to read package version\n const version = this.getPackageVersion(modulePath);\n\n return {\n packageName: modulePath,\n version,\n importPath: modulePath,\n isExternal: true\n };\n }\n }\n }\n }\n }\n }\n\n return undefined;\n }\n\n /**\n * Get package version from package.json\n */\n private getPackageVersion(packageName: string): string | undefined {\n try {\n const packageJsonPath = path.join(this.rootDir, 'node_modules', packageName, 'package.json');\n if (fs.existsSync(packageJsonPath)) {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n return packageJson.version;\n }\n } catch (e) {\n // Ignore\n }\n return undefined;\n }\n\n /**\n * Generate OpenAPI specification\n */\n public generateOpenAPISpec(): any {\n const paths: Record<string, any> = {};\n\n const collectRoutes = (app: ApplicationInstance, prefix: string = '') => {\n // Add direct routes\n for (let i = 0; i < app.routes.length; i++) {\n const route = app.routes[i];\n // Ensure prefix handles slashes correctly\n const cleanPrefix = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;\n const cleanPath = route.path.startsWith('/') ? route.path : '/' + route.path;\n const fullPath = (cleanPrefix + cleanPath) || '/';\n\n // Normalization is already done in extractRouteFromCall, but double check\n const pathKey = fullPath.replace(/:([a-zA-Z0-9_]+)/g, '{$1}');\n\n if (!paths[pathKey]) {\n paths[pathKey] = {};\n }\n\n const method = route.method.toLowerCase();\n const operation: any = {\n summary: route.summary || `${route.method.toUpperCase()} ${pathKey}`,\n description: route.description,\n tags: route.tags,\n operationId: route.operationId,\n responses: {\n '200': {\n description: 'Successful response'\n }\n }\n };\n\n // Clean up undefined\n if (!operation.description) delete operation.description;\n if (!operation.tags) delete operation.tags;\n if (!operation.operationId) delete operation.operationId;\n\n // Add inferred response schema\n if (route.responseSchema) {\n operation.responses['200'].content = {\n 'application/json': {\n schema: route.responseSchema\n }\n };\n } else if (route.responseType) {\n // Fallback to basic type\n const contentType = route.responseType === 'string' ? 'text/plain' : 'application/json';\n operation.responses['200'].content = {\n [contentType]: {\n schema: { type: route.responseType }\n }\n };\n } else {\n // Default object\n operation.responses['200'].content = {\n 'application/json': {\n schema: { type: 'object' }\n }\n };\n }\n\n // Add request body schema if available\n if (route.requestTypes?.body) {\n operation.requestBody = {\n content: {\n 'application/json': {\n schema: route.requestTypes.body\n }\n }\n };\n }\n\n // Add query/path parameters\n const parameters: any[] = [];\n\n if (route.requestTypes?.query) {\n const entries = Object.entries(route.requestTypes.query);\n for (let i = 0; i < entries.length; i++) {\n const [key] = entries[i];\n parameters.push({\n name: key,\n in: 'query',\n schema: { type: 'string' }\n });\n }\n }\n\n if (route.requestTypes?.params) {\n // Also check for path params implied by the URL {param}\n // But assume extractRouteFromCall handled explicit ones?\n // Let's just trust requestTypes for now\n const entries = Object.entries(route.requestTypes.params);\n for (let i = 0; i < entries.length; i++) {\n const [key] = entries[i];\n parameters.push({\n name: key,\n in: 'path',\n required: true,\n schema: { type: 'string' }\n });\n }\n }\n\n // Extract params from path if not explicitly typed but present in URL\n // This backfills untyped params so they appear in spec\n const pathParams = pathKey.match(/{([^}]+)}/g);\n if (pathParams) {\n pathParams.forEach(p => {\n const name = p.slice(1, -1);\n if (!parameters.some(param => param.name === name && param.in === 'path')) {\n parameters.push({\n name,\n in: 'path',\n required: true,\n schema: { type: 'string' } // Default to string\n });\n }\n });\n }\n\n if (parameters.length > 0) {\n operation.parameters = parameters;\n }\n\n paths[pathKey][method] = operation;\n }\n\n // Recurse into mounted apps\n for (let i = 0; i < app.mounted.length; i++) {\n const mount = app.mounted[i];\n // We need to resolve the ApplicationInstance for the target\n // The 'mounted' array currently only stores { prefix, target, dependency }\n // We need to find the AppInstance that matches 'target' class name\n\n // Note: This simple matching by class name might be brittle if multiple files have same class name\n // In a full implementation we would resolve the file path.\n const mountedApp = this.applications.find(a => a.name === mount.target || a.className === mount.target);\n\n if (mountedApp) {\n // Prevent infinite recursion\n if (mountedApp === app) continue;\n\n const cleanPrefix = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;\n const mountPrefix = mount.prefix.startsWith('/') ? mount.prefix : '/' + mount.prefix;\n const nextPrefix = cleanPrefix + mountPrefix;\n\n collectRoutes(mountedApp, nextPrefix);\n }\n }\n };\n\n for (let i = 0; i < this.applications.length; i++) {\n const app = this.applications[i];\n // We only want to start collection from \"root\" apps? \n // Or just collect everything? \n // If we collect everything, we might duplicate routes if they are mounted.\n // Current findApplications finds ALL instances.\n\n // Heuristic: If this app is mounted by another app, don't collect it as root.\n // But we don't have back-references easily.\n // Simplified: Just collect everything for now, but realize that un-mounted routers might show up as root.\n // Ideally we need to find the \"Main\" app (root).\n\n // For now, let's collect all, but we might have duplicates if we traverse mounts.\n // Let's rely on the user to only have one Main entrypoint usually.\n // Or we can try to detect if it is mounted.\n\n const isMounted = this.applications.some(parent =>\n parent.mounted.some(m => m.target === app.name || m.target === app.className)\n );\n\n if (!isMounted) {\n collectRoutes(app);\n }\n }\n\n return {\n openapi: '3.1.0',\n info: {\n title: 'Shokupan API',\n version: '1.0.0',\n description: 'Auto-generated from Shokupan application analysis'\n },\n paths,\n components: {\n schemas: {}\n }\n };\n }\n}\n\n/**\n * Analyze a directory and generate OpenAPI spec\n */\nexport async function analyzeDirectory(directory: string): Promise<any> {\n const analyzer = new OpenAPIAnalyzer(directory);\n return await analyzer.analyze();\n}\n"],"names":["i"],"mappings":"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"}
|
package/dist/plugins/scalar.d.ts
CHANGED
|
@@ -10,6 +10,6 @@ export type ScalarPluginOptions = {
|
|
|
10
10
|
export declare class ScalarPlugin extends ShokupanRouter<any> {
|
|
11
11
|
private readonly pluginOptions;
|
|
12
12
|
constructor(pluginOptions?: ScalarPluginOptions);
|
|
13
|
-
init
|
|
13
|
+
private init;
|
|
14
14
|
onMount(parent: ShokupanRouter<any>): void;
|
|
15
15
|
}
|
package/dist/router.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ShokupanContext } from './context';
|
|
2
2
|
import { Shokupan } from './shokupan';
|
|
3
3
|
import { $appRoot, $childControllers, $childRouters, $isApplication, $isMounted, $isRouter, $mountPath, $parent, $routes } from './symbol';
|
|
4
|
-
import { GuardAPISpec, JSXRenderer, Method, MethodAPISpec, Middleware, OpenAPIOptions, ProcessResult, RequestOptions, RouteMetadata, ShokupanController, ShokupanHandler, ShokupanRoute, ShokupanRouteConfig, StaticServeOptions } from './types';
|
|
4
|
+
import { GuardAPISpec, JSXRenderer, Method, MethodAPISpec, Middleware, OpenAPIOptions, ProcessResult, RequestOptions, RouteMetadata, ShokupanController, ShokupanHandler, ShokupanHooks, ShokupanRoute, ShokupanRouteConfig, StaticServeOptions } from './types';
|
|
5
5
|
type HeadersInit = Headers | Record<string, string> | [string, string][];
|
|
6
6
|
export declare const RouterRegistry: Map<string, ShokupanRouter<any>>;
|
|
7
7
|
export declare const ShokupanApplicationTree: {};
|
|
@@ -15,6 +15,8 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
15
15
|
private [$parent];
|
|
16
16
|
[$childRouters]: ShokupanRouter<T>[];
|
|
17
17
|
[$childControllers]: ShokupanController[];
|
|
18
|
+
private hookCache;
|
|
19
|
+
private hooksInitialized;
|
|
18
20
|
middleware: Middleware[];
|
|
19
21
|
get rootConfig(): {
|
|
20
22
|
[x: string]: any;
|
|
@@ -26,6 +28,7 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
26
28
|
reusePort?: boolean;
|
|
27
29
|
controllersOnly?: boolean;
|
|
28
30
|
enableTracing?: boolean;
|
|
31
|
+
jsonParser?: "native" | "parse-json" | "secure-json-parse";
|
|
29
32
|
autoBackpressureFeedback?: boolean;
|
|
30
33
|
autoBackpressureLevel?: number;
|
|
31
34
|
enableMiddlewareTracking?: boolean;
|
|
@@ -46,7 +49,7 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
46
49
|
renderer?: JSXRenderer;
|
|
47
50
|
serverFactory?: import('./types').ServerFactory;
|
|
48
51
|
hooks?: {
|
|
49
|
-
onError?: (
|
|
52
|
+
onError?: (ctx: ShokupanContext<Record<string, any>>, error: unknown) => void | Promise<void>;
|
|
50
53
|
onRequestStart?: (ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
51
54
|
onRequestEnd?: (ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
52
55
|
onResponseStart?: (ctx: ShokupanContext<Record<string, any>>, response: Response) => void | Promise<void>;
|
|
@@ -57,7 +60,7 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
57
60
|
onWriteTimeout?: (ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
58
61
|
onRequestTimeout?: (ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
59
62
|
} | {
|
|
60
|
-
onError?: (
|
|
63
|
+
onError?: (ctx: ShokupanContext<Record<string, any>>, error: unknown) => void | Promise<void>;
|
|
61
64
|
onRequestStart?: (ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
62
65
|
onRequestEnd?: (ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
63
66
|
onResponseStart?: (ctx: ShokupanContext<Record<string, any>>, response: Response) => void | Promise<void>;
|
|
@@ -68,6 +71,7 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
68
71
|
onWriteTimeout?: (ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
69
72
|
onRequestTimeout?: (ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
70
73
|
}[];
|
|
74
|
+
validateStatusCodes?: boolean;
|
|
71
75
|
};
|
|
72
76
|
get root(): Shokupan<any>;
|
|
73
77
|
[$routes]: ShokupanRoute[];
|
|
@@ -132,22 +136,22 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
132
136
|
handler: ShokupanHandler<T>;
|
|
133
137
|
}[];
|
|
134
138
|
/**
|
|
135
|
-
* Makes
|
|
136
|
-
* This is useful for
|
|
139
|
+
* Makes an internal request through this router's full routing pipeline.
|
|
140
|
+
* This is useful for calling other routes internally and supports streaming responses.
|
|
137
141
|
* @param options The request options.
|
|
138
|
-
* @returns The
|
|
142
|
+
* @returns The raw Response object.
|
|
139
143
|
*/
|
|
140
|
-
|
|
144
|
+
internalRequest(arg: {
|
|
141
145
|
path: string;
|
|
142
146
|
method?: Method;
|
|
143
147
|
headers?: HeadersInit;
|
|
144
148
|
body?: any;
|
|
145
149
|
} | string): Promise<Response>;
|
|
146
150
|
/**
|
|
147
|
-
* Processes a request
|
|
151
|
+
* Processes a request for testing purposes.
|
|
152
|
+
* Returns a simplified { status, headers, data } object instead of a Response.
|
|
148
153
|
*/
|
|
149
|
-
|
|
150
|
-
private applyRouterHooks;
|
|
154
|
+
testRequest(options: RequestOptions): Promise<ProcessResult>;
|
|
151
155
|
private wrapWithHooks;
|
|
152
156
|
/**
|
|
153
157
|
* Find a route matching the given method and path.
|
|
@@ -164,11 +168,16 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
164
168
|
/**
|
|
165
169
|
* Adds a route to the router.
|
|
166
170
|
*
|
|
167
|
-
* @param
|
|
168
|
-
* @param
|
|
169
|
-
* @param
|
|
170
|
-
* @param
|
|
171
|
-
* @param
|
|
171
|
+
* @param arg - Route configuration object
|
|
172
|
+
* @param arg.method - HTTP method
|
|
173
|
+
* @param arg.path - URL path
|
|
174
|
+
* @param arg.spec - OpenAPI specification for the route
|
|
175
|
+
* @param arg.handler - Route handler function
|
|
176
|
+
* @param arg.regex - Custom regex for path matching
|
|
177
|
+
* @param arg.group - Group for the route
|
|
178
|
+
* @param arg.requestTimeout - Timeout for this route in milliseconds
|
|
179
|
+
* @param arg.renderer - JSX renderer for the route
|
|
180
|
+
* @param arg.controller - Controller for the route
|
|
172
181
|
*/
|
|
173
182
|
add({ method, path, spec, handler, regex: customRegex, group, requestTimeout, renderer, controller }: {
|
|
174
183
|
method: Method;
|
|
@@ -185,7 +194,7 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
185
194
|
* Adds a GET route to the router.
|
|
186
195
|
*
|
|
187
196
|
* @param path - URL path
|
|
188
|
-
* @param
|
|
197
|
+
* @param handlers - Route handler functions
|
|
189
198
|
*/
|
|
190
199
|
get(path: string, ...handlers: ShokupanHandler<T>[]): any;
|
|
191
200
|
/**
|
|
@@ -200,7 +209,7 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
200
209
|
* Adds a POST route to the router.
|
|
201
210
|
*
|
|
202
211
|
* @param path - URL path
|
|
203
|
-
* @param
|
|
212
|
+
* @param handlers - Route handler functions
|
|
204
213
|
*/
|
|
205
214
|
post(path: string, ...handlers: ShokupanHandler<T>[]): any;
|
|
206
215
|
/**
|
|
@@ -215,7 +224,7 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
215
224
|
* Adds a PUT route to the router.
|
|
216
225
|
*
|
|
217
226
|
* @param path - URL path
|
|
218
|
-
* @param
|
|
227
|
+
* @param handlers - Route handler functions
|
|
219
228
|
*/
|
|
220
229
|
put(path: string, ...handlers: ShokupanHandler<T>[]): any;
|
|
221
230
|
/**
|
|
@@ -230,7 +239,7 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
230
239
|
* Adds a DELETE route to the router.
|
|
231
240
|
*
|
|
232
241
|
* @param path - URL path
|
|
233
|
-
* @param
|
|
242
|
+
* @param handlers - Route handler functions
|
|
234
243
|
*/
|
|
235
244
|
delete(path: string, ...handlers: ShokupanHandler<T>[]): any;
|
|
236
245
|
/**
|
|
@@ -245,7 +254,7 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
245
254
|
* Adds a PATCH route to the router.
|
|
246
255
|
*
|
|
247
256
|
* @param path - URL path
|
|
248
|
-
* @param
|
|
257
|
+
* @param handlers - Route handler functions
|
|
249
258
|
*/
|
|
250
259
|
patch(path: string, ...handlers: ShokupanHandler<T>[]): any;
|
|
251
260
|
/**
|
|
@@ -260,7 +269,7 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
260
269
|
* Adds a OPTIONS route to the router.
|
|
261
270
|
*
|
|
262
271
|
* @param path - URL path
|
|
263
|
-
* @param
|
|
272
|
+
* @param handlers - Route handler functions
|
|
264
273
|
*/
|
|
265
274
|
options(path: string, ...handlers: ShokupanHandler<T>[]): any;
|
|
266
275
|
/**
|
|
@@ -275,7 +284,7 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
275
284
|
* Adds a HEAD route to the router.
|
|
276
285
|
*
|
|
277
286
|
* @param path - URL path
|
|
278
|
-
* @param
|
|
287
|
+
* @param handlers - Route handler functions
|
|
279
288
|
*/
|
|
280
289
|
head(path: string, ...handlers: ShokupanHandler<T>[]): any;
|
|
281
290
|
/**
|
|
@@ -317,5 +326,7 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
317
326
|
* Now includes runtime analysis of handler functions to infer request/response types.
|
|
318
327
|
*/
|
|
319
328
|
generateApiSpec(options?: OpenAPIOptions): any;
|
|
329
|
+
private ensureHooksInitialized;
|
|
330
|
+
runHooks(name: keyof ShokupanHooks, ...args: any[]): Promise<void>;
|
|
320
331
|
}
|
|
321
332
|
export {};
|