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.
Files changed (49) hide show
  1. package/dist/analyzer-BqIe1p0R.js +35 -0
  2. package/dist/analyzer-BqIe1p0R.js.map +1 -0
  3. package/dist/analyzer-CKLGLFtx.cjs +35 -0
  4. package/dist/analyzer-CKLGLFtx.cjs.map +1 -0
  5. package/dist/{analyzer-Ce_7JxZh.js → analyzer.impl-CV6W1Eq7.js} +238 -21
  6. package/dist/analyzer.impl-CV6W1Eq7.js.map +1 -0
  7. package/dist/{analyzer-Bei1sVWp.cjs → analyzer.impl-D9Yi1Hax.cjs} +237 -20
  8. package/dist/analyzer.impl-D9Yi1Hax.cjs.map +1 -0
  9. package/dist/cli.cjs +1 -1
  10. package/dist/cli.js +1 -1
  11. package/dist/context.d.ts +19 -7
  12. package/dist/http-server-BEMPIs33.cjs.map +1 -1
  13. package/dist/http-server-CCeagTyU.js.map +1 -1
  14. package/dist/index.cjs +1500 -275
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.ts +3 -0
  17. package/dist/index.js +1482 -256
  18. package/dist/index.js.map +1 -1
  19. package/dist/plugins/application/api-explorer/plugin.d.ts +9 -0
  20. package/dist/plugins/application/api-explorer/static/explorer-client.mjs +880 -0
  21. package/dist/plugins/application/api-explorer/static/style.css +767 -0
  22. package/dist/plugins/application/api-explorer/static/theme.css +128 -0
  23. package/dist/plugins/application/asyncapi/generator.d.ts +3 -0
  24. package/dist/plugins/application/asyncapi/plugin.d.ts +15 -0
  25. package/dist/plugins/application/asyncapi/static/asyncapi-client.mjs +748 -0
  26. package/dist/plugins/application/asyncapi/static/style.css +565 -0
  27. package/dist/plugins/application/asyncapi/static/theme.css +128 -0
  28. package/dist/plugins/application/auth.d.ts +3 -1
  29. package/dist/plugins/application/dashboard/metrics-collector.d.ts +3 -1
  30. package/dist/plugins/application/dashboard/plugin.d.ts +13 -3
  31. package/dist/plugins/application/dashboard/static/registry.css +0 -53
  32. package/dist/plugins/application/dashboard/static/styles.css +29 -20
  33. package/dist/plugins/application/dashboard/static/tabulator.css +83 -31
  34. package/dist/plugins/application/dashboard/static/theme.css +128 -0
  35. package/dist/plugins/application/graphql-apollo.d.ts +33 -0
  36. package/dist/plugins/application/graphql-yoga.d.ts +25 -0
  37. package/dist/plugins/application/openapi/analyzer.d.ts +12 -119
  38. package/dist/plugins/application/openapi/analyzer.impl.d.ts +167 -0
  39. package/dist/plugins/application/scalar.d.ts +9 -2
  40. package/dist/router.d.ts +80 -51
  41. package/dist/shokupan.d.ts +14 -8
  42. package/dist/util/datastore.d.ts +71 -7
  43. package/dist/util/decorators.d.ts +2 -2
  44. package/dist/util/types.d.ts +96 -3
  45. package/package.json +32 -12
  46. package/dist/analyzer-Bei1sVWp.cjs.map +0 -1
  47. package/dist/analyzer-Ce_7JxZh.js.map +0 -1
  48. package/dist/plugins/application/dashboard/static/scrollbar.css +0 -24
  49. 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
- if (!isTestEnv && !isFixtureFile) {
107
- if (sourceFile.fileName.includes("/test/") || sourceFile.fileName.includes("/tests/")) continue;
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) isController = true;
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
- ...metadata
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) && node.expression) {
420
- analyzeReturnExpression(node.expression);
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
- dependency
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-Ce_7JxZh.js.map
1008
+ //# sourceMappingURL=analyzer.impl-CV6W1Eq7.js.map