shokupan 0.9.0 → 0.10.1
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/analyzer-BqIe1p0R.js +35 -0
- package/dist/analyzer-BqIe1p0R.js.map +1 -0
- package/dist/analyzer-CKLGLFtx.cjs +35 -0
- package/dist/analyzer-CKLGLFtx.cjs.map +1 -0
- package/dist/{analyzer-Ce_7JxZh.js → analyzer.impl-CV6W1Eq7.js} +238 -21
- package/dist/analyzer.impl-CV6W1Eq7.js.map +1 -0
- package/dist/{analyzer-Bei1sVWp.cjs → analyzer.impl-D9Yi1Hax.cjs} +237 -20
- package/dist/analyzer.impl-D9Yi1Hax.cjs.map +1 -0
- package/dist/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/dist/context.d.ts +19 -7
- package/dist/http-server-BEMPIs33.cjs.map +1 -1
- package/dist/http-server-CCeagTyU.js.map +1 -1
- package/dist/index.cjs +1500 -275
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1482 -256
- package/dist/index.js.map +1 -1
- package/dist/plugins/application/api-explorer/plugin.d.ts +9 -0
- package/dist/plugins/application/api-explorer/static/explorer-client.mjs +880 -0
- package/dist/plugins/application/api-explorer/static/style.css +767 -0
- package/dist/plugins/application/api-explorer/static/theme.css +128 -0
- package/dist/plugins/application/asyncapi/generator.d.ts +3 -0
- package/dist/plugins/application/asyncapi/plugin.d.ts +15 -0
- package/dist/plugins/application/asyncapi/static/asyncapi-client.mjs +748 -0
- package/dist/plugins/application/asyncapi/static/style.css +565 -0
- package/dist/plugins/application/asyncapi/static/theme.css +128 -0
- package/dist/plugins/application/auth.d.ts +3 -1
- package/dist/plugins/application/dashboard/metrics-collector.d.ts +3 -1
- package/dist/plugins/application/dashboard/plugin.d.ts +13 -3
- package/dist/plugins/application/dashboard/static/registry.css +0 -53
- package/dist/plugins/application/dashboard/static/styles.css +29 -20
- package/dist/plugins/application/dashboard/static/tabulator.css +83 -31
- package/dist/plugins/application/dashboard/static/theme.css +128 -0
- package/dist/plugins/application/graphql-apollo.d.ts +33 -0
- package/dist/plugins/application/graphql-yoga.d.ts +25 -0
- package/dist/plugins/application/openapi/analyzer.d.ts +12 -119
- package/dist/plugins/application/openapi/analyzer.impl.d.ts +167 -0
- package/dist/plugins/application/scalar.d.ts +9 -2
- package/dist/router.d.ts +80 -51
- package/dist/shokupan.d.ts +14 -8
- package/dist/util/datastore.d.ts +71 -7
- package/dist/util/decorators.d.ts +2 -2
- package/dist/util/types.d.ts +96 -3
- package/package.json +32 -12
- package/dist/analyzer-Bei1sVWp.cjs.map +0 -1
- package/dist/analyzer-Ce_7JxZh.js.map +0 -1
- package/dist/plugins/application/dashboard/static/scrollbar.css +0 -24
- package/dist/plugins/application/dashboard/template.eta +0 -246
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class OpenAPIAnalyzer {
|
|
2
|
+
constructor(rootDir, entrypoint) {
|
|
3
|
+
this.rootDir = rootDir;
|
|
4
|
+
this.entrypoint = entrypoint;
|
|
5
|
+
}
|
|
6
|
+
analyzerImpl;
|
|
7
|
+
/**
|
|
8
|
+
* Main analysis entry point.
|
|
9
|
+
* Dynamically imports the implementation and runs the analysis.
|
|
10
|
+
*/
|
|
11
|
+
async analyze() {
|
|
12
|
+
const { OpenAPIAnalyzer: AnalyzerImpl } = await import("./analyzer.impl-CV6W1Eq7.js");
|
|
13
|
+
this.analyzerImpl = new AnalyzerImpl(this.rootDir, this.entrypoint);
|
|
14
|
+
return this.analyzerImpl.analyze();
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Generate OpenAPI specification.
|
|
18
|
+
* Must be called after analyze().
|
|
19
|
+
*/
|
|
20
|
+
generateOpenAPISpec() {
|
|
21
|
+
if (!this.analyzerImpl) {
|
|
22
|
+
throw new Error("Must call analyze() before generateOpenAPISpec()");
|
|
23
|
+
}
|
|
24
|
+
return this.analyzerImpl.generateOpenAPISpec();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function analyzeDirectory(directory) {
|
|
28
|
+
const analyzer = new OpenAPIAnalyzer(directory);
|
|
29
|
+
return await analyzer.analyze();
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
OpenAPIAnalyzer,
|
|
33
|
+
analyzeDirectory
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=analyzer-BqIe1p0R.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer-BqIe1p0R.js","sources":["../src/plugins/application/openapi/analyzer.ts"],"sourcesContent":["\n// Re-export types used in public API\nexport type { ApplicationInstance, RouteInfo } from './analyzer.impl';\nimport type { ApplicationInstance } from './analyzer.impl';\n\n/**\n * OpenAPI Analyzer Wrapper.\n * \n * This class wraps the actual OpenAPIAnalyzer implementation to facilitate\n * lazy loading of the 'typescript' peer dependency. The actual implementation\n * and the 'typescript' module are only loaded when `analyze()` is called.\n */\nexport class OpenAPIAnalyzer {\n private analyzerImpl: any;\n\n constructor(private rootDir: string, private entrypoint?: string) { }\n\n /**\n * Main analysis entry point.\n * Dynamically imports the implementation and runs the analysis.\n */\n public async analyze(): Promise<{ applications: ApplicationInstance[]; }> {\n // Dynamic import to avoid loading 'typescript' peer dependency if not needed (e.g. at runtime)\n const { OpenAPIAnalyzer: AnalyzerImpl } = await import('./analyzer.impl');\n this.analyzerImpl = new AnalyzerImpl(this.rootDir, this.entrypoint);\n return this.analyzerImpl.analyze();\n }\n\n /**\n * Generate OpenAPI specification.\n * Must be called after analyze().\n */\n public generateOpenAPISpec(): any {\n if (!this.analyzerImpl) {\n throw new Error('Must call analyze() before generateOpenAPISpec()');\n }\n return this.analyzerImpl.generateOpenAPISpec();\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":[],"mappings":"AAYO,MAAM,gBAAgB;AAAA,EAGzB,YAAoB,SAAyB,YAAqB;AAA9C,SAAA,UAAA;AAAyB,SAAA,aAAA;AAAA,EAAuB;AAAA,EAF5D;AAAA;AAAA;AAAA;AAAA;AAAA,EAQR,MAAa,UAA6D;AAEtE,UAAM,EAAE,iBAAiB,iBAAiB,MAAM,OAAO,6BAAiB;AACxE,SAAK,eAAe,IAAI,aAAa,KAAK,SAAS,KAAK,UAAU;AAClE,WAAO,KAAK,aAAa,QAAA;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,sBAA2B;AAC9B,QAAI,CAAC,KAAK,cAAc;AACpB,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACtE;AACA,WAAO,KAAK,aAAa,oBAAA;AAAA,EAC7B;AACJ;AAKA,eAAsB,iBAAiB,WAAiC;AACpE,QAAM,WAAW,IAAI,gBAAgB,SAAS;AAC9C,SAAO,MAAM,SAAS,QAAA;AAC1B;"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
class OpenAPIAnalyzer {
|
|
4
|
+
constructor(rootDir, entrypoint) {
|
|
5
|
+
this.rootDir = rootDir;
|
|
6
|
+
this.entrypoint = entrypoint;
|
|
7
|
+
}
|
|
8
|
+
analyzerImpl;
|
|
9
|
+
/**
|
|
10
|
+
* Main analysis entry point.
|
|
11
|
+
* Dynamically imports the implementation and runs the analysis.
|
|
12
|
+
*/
|
|
13
|
+
async analyze() {
|
|
14
|
+
const { OpenAPIAnalyzer: AnalyzerImpl } = await Promise.resolve().then(() => require("./analyzer.impl-D9Yi1Hax.cjs"));
|
|
15
|
+
this.analyzerImpl = new AnalyzerImpl(this.rootDir, this.entrypoint);
|
|
16
|
+
return this.analyzerImpl.analyze();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generate OpenAPI specification.
|
|
20
|
+
* Must be called after analyze().
|
|
21
|
+
*/
|
|
22
|
+
generateOpenAPISpec() {
|
|
23
|
+
if (!this.analyzerImpl) {
|
|
24
|
+
throw new Error("Must call analyze() before generateOpenAPISpec()");
|
|
25
|
+
}
|
|
26
|
+
return this.analyzerImpl.generateOpenAPISpec();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function analyzeDirectory(directory) {
|
|
30
|
+
const analyzer = new OpenAPIAnalyzer(directory);
|
|
31
|
+
return await analyzer.analyze();
|
|
32
|
+
}
|
|
33
|
+
exports.OpenAPIAnalyzer = OpenAPIAnalyzer;
|
|
34
|
+
exports.analyzeDirectory = analyzeDirectory;
|
|
35
|
+
//# sourceMappingURL=analyzer-CKLGLFtx.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer-CKLGLFtx.cjs","sources":["../src/plugins/application/openapi/analyzer.ts"],"sourcesContent":["\n// Re-export types used in public API\nexport type { ApplicationInstance, RouteInfo } from './analyzer.impl';\nimport type { ApplicationInstance } from './analyzer.impl';\n\n/**\n * OpenAPI Analyzer Wrapper.\n * \n * This class wraps the actual OpenAPIAnalyzer implementation to facilitate\n * lazy loading of the 'typescript' peer dependency. The actual implementation\n * and the 'typescript' module are only loaded when `analyze()` is called.\n */\nexport class OpenAPIAnalyzer {\n private analyzerImpl: any;\n\n constructor(private rootDir: string, private entrypoint?: string) { }\n\n /**\n * Main analysis entry point.\n * Dynamically imports the implementation and runs the analysis.\n */\n public async analyze(): Promise<{ applications: ApplicationInstance[]; }> {\n // Dynamic import to avoid loading 'typescript' peer dependency if not needed (e.g. at runtime)\n const { OpenAPIAnalyzer: AnalyzerImpl } = await import('./analyzer.impl');\n this.analyzerImpl = new AnalyzerImpl(this.rootDir, this.entrypoint);\n return this.analyzerImpl.analyze();\n }\n\n /**\n * Generate OpenAPI specification.\n * Must be called after analyze().\n */\n public generateOpenAPISpec(): any {\n if (!this.analyzerImpl) {\n throw new Error('Must call analyze() before generateOpenAPISpec()');\n }\n return this.analyzerImpl.generateOpenAPISpec();\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":[],"mappings":";;AAYO,MAAM,gBAAgB;AAAA,EAGzB,YAAoB,SAAyB,YAAqB;AAA9C,SAAA,UAAA;AAAyB,SAAA,aAAA;AAAA,EAAuB;AAAA,EAF5D;AAAA;AAAA;AAAA;AAAA;AAAA,EAQR,MAAa,UAA6D;AAEtE,UAAM,EAAE,iBAAiB,iBAAiB,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,QAAO,8BAAiB,CAAA;AACxE,SAAK,eAAe,IAAI,aAAa,KAAK,SAAS,KAAK,UAAU;AAClE,WAAO,KAAK,aAAa,QAAA;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,sBAA2B;AAC9B,QAAI,CAAC,KAAK,cAAc;AACpB,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACtE;AACA,WAAO,KAAK,aAAa,oBAAA;AAAA,EAC7B;AACJ;AAKA,eAAsB,iBAAiB,WAAiC;AACpE,QAAM,WAAW,IAAI,gBAAgB,SAAS;AAC9C,SAAO,MAAM,SAAS,QAAA;AAC1B;;;"}
|
|
@@ -23,8 +23,41 @@ class OpenAPIAnalyzer {
|
|
|
23
23
|
await this.processSourceMaps();
|
|
24
24
|
await this.findApplications();
|
|
25
25
|
await this.extractRoutes();
|
|
26
|
+
this.pruneApplications();
|
|
26
27
|
return { applications: this.applications };
|
|
27
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Remove GenericModules that are not mounted by any Shokupan application/router
|
|
31
|
+
*/
|
|
32
|
+
pruneApplications() {
|
|
33
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
34
|
+
const queue = [];
|
|
35
|
+
for (const app of this.applications) {
|
|
36
|
+
if (app.name !== "GenericModule") {
|
|
37
|
+
reachable.add(app.filePath);
|
|
38
|
+
queue.push(app);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
while (queue.length > 0) {
|
|
42
|
+
const app = queue.shift();
|
|
43
|
+
for (const mount of app.mounted) {
|
|
44
|
+
if (mount.targetFilePath && !reachable.has(mount.targetFilePath)) {
|
|
45
|
+
reachable.add(mount.targetFilePath);
|
|
46
|
+
const mountedApp = this.applications.find((a) => a.filePath === mount.targetFilePath);
|
|
47
|
+
if (mountedApp) {
|
|
48
|
+
queue.push(mountedApp);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
this.applications.length;
|
|
54
|
+
this.applications = this.applications.filter((app) => {
|
|
55
|
+
if (app.name === "GenericModule" && !reachable.has(app.filePath)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
28
61
|
/**
|
|
29
62
|
* Recursively scan directory for TypeScript/JavaScript files
|
|
30
63
|
*/
|
|
@@ -101,10 +134,13 @@ class OpenAPIAnalyzer {
|
|
|
101
134
|
const sourceFile = this.program.getSourceFiles()[i];
|
|
102
135
|
if (sourceFile.fileName.includes("node_modules")) continue;
|
|
103
136
|
if (sourceFile.isDeclarationFile) continue;
|
|
104
|
-
const isTestEnv = this.rootDir.includes("/test/") || this.rootDir.includes("/tests/") || this.rootDir.includes("/fixtures/");
|
|
137
|
+
const isTestEnv = this.rootDir.includes("/test/") || this.rootDir.includes("/tests/") || this.rootDir.includes("/fixtures/") || this.entrypoint && (this.entrypoint.includes("/test/") || this.entrypoint.includes("/tests/"));
|
|
105
138
|
const isFixtureFile = sourceFile.fileName.includes("/fixtures/");
|
|
106
|
-
|
|
107
|
-
|
|
139
|
+
const isEntrypoint = this.entrypoint && sourceFile.fileName === this.entrypoint;
|
|
140
|
+
if (!isTestEnv && !isFixtureFile && !isEntrypoint) {
|
|
141
|
+
if (sourceFile.fileName.includes("/test/") || sourceFile.fileName.includes("/tests/")) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
108
144
|
if (sourceFile.fileName.includes("/base_test/")) continue;
|
|
109
145
|
if (sourceFile.fileName.includes(".test.ts") || sourceFile.fileName.includes(".spec.ts")) continue;
|
|
110
146
|
}
|
|
@@ -119,6 +155,7 @@ class OpenAPIAnalyzer {
|
|
|
119
155
|
visitNode(node, sourceFile, typeChecker) {
|
|
120
156
|
if (ts.isClassDeclaration(node)) {
|
|
121
157
|
let isController = false;
|
|
158
|
+
let controllerPrefix;
|
|
122
159
|
let className = node.name?.getText(sourceFile);
|
|
123
160
|
const decorators = node.decorators || node.modifiers?.filter((m) => ts.isDecorator(m));
|
|
124
161
|
if (decorators) {
|
|
@@ -130,7 +167,13 @@ class OpenAPIAnalyzer {
|
|
|
130
167
|
}
|
|
131
168
|
return false;
|
|
132
169
|
});
|
|
133
|
-
if (controllerDecorator)
|
|
170
|
+
if (controllerDecorator) {
|
|
171
|
+
isController = true;
|
|
172
|
+
const expr = controllerDecorator.expression;
|
|
173
|
+
if (expr.arguments.length > 0 && ts.isStringLiteral(expr.arguments[0])) {
|
|
174
|
+
controllerPrefix = expr.arguments[0].text;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
134
177
|
}
|
|
135
178
|
if (!isController) {
|
|
136
179
|
const hasRouteDecorators = node.members.some((m) => {
|
|
@@ -141,7 +184,7 @@ class OpenAPIAnalyzer {
|
|
|
141
184
|
const expr = d.expression;
|
|
142
185
|
if (ts.isCallExpression(expr)) {
|
|
143
186
|
const identifier = expr.expression.getText(sourceFile);
|
|
144
|
-
return ["get", "post", "put", "delete", "patch", "options", "head"].includes(identifier.toLowerCase());
|
|
187
|
+
return ["get", "post", "put", "delete", "patch", "options", "head", "event"].includes(identifier.toLowerCase());
|
|
145
188
|
}
|
|
146
189
|
return false;
|
|
147
190
|
});
|
|
@@ -158,6 +201,7 @@ class OpenAPIAnalyzer {
|
|
|
158
201
|
name: className,
|
|
159
202
|
filePath: sourceFile.fileName,
|
|
160
203
|
className: "Controller",
|
|
204
|
+
controllerPrefix,
|
|
161
205
|
routes: [],
|
|
162
206
|
mounted: []
|
|
163
207
|
});
|
|
@@ -179,6 +223,24 @@ class OpenAPIAnalyzer {
|
|
|
179
223
|
}
|
|
180
224
|
}
|
|
181
225
|
}
|
|
226
|
+
if (ts.isCallExpression(node)) {
|
|
227
|
+
const expr = node.expression;
|
|
228
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
229
|
+
const method = expr.name.getText(sourceFile);
|
|
230
|
+
if (["get", "post", "put", "delete", "patch", "options", "head", "event", "on"].includes(method)) {
|
|
231
|
+
const existing = this.applications.find((a) => a.filePath === sourceFile.fileName);
|
|
232
|
+
if (!existing) {
|
|
233
|
+
this.applications.push({
|
|
234
|
+
name: "GenericModule",
|
|
235
|
+
filePath: sourceFile.fileName,
|
|
236
|
+
className: "Shokupan",
|
|
237
|
+
routes: [],
|
|
238
|
+
mounted: []
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
182
244
|
ts.forEachChild(node, (child) => this.visitNode(child, sourceFile, typeChecker));
|
|
183
245
|
}
|
|
184
246
|
/**
|
|
@@ -208,7 +270,7 @@ class OpenAPIAnalyzer {
|
|
|
208
270
|
const expr = d.expression;
|
|
209
271
|
if (ts.isCallExpression(expr)) {
|
|
210
272
|
const identifier = expr.expression.getText(sourceFile);
|
|
211
|
-
return ["get", "post", "put", "delete", "patch", "options", "head"].includes(identifier.toLowerCase());
|
|
273
|
+
return ["get", "post", "put", "delete", "patch", "options", "head", "event"].includes(identifier.toLowerCase());
|
|
212
274
|
}
|
|
213
275
|
return false;
|
|
214
276
|
});
|
|
@@ -230,7 +292,14 @@ class OpenAPIAnalyzer {
|
|
|
230
292
|
handlerSource: methodNode.getText(sourceFile),
|
|
231
293
|
requestTypes: analysis.requestTypes,
|
|
232
294
|
responseType: analysis.responseType,
|
|
233
|
-
responseSchema: analysis.responseSchema
|
|
295
|
+
responseSchema: analysis.responseSchema,
|
|
296
|
+
emits: analysis.emits,
|
|
297
|
+
sourceContext: {
|
|
298
|
+
file: sourceFile.fileName,
|
|
299
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(methodNode.getStart()).line + 1,
|
|
300
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(methodNode.getEnd()).line + 1,
|
|
301
|
+
highlights: analysis.highlights
|
|
302
|
+
}
|
|
234
303
|
});
|
|
235
304
|
}
|
|
236
305
|
}
|
|
@@ -251,8 +320,8 @@ class OpenAPIAnalyzer {
|
|
|
251
320
|
if (ts.isPropertyAccessExpression(expr)) {
|
|
252
321
|
const objName = expr.expression.getText(sourceFile);
|
|
253
322
|
const methodName = expr.name.getText(sourceFile);
|
|
254
|
-
if (objName === app.name) {
|
|
255
|
-
if (["get", "post", "put", "delete", "patch", "options", "head"].includes(methodName.toLowerCase())) {
|
|
323
|
+
if (objName === app.name || app.name === "GenericModule" && node.arguments.length >= 2) {
|
|
324
|
+
if (["get", "post", "put", "delete", "patch", "options", "head", "on", "event"].includes(methodName.toLowerCase())) {
|
|
256
325
|
const route = this.extractRouteFromCall(node, sourceFile, methodName.toUpperCase());
|
|
257
326
|
if (route) {
|
|
258
327
|
app.routes.push(route);
|
|
@@ -281,6 +350,8 @@ class OpenAPIAnalyzer {
|
|
|
281
350
|
let routePath = "/";
|
|
282
351
|
if (ts.isStringLiteral(pathArg)) {
|
|
283
352
|
routePath = pathArg.text;
|
|
353
|
+
} else {
|
|
354
|
+
routePath = "__DYNAMIC_EVENT__";
|
|
284
355
|
}
|
|
285
356
|
const normalizedPath = routePath.replace(/:([a-zA-Z0-9_]+)/g, "{$1}");
|
|
286
357
|
let metadata = {};
|
|
@@ -313,7 +384,14 @@ class OpenAPIAnalyzer {
|
|
|
313
384
|
requestTypes: handlerInfo.requestTypes,
|
|
314
385
|
responseType: handlerInfo.responseType,
|
|
315
386
|
responseSchema: handlerInfo.responseSchema,
|
|
316
|
-
|
|
387
|
+
emits: handlerInfo.emits,
|
|
388
|
+
...metadata,
|
|
389
|
+
sourceContext: {
|
|
390
|
+
file: sourceFile.fileName,
|
|
391
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(handlerArg.getStart()).line + 1,
|
|
392
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(handlerArg.getEnd()).line + 1,
|
|
393
|
+
highlights: handlerInfo.highlights
|
|
394
|
+
}
|
|
317
395
|
};
|
|
318
396
|
}
|
|
319
397
|
/**
|
|
@@ -324,6 +402,8 @@ class OpenAPIAnalyzer {
|
|
|
324
402
|
let responseType;
|
|
325
403
|
let responseSchema;
|
|
326
404
|
let hasExplicitReturnType = false;
|
|
405
|
+
const emits = [];
|
|
406
|
+
const highlights = [];
|
|
327
407
|
const scope = /* @__PURE__ */ new Map();
|
|
328
408
|
if (ts.isFunctionLike(handler)) {
|
|
329
409
|
handler.parameters.forEach((param) => {
|
|
@@ -362,6 +442,9 @@ class OpenAPIAnalyzer {
|
|
|
362
442
|
} else if (callProp === "text") {
|
|
363
443
|
responseType = "string";
|
|
364
444
|
return;
|
|
445
|
+
} else if (callProp === "html" || callProp === "jsx") {
|
|
446
|
+
responseType = "html";
|
|
447
|
+
return;
|
|
365
448
|
}
|
|
366
449
|
}
|
|
367
450
|
}
|
|
@@ -387,6 +470,27 @@ class OpenAPIAnalyzer {
|
|
|
387
470
|
if (ts.isArrowFunction(handler) || ts.isFunctionExpression(handler) || ts.isMethodDeclaration(handler) || handler.kind === 175) {
|
|
388
471
|
body = handler.body;
|
|
389
472
|
const visit = (node) => {
|
|
473
|
+
if (ts.isVariableDeclaration(node)) {
|
|
474
|
+
if (node.initializer && ts.isIdentifier(node.name)) {
|
|
475
|
+
const varName = node.name.getText(sourceFile);
|
|
476
|
+
let initializer = node.initializer;
|
|
477
|
+
if (ts.isAsExpression(initializer)) {
|
|
478
|
+
if (this.isCtxBodyCall(initializer.expression, sourceFile)) {
|
|
479
|
+
const schema = this.convertTypeNodeToSchema(initializer.type, sourceFile);
|
|
480
|
+
if (schema) {
|
|
481
|
+
requestTypes.body = schema;
|
|
482
|
+
scope.set(varName, schema);
|
|
483
|
+
}
|
|
484
|
+
} else {
|
|
485
|
+
const schema = this.convertExpressionToSchema(initializer, sourceFile, scope);
|
|
486
|
+
scope.set(varName, schema);
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
const schema = this.convertExpressionToSchema(initializer, sourceFile, scope);
|
|
490
|
+
scope.set(varName, schema);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
390
494
|
if (ts.isAsExpression(node)) {
|
|
391
495
|
if (this.isCtxBodyCall(node.expression, sourceFile)) {
|
|
392
496
|
const schema = this.convertTypeNodeToSchema(node.type, sourceFile);
|
|
@@ -399,6 +503,28 @@ class OpenAPIAnalyzer {
|
|
|
399
503
|
}
|
|
400
504
|
}
|
|
401
505
|
}
|
|
506
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
|
|
507
|
+
const objText = node.expression.expression.getText(sourceFile);
|
|
508
|
+
const propText = node.expression.name.getText(sourceFile);
|
|
509
|
+
if (objText === "ctx" || objText.endsWith(".ctx") || objText === "this") {
|
|
510
|
+
const startLine = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
|
|
511
|
+
const endLine = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1;
|
|
512
|
+
if (["text", "html", "jsx"].includes(propText)) {
|
|
513
|
+
highlights.push({ startLine, endLine, type: "return-success" });
|
|
514
|
+
} else if (propText === "json") {
|
|
515
|
+
let isStatic = false;
|
|
516
|
+
if (node.arguments.length > 0) {
|
|
517
|
+
const schema = this.convertExpressionToSchema(node.arguments[0], sourceFile, scope);
|
|
518
|
+
if (schema && (schema.type !== "object" || schema.properties && Object.keys(schema.properties).length > 0)) {
|
|
519
|
+
isStatic = true;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
highlights.push({ startLine, endLine, type: isStatic ? "return-success" : "return-warning" });
|
|
523
|
+
} else if (["send", "emit"].includes(propText)) {
|
|
524
|
+
highlights.push({ startLine, endLine, type: "emit" });
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
402
528
|
if (ts.isPropertyAccessExpression(node)) {
|
|
403
529
|
const objText = node.expression.getText(sourceFile);
|
|
404
530
|
const propText = node.name.getText(sourceFile);
|
|
@@ -416,14 +542,62 @@ class OpenAPIAnalyzer {
|
|
|
416
542
|
}
|
|
417
543
|
}
|
|
418
544
|
}
|
|
419
|
-
if (ts.isReturnStatement(node)
|
|
420
|
-
|
|
545
|
+
if (ts.isReturnStatement(node)) {
|
|
546
|
+
const startLine = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
|
|
547
|
+
const endLine = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1;
|
|
548
|
+
let isStatic = false;
|
|
549
|
+
if (node.expression) {
|
|
550
|
+
analyzeReturnExpression(node.expression);
|
|
551
|
+
if (responseSchema && (responseSchema.type !== "object" || responseSchema.properties && Object.keys(responseSchema.properties).length > 0)) {
|
|
552
|
+
isStatic = true;
|
|
553
|
+
}
|
|
554
|
+
} else if (hasExplicitReturnType) {
|
|
555
|
+
isStatic = true;
|
|
556
|
+
}
|
|
557
|
+
highlights.push({ startLine, endLine, type: isStatic ? "return-success" : "return-warning" });
|
|
421
558
|
}
|
|
422
559
|
if (ts.isArrowFunction(node) && !ts.isBlock(node.body)) {
|
|
560
|
+
const startLine = sourceFile.getLineAndCharacterOfPosition(node.body.getStart()).line + 1;
|
|
561
|
+
const endLine = sourceFile.getLineAndCharacterOfPosition(node.body.getEnd()).line + 1;
|
|
423
562
|
analyzeReturnExpression(node.body);
|
|
563
|
+
let isStatic = false;
|
|
564
|
+
if (responseSchema && (responseSchema.type !== "object" || responseSchema.properties && Object.keys(responseSchema.properties).length > 0)) {
|
|
565
|
+
isStatic = true;
|
|
566
|
+
}
|
|
567
|
+
highlights.push({ startLine, endLine, type: isStatic ? "return-success" : "return-warning" });
|
|
424
568
|
}
|
|
425
569
|
if (ts.isExpressionStatement(node)) {
|
|
426
570
|
analyzeReturnExpression(node.expression);
|
|
571
|
+
if (ts.isCallExpression(node.expression)) {
|
|
572
|
+
const expr = node.expression;
|
|
573
|
+
if (ts.isPropertyAccessExpression(expr.expression)) {
|
|
574
|
+
const objText = expr.expression.expression.getText(sourceFile);
|
|
575
|
+
const propText = expr.expression.name.getText(sourceFile);
|
|
576
|
+
if ((objText === "ctx" || objText.endsWith(".ctx") || (objText === "this" || objText.endsWith(".this"))) && propText === "emit") {
|
|
577
|
+
if (expr.arguments.length >= 1) {
|
|
578
|
+
const eventNameArg = expr.arguments[0];
|
|
579
|
+
if (ts.isStringLiteral(eventNameArg)) {
|
|
580
|
+
const eventName = eventNameArg.text;
|
|
581
|
+
let payload = { type: "object" };
|
|
582
|
+
if (expr.arguments.length >= 2) {
|
|
583
|
+
payload = this.convertExpressionToSchema(expr.arguments[1], sourceFile, scope);
|
|
584
|
+
}
|
|
585
|
+
const emitLoc = {
|
|
586
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(expr.getStart()).line + 1,
|
|
587
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(expr.getEnd()).line + 1
|
|
588
|
+
};
|
|
589
|
+
emits.push({ event: eventName, payload, location: emitLoc });
|
|
590
|
+
} else {
|
|
591
|
+
const emitLoc = {
|
|
592
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(expr.getStart()).line + 1,
|
|
593
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(expr.getEnd()).line + 1
|
|
594
|
+
};
|
|
595
|
+
emits.push({ event: "__DYNAMIC_EMIT__", payload: { type: "object" }, location: emitLoc });
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
427
601
|
}
|
|
428
602
|
ts.forEachChild(node, visit);
|
|
429
603
|
};
|
|
@@ -434,7 +608,7 @@ class OpenAPIAnalyzer {
|
|
|
434
608
|
ts.forEachChild(body, visit);
|
|
435
609
|
}
|
|
436
610
|
}
|
|
437
|
-
return { requestTypes, responseType, responseSchema };
|
|
611
|
+
return { requestTypes, responseType, responseSchema, emits, highlights };
|
|
438
612
|
}
|
|
439
613
|
/**
|
|
440
614
|
* Convert an Expression node to an OpenAPI schema (best effort)
|
|
@@ -586,10 +760,58 @@ class OpenAPIAnalyzer {
|
|
|
586
760
|
}
|
|
587
761
|
const target = targetArg.getText(sourceFile);
|
|
588
762
|
const dependency = this.checkIfExternalDependency(target, sourceFile);
|
|
763
|
+
let targetFilePath;
|
|
764
|
+
if (!dependency) {
|
|
765
|
+
let modulePath;
|
|
766
|
+
ts.forEachChild(sourceFile, (node2) => {
|
|
767
|
+
if (targetFilePath || modulePath) return;
|
|
768
|
+
if (ts.isImportDeclaration(node2)) {
|
|
769
|
+
const specifier = node2.moduleSpecifier;
|
|
770
|
+
if (ts.isStringLiteral(specifier)) {
|
|
771
|
+
const path2 = specifier.text;
|
|
772
|
+
if (path2.startsWith(".")) {
|
|
773
|
+
if (node2.importClause?.name?.getText(sourceFile) === target) {
|
|
774
|
+
modulePath = path2;
|
|
775
|
+
} else if (node2.importClause?.namedBindings && ts.isNamedImports(node2.importClause.namedBindings)) {
|
|
776
|
+
for (const element of node2.importClause.namedBindings.elements) {
|
|
777
|
+
if (element.name.getText(sourceFile) === target) {
|
|
778
|
+
modulePath = path2;
|
|
779
|
+
break;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
if (modulePath) {
|
|
788
|
+
const dir = path.dirname(sourceFile.fileName);
|
|
789
|
+
const absolutePath = path.resolve(dir, modulePath);
|
|
790
|
+
const extensions = [".ts", ".js", ".tsx", ".jsx", "/index.ts", "/index.js"];
|
|
791
|
+
for (const ext of extensions) {
|
|
792
|
+
if (fs.existsSync(absolutePath + ext)) {
|
|
793
|
+
targetFilePath = absolutePath + ext;
|
|
794
|
+
break;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (!targetFilePath && fs.existsSync(absolutePath)) {
|
|
798
|
+
targetFilePath = absolutePath;
|
|
799
|
+
}
|
|
800
|
+
if (!targetFilePath && fs.existsSync(path.join(absolutePath, "index.ts"))) {
|
|
801
|
+
targetFilePath = path.join(absolutePath, "index.ts");
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
589
805
|
return {
|
|
590
806
|
prefix,
|
|
591
807
|
target,
|
|
592
|
-
|
|
808
|
+
targetFilePath,
|
|
809
|
+
dependency,
|
|
810
|
+
sourceContext: {
|
|
811
|
+
file: sourceFile.fileName,
|
|
812
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1,
|
|
813
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1
|
|
814
|
+
}
|
|
593
815
|
};
|
|
594
816
|
}
|
|
595
817
|
/**
|
|
@@ -780,12 +1002,7 @@ class OpenAPIAnalyzer {
|
|
|
780
1002
|
};
|
|
781
1003
|
}
|
|
782
1004
|
}
|
|
783
|
-
async function analyzeDirectory(directory) {
|
|
784
|
-
const analyzer = new OpenAPIAnalyzer(directory);
|
|
785
|
-
return await analyzer.analyze();
|
|
786
|
-
}
|
|
787
1005
|
export {
|
|
788
|
-
OpenAPIAnalyzer
|
|
789
|
-
analyzeDirectory
|
|
1006
|
+
OpenAPIAnalyzer
|
|
790
1007
|
};
|
|
791
|
-
//# sourceMappingURL=analyzer-
|
|
1008
|
+
//# sourceMappingURL=analyzer.impl-CV6W1Eq7.js.map
|